ITX getter and Workers RPC promise pipelining

Conclusion#

Yes: the natural API can be:

await this.itx.agents.get("/agents/onboarding").create();

env.ITX.get() is a Workers RPC call. At runtime such a call returns a special, lazily-pipelined thenable/stub rather than a native JavaScript Promise. It can receive property reads and method calls before it resolves. Cloudflare documents the same shape as await promiseForCounter.increment() and also shows nested property traversal before resolution.

This branch pins workerd to 1.20260701.1 and Cap'n Web to @iterate-com/capnweb@0.10.0 (the upstream capnweb@0.10.0 release).

Implementation#

The server side is an ordinary WorkerEntrypoint RPC method:

export class ItxEntrypoint extends WorkerEntrypoint {
  async get() {
    return itxForScope(/* … */);
  }
}

Therefore the raw caller-side result of env.ITX.get() is the native Workers RPC custom thenable. The SDK preserves that exact object:

protected get itx(): PipelinedProject {
  return (this.#itx ??= this.env.ITX.get());
}

Cap'n Web makes the same distinction explicitly: an RpcPromise is a thenable that is also a proxy for its eventual result, and property access creates another pipelined promise.

Design implication#

This does not need a new generic record-and-replay proxy. Keep the raw Workers RPC result as the cached value and type it as a Workers-RPC pipelined project result. That preserves both lazy acquisition and one-round-trip chaining. Cap'n Web's RpcPromise<Project> is structurally close, but is a slightly broader promise to users because Cap'n Web also exposes .map() and onRpcBroken(), which native Workers RPC does not currently match. A small local intersection such as Project & Promise<Project> can describe the behavior this SDK actually exposes without claiming those extra methods.

There is no SDK proxy around the project or environment. The only special case is explicit at its call site:

const denied = await this.fetchProjectAuth(request, { policy: "project-member" });

That helper sends a fresh bodyless Request containing the URL, method, and headers for paths auth may decline, so a null result leaves the app's request body untouched. The auth callback POST belongs to auth and receives the complete request. Direct itx.auth calls are otherwise ordinary native RPC calls.

This was checked with the repository's pinned workerd@1.20260701.1. An outer JavaScript proxy needed receiver-binding ceremony for then, catch, finally, and disposal. Keeping the native object removes that entire failure mode and preserves pipelined nested calls directly.

Do not solve this by an async wrapper around get(): any such wrapper necessarily turns the native RPC thenable into a native Promise and destroys the pipeline.

Was this page helpful?