@nimbus-sh/fabric 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +98 -11
  2. package/dist/bindings.d.ts +31 -33
  3. package/dist/bindings.d.ts.map +1 -1
  4. package/dist/bindings.js +108 -97
  5. package/dist/budgets.d.ts +102 -29
  6. package/dist/budgets.d.ts.map +1 -1
  7. package/dist/budgets.js +266 -44
  8. package/dist/do-calls.d.ts +20 -0
  9. package/dist/do-calls.d.ts.map +1 -1
  10. package/dist/do-calls.js +24 -11
  11. package/dist/fanout.d.ts +40 -32
  12. package/dist/fanout.d.ts.map +1 -1
  13. package/dist/fanout.js +48 -51
  14. package/dist/fenced-work.d.ts +3 -3
  15. package/dist/fenced-work.js +3 -3
  16. package/dist/host-wasm.d.ts +29 -0
  17. package/dist/host-wasm.d.ts.map +1 -0
  18. package/dist/host-wasm.js +31 -0
  19. package/dist/image-store.d.ts +1 -1
  20. package/dist/image-store.d.ts.map +1 -1
  21. package/dist/image-store.js +33 -1
  22. package/dist/inner-do-env.d.ts +83 -0
  23. package/dist/inner-do-env.d.ts.map +1 -0
  24. package/dist/inner-do-env.js +181 -0
  25. package/dist/isolate-pool.d.ts +40 -23
  26. package/dist/isolate-pool.d.ts.map +1 -1
  27. package/dist/isolate-pool.js +105 -55
  28. package/dist/process-fabric.d.ts +26 -11
  29. package/dist/process-fabric.d.ts.map +1 -1
  30. package/dist/process-fabric.js +44 -0
  31. package/dist/timers.d.ts +12 -0
  32. package/dist/timers.d.ts.map +1 -1
  33. package/dist/timers.js +44 -9
  34. package/dist/vendor/types.d.ts +11 -5
  35. package/dist/vendor/types.d.ts.map +1 -1
  36. package/dist/workerd-facet-host.d.ts.map +1 -1
  37. package/dist/workerd-facet-host.js +35 -30
  38. package/package.json +4 -4
  39. package/src/bindings.ts +121 -98
  40. package/src/budgets.ts +311 -53
  41. package/src/do-calls.ts +45 -11
  42. package/src/fanout.ts +62 -53
  43. package/src/fenced-work.ts +3 -3
  44. package/src/host-wasm.ts +41 -0
  45. package/src/image-store.ts +29 -2
  46. package/src/inner-do-env.ts +213 -0
  47. package/src/isolate-pool.ts +145 -75
  48. package/src/process-fabric.ts +55 -13
  49. package/src/timers.ts +45 -9
  50. package/src/vendor/types.ts +11 -5
  51. package/src/workerd-facet-host.ts +36 -31
package/README.md CHANGED
@@ -22,9 +22,19 @@ npm install @nimbus-sh/fabric
22
22
 
23
23
  ## Requirements
24
24
 
25
- Set `compatibility_flags: ["nodejs_compat"]` in your Worker. The timer
26
- dispatcher needs `AsyncLocalStorage`, which workerd ships only under that
27
- flag. Without it the module fails to load at deploy time.
25
+ Use a compatibility date of 2026-08-04 or later, or list `nodejs_compat` in
26
+ `compatibility_flags`. The timer dispatcher needs `AsyncLocalStorage`, which
27
+ workerd ships only under `nodejs_compat`, on by date from 2026-08-04. Without
28
+ it the module fails to load at deploy time.
29
+
30
+ `composeFabric` also needs `enhanced_error_serialization`: a compatibility
31
+ date of 2026-04-21 or later, or the flag in `compatibility_flags` on an older
32
+ date. A program's filesystem errors reach it across workerd RPC, and only
33
+ that flag carries their `code`. On workerd without it `composeFabric` throws,
34
+ naming both fixes, rather than every program seeing `EIO`. It throws where
35
+ it is called. At module scope the Worker fails at startup. A library host
36
+ that composes through `NimbusWorkspace.create({ fabric })` deploys and
37
+ starts, and its first create throws.
28
38
 
29
39
  Import the root inside a Worker. Outside workerd, import subpaths such as
30
40
  `@nimbus-sh/fabric/timers.js`, which are typed against plain objects and run
@@ -57,6 +67,15 @@ Worker that imports Nimbus's own entry (`@nimbus-sh/sdk/worker`) inherits
57
67
  that entry's composition, so a host that names its own namespace composes in
58
68
  a Worker that does not import it.
59
69
 
70
+ A program's filesystem calls reach the supervisor entrypoint through its
71
+ `answer(method, args)`, which resolves `{ value }`, or `{ refusal }` for an
72
+ error with a `code` (ENOENT, ENOTDIR, EEXIST), and the program's client
73
+ rethrows the refusal as the error a throw would have delivered. A refusal
74
+ thrown from an entrypoint is recorded by the platform as an exception
75
+ ("canceled ... your Worker's code had hung") although its caller was
76
+ answered. `SupervisorRPC` implements `answer`, so an entrypoint that extends
77
+ it has it; one written from scratch must implement it too.
78
+
60
79
  The route back to the host (namespace, dispatch method, supervisor
61
80
  entrypoint) is minted into every binding the fabric hands a program, in the
62
81
  host's isolate, and the entrypoints that answer those bindings read it from
@@ -224,13 +243,55 @@ Warm isolates are scoped to one session. A pool may opt into
224
243
  `cacheScope: 'global'` only if it takes no supervisor binding and keeps no
225
244
  user state.
226
245
 
227
- `Fanout` handles wider batches. One DO method can drive at most 4 concurrent
228
- loader fetches, so batches under 5 run in the coordinator and larger ones
229
- shard across up to 32 sibling objects, 4 at a time.
246
+ `Fanout` handles wider batches. A Durable Object may have 10 distinct Dynamic
247
+ Workers with in-flight requests at once (`DO_DYNAMIC_WORKER_LIMIT`), shared
248
+ across every concurrent request to it; repeated requests to one Dynamic
249
+ Worker count once. A batch that fits the coordinator's remaining headroom
250
+ runs there, one Dynamic Worker per task; a wider one shards across up to 32
251
+ sibling objects, 4 at a time, each spending its own headroom.
252
+
253
+ The loader ledger counts what is in flight per DO: pool and one-shot calls,
254
+ esbuild facet calls, git network ops, and every resident process for as long
255
+ as it lives. `dynamicWorkerHeadroom(ctx)` is what is left,
256
+ `claimDynamicWorkers(ctx, n)` reserves a width, `loaderLedgerStats(ctx)`
257
+ reports it all, and a limit refusal names the workers in flight.
258
+
259
+ `beginLoaderFetch(ctx, key)` holds a worker and returns the function that
260
+ ends the hold. To wait for room rather than be refused, take the hold with
261
+ `beginLoaderFetchWhenFree` instead:
230
262
 
231
- Each keyed `loader.get(id)` permanently holds one of roughly 5–6
232
- dynamic-worker slots. `loaderLedgerStats(ctx)` reports what you have
233
- consumed, and a cap refusal names the IDs holding slots.
263
+ ```ts
264
+ import { beginLoaderFetchWhenFree } from '@nimbus-sh/fabric';
265
+
266
+ const end = await beginLoaderFetchWhenFree(ctx, key, { signal: AbortSignal.timeout(15_000) });
267
+ try {
268
+ return await worker.getEntrypoint().run();
269
+ } catch (error) {
270
+ end(error); // a limit refusal pauses the ledger; anything else just ends the hold
271
+ throw error;
272
+ } finally {
273
+ end();
274
+ }
275
+ ```
276
+
277
+ It resolves, holding `key`, once `key` is already in flight (requests to
278
+ one worker count once) or one more distinct worker fits. Waits are let in in
279
+ the order they asked, one per freed slot, by whoever makes the room: any
280
+ hold's end, a claim's release, or the end of a refusal's pause, so a wait
281
+ sees Nimbus's own releases as well as the caller's. The hold is taken as the
282
+ wait is let in, so no other caller can take the slot first, and it is the
283
+ caller's to end. An aborted `signal` rejects the wait with its reason, and
284
+ the wait holds nothing. A resident process holds its worker for as long as
285
+ it runs, so bound a wait that room may never reach.
286
+
287
+ The platform counts a worker for a moment after its call returns, which no
288
+ release can show. So a call refused with "Dynamic worker concurrency limit
289
+ exceeded" hands the refusal to its end function, and the ledger then admits
290
+ no new worker for 50 ms, doubling to 2 s while refusals continue
291
+ (`loaderLedgerStats(ctx).pauseMs`; the headroom reads 0 meanwhile).
292
+ `IsolatePool` retries a refused call this way, for 15 s in all. Holds taken
293
+ under a claim (`beginLoaderFetch(ctx, key, claim)`, or `IsolatePool`'s
294
+ `claim` option, which `Fanout` passes) count inside its width.
234
295
 
235
296
  ## Process fabric
236
297
 
@@ -329,6 +390,32 @@ and never `method.call(ep, request)`, which workerd refuses.
329
390
 
330
391
  Nesting is capped at depth 4. Raise it with `NIMBUS_INNER_LOADER_DEPTH`.
331
392
 
393
+ A classic Durable Object binding is a local namespace inside the inner
394
+ Worker (`inner-do-env.ts`), because a namespace's API is synchronous and an
395
+ RpcPromise cannot travel as an argument. `innerWorkerModules` makes an adapter
396
+ the bundle's first import; it replaces `env.MY_DO` in the env every handler,
397
+ entrypoint and object of the isolate sees, so
398
+ `env.MY_DO.get(env.MY_DO.idFromName('x'))` needs no `await`. A stub is
399
+ `new RpcStub(target)`, with a prototype shaped as a Durable Object stub's:
400
+ an entrypoint of a dynamically-loaded Worker is not transferable, an RPC stub
401
+ is. The target relays each member the caller reaches (a call, a read, or a
402
+ path through both) to `NimbusDurableObjectNamespace.callOn` or `getOn`, and
403
+ the session reaches it on the object's facet. The adapter also exports the
404
+ build's class check, under a name the bundle never spells.
405
+ tests/unit/wrangler-dev-do-rpc-workerd.mjs checks the whole stub surface
406
+ against plain workerd.
407
+
408
+ Limits, where a stub differs from Cloudflare's:
409
+
410
+ - `typeof stub` is `'function'`, not `'object'`: the runtime makes every RPC
411
+ stub callable.
412
+ - A Worker Loader env cannot carry a stub. `env.LOADER.load({ env: { S: stub } })`
413
+ throws "RpcStub cannot be serialized in this context because it is not a
414
+ persistent stub", where Cloudflare passes it. The loader shim keeps a
415
+ child's code and loads it again in each later request, so the child's env
416
+ can carry nothing made in one request; a namespace is refused there on
417
+ Cloudflare too.
418
+
332
419
  ## Measured platform limits
333
420
 
334
421
  Figures below come from production workerd, June to August 2026. The code
@@ -370,8 +457,8 @@ or left to you.
370
457
  | Request-time `WebAssembly.compile`/`instantiate` CSP-blocked; wasm rides the loader modules map as `{ wasm: ArrayBuffer }`, compiled at module load | RPC of a compiled `Module` refused by structured clone; inlined bytes OOMed the supervisor |
371
458
  | Module scope bans I/O; `new Function` succeeds at module scope and throws at request time | code reaches a facet through the module map or not at all |
372
459
  | The facet start callback fires at most once | re-running it would re-execute the user's program |
373
- | ~5–6 concurrent dynamic workers per DO; at most 4 concurrent Loader fetches per DO method; loader-cache entries are never released | `IN_DO_THRESHOLD` = 5 sits under the fetch cap; every `loader.get(id)` permanently consumes a slot — counted per DO by the loader ledger, and a cap refusal names the ids holding them |
374
- | `ctx.facets.clone` is same-object only, absent from `@cloudflare/workers-types` and the pinned workerd, present in production | 18–31 ms / 45.7 MB, 34–54 ms / 1 GB; an unresolvable `src` silently EMPTIES the destination and reports success — `cloneStorage` enforces the both-ends validation |
460
+ | 10 distinct Dynamic Workers with in-flight requests per DO, shared across its concurrent requests; repeated requests to one Dynamic Worker count once ([changelog, 2026-08-28](https://developers.cloudflare.com/changelog/post/2026-08-28-durable-objects-dynamic-workers-limit/)) | `DO_DYNAMIC_WORKER_LIMIT`; `Fanout` sizes in-DO batches to the live headroom, and a refusal names the workers in flight |
461
+ | `ctx.facets.clone` is same-object only; declared by `@cloudflare/workers-types` 5 and present in workerd ≥ 1.20260926.1 and in production | 18–31 ms / 45.7 MB, 34–54 ms / 1 GB; an unresolvable `src` silently EMPTIES the destination and reports success — `cloneStorage` enforces the both-ends validation |
375
462
  | A DO dies at ~200 MiB of live wasm linear memory; reserved and written pages die at the same ceiling | lazy growth buys nothing; bound guest memory by rewriting the memory section |
376
463
  | A wasm stack suspended (JSPI) in one request cannot resume in another | 3 in-context resumes took 6 ms; the first cross-context one hit a 30 s timeout |
377
464
 
@@ -226,30 +226,20 @@ interface NimbusDoNamespaceProps {
226
226
  route?: HostRoute;
227
227
  }
228
228
  /**
229
- * `env.MY_DO` shim — a DurableObjectNamespace-like WorkerEntrypoint.
229
+ * The binding the loader passes for `env.MY_DO` (a WorkerEntrypoint of the
230
+ * session's isolate). The inner Worker does not use it as its namespace: a
231
+ * DurableObjectNamespace's API is synchronous, and this one's answers are
232
+ * RpcPromises, which cannot travel as arguments. The inner Worker replaces it
233
+ * with a local namespace (inner-do-env.ts, innerWorkerModules) that makes ids
234
+ * and stubs itself, and relays each member a stub's caller reaches, its
235
+ * fetch included, to `callOn` or `getOn`.
230
236
  *
231
- * Usage from inner Worker:
232
- * const id = await env.MY_DO.idFromName('x'); // AWAIT required
233
- * const stub = env.MY_DO.get(id);
234
- * await stub.fetch(request);
235
- *
236
- * IMPORTANT: unlike the real DurableObjectNamespace, idFromName /
237
- * newUniqueId / idFromString here return **Promises**, because they're
238
- * RPC-backed WorkerEntrypoint methods. The inner caller MUST `await`
239
- * them before passing the result to `.get()`. Workers RPC pipelining
240
- * does not currently allow passing an RpcPromise as a method argument
241
- * — the no-await form fails with:
242
- * "Could not serialize object of type \"RpcPromise\"."
243
- *
244
- * Typical real-Worker code written for Cloudflare's synchronous
245
- * DurableObjectNamespace needs a one-word change (add `await`).
246
- *
247
- * idFromName produces prefix `name:` (deterministic FNV-style hash);
248
- * newUniqueId uses `uniq:` (random). The prefixes keep the two id
249
- * spaces distinct so a name-derived id can't collide with a random
250
- * one.
237
+ * `idFromName`, `newUniqueId`, `idFromString` and `get` answer as before, for
238
+ * a caller that awaits them. idFromName produces prefix `name:` (a
239
+ * deterministic hash, innerDoIdFromName); newUniqueId uses `uniq:`; the
240
+ * prefixes keep the two id spaces distinct.
251
241
  */
252
- export declare class NimbusDurableObjectNamespace extends WorkerEntrypoint<unknown, NimbusDoNamespaceProps> {
242
+ export declare class NimbusDurableObjectNamespace extends WorkerEntrypoint<object, NimbusDoNamespaceProps> {
253
243
  /** Stable string id derived from a name. Hash is deterministic. */
254
244
  idFromName(name: string): string;
255
245
  /** Fresh random id (matches DurableObjectNamespace.newUniqueId()). */
@@ -258,25 +248,33 @@ export declare class NimbusDurableObjectNamespace extends WorkerEntrypoint<unkno
258
248
  idFromString(s: string): string;
259
249
  /** Return a stub bound to the given id. */
260
250
  get(id: string): unknown;
251
+ /**
252
+ * The member of object `id` at `path` (names from the object down), called
253
+ * with `args`: its answer, or what it throws, as the object gave it.
254
+ */
255
+ callOn(id: string, path: string[], args: unknown[]): Promise<unknown>;
256
+ /** The member of object `id` at `path`, read. */
257
+ getOn(id: string, path: string[]): Promise<unknown>;
261
258
  }
262
259
  /** Props the DO stub carries: which binding, which supervisor, which id. */
263
260
  interface NimbusDoStubProps extends NimbusDoNamespaceProps {
264
261
  id?: string;
265
262
  }
263
+ /** What the session's innerDoFetch answers: the object's response, as fields. */
264
+ export interface InnerDoFetchAnswer {
265
+ status: number;
266
+ statusText: string;
267
+ headers: [string, string][];
268
+ body: ArrayBuffer | null;
269
+ }
266
270
  /**
267
- * A Durable-Object-namespace-stub for a specific id. Exposes fetch()
268
- * and will, if we later need it, forward RPC method calls through a
269
- * dispatch helper. The important invariant: EVERY call resolves the
270
- * inner DO class via getInnerDoClass() (./inner-do-registry.js) and
271
- * spins up / attaches to a facet via the supervisor's ctx.facets in
272
- * the SAME outer request context — never reusing stubs across requests.
271
+ * A Durable-Object-namespace-stub for a specific id, for a caller of the
272
+ * binding's own `get`: its fetch. The important invariant: EVERY call
273
+ * resolves the inner DO class via getInnerDoClass() (./inner-do-registry.js)
274
+ * and spins up / attaches to a facet via the supervisor's ctx.facets in the
275
+ * SAME outer request context — never reusing stubs across requests.
273
276
  */
274
277
  export declare class NimbusDOStub extends WorkerEntrypoint<object, NimbusDoStubProps> {
275
- /**
276
- * Resolve the supervisor DO through the composed host namespace and
277
- * dispatch the innerDoFetch op through its one supervisorOp entrypoint —
278
- * a host forwards envelopes, not private _rpc* methods.
279
- */
280
278
  fetch(request: Request): Promise<Response>;
281
279
  }
282
280
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}
package/dist/bindings.js CHANGED
@@ -27,6 +27,7 @@ import { z } from 'zod/v4';
27
27
  import { disposeRpcResource, useRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
28
28
  import { supervisorEntrypoint, supervisorEntrypointName, stagedBootAssembler } from './composition.js';
29
29
  import { hostNamespaceBinding, hostOpDispatch } from './host-dispatch.js';
30
+ import { innerDoIdFromName } from './inner-do-env.js';
30
31
  import { assertModuleMapWithinCodeLimit } from './budgets.js';
31
32
  /**
32
33
  * `ctx.exports` — workerd's loopback bag, which the installed
@@ -586,46 +587,23 @@ export class NimbusLoadedEntrypoint extends WorkerEntrypoint {
586
587
  }
587
588
  }
588
589
  /**
589
- * `env.MY_DO` shim — a DurableObjectNamespace-like WorkerEntrypoint.
590
+ * The binding the loader passes for `env.MY_DO` (a WorkerEntrypoint of the
591
+ * session's isolate). The inner Worker does not use it as its namespace: a
592
+ * DurableObjectNamespace's API is synchronous, and this one's answers are
593
+ * RpcPromises, which cannot travel as arguments. The inner Worker replaces it
594
+ * with a local namespace (inner-do-env.ts, innerWorkerModules) that makes ids
595
+ * and stubs itself, and relays each member a stub's caller reaches, its
596
+ * fetch included, to `callOn` or `getOn`.
590
597
  *
591
- * Usage from inner Worker:
592
- * const id = await env.MY_DO.idFromName('x'); // AWAIT required
593
- * const stub = env.MY_DO.get(id);
594
- * await stub.fetch(request);
595
- *
596
- * IMPORTANT: unlike the real DurableObjectNamespace, idFromName /
597
- * newUniqueId / idFromString here return **Promises**, because they're
598
- * RPC-backed WorkerEntrypoint methods. The inner caller MUST `await`
599
- * them before passing the result to `.get()`. Workers RPC pipelining
600
- * does not currently allow passing an RpcPromise as a method argument
601
- * — the no-await form fails with:
602
- * "Could not serialize object of type \"RpcPromise\"."
603
- *
604
- * Typical real-Worker code written for Cloudflare's synchronous
605
- * DurableObjectNamespace needs a one-word change (add `await`).
606
- *
607
- * idFromName produces prefix `name:` (deterministic FNV-style hash);
608
- * newUniqueId uses `uniq:` (random). The prefixes keep the two id
609
- * spaces distinct so a name-derived id can't collide with a random
610
- * one.
598
+ * `idFromName`, `newUniqueId`, `idFromString` and `get` answer as before, for
599
+ * a caller that awaits them. idFromName produces prefix `name:` (a
600
+ * deterministic hash, innerDoIdFromName); newUniqueId uses `uniq:`; the
601
+ * prefixes keep the two id spaces distinct.
611
602
  */
612
603
  export class NimbusDurableObjectNamespace extends WorkerEntrypoint {
613
604
  /** Stable string id derived from a name. Hash is deterministic. */
614
605
  idFromName(name) {
615
- // Simple 64-bit-ish FNV-style hash → hex. Stable across runs;
616
- // distinct names → distinct strings; same name → same string.
617
- let h1 = 0xdeadbeef ^ name.length;
618
- let h2 = 0x41c6ce57 ^ name.length;
619
- for (let i = 0; i < name.length; i++) {
620
- const ch = name.charCodeAt(i);
621
- h1 = Math.imul(h1 ^ ch, 2654435761);
622
- h2 = Math.imul(h2 ^ ch, 1597334677);
623
- }
624
- h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
625
- h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
626
- const high = (h1 >>> 0).toString(16).padStart(8, '0');
627
- const low = (h2 >>> 0).toString(16).padStart(8, '0');
628
- return 'name:' + high + low;
606
+ return innerDoIdFromName(name);
629
607
  }
630
608
  /** Fresh random id (matches DurableObjectNamespace.newUniqueId()). */
631
609
  newUniqueId() {
@@ -650,72 +628,105 @@ export class NimbusDurableObjectNamespace extends WorkerEntrypoint {
650
628
  },
651
629
  });
652
630
  }
631
+ /**
632
+ * The member of object `id` at `path` (names from the object down), called
633
+ * with `args`: its answer, or what it throws, as the object gave it.
634
+ */
635
+ callOn(id, path, args) {
636
+ return innerDoMember(this.env, { ...(this.ctx.props || {}), id: String(id) }, path, args);
637
+ }
638
+ /** The member of object `id` at `path`, read. */
639
+ getOn(id, path) {
640
+ return innerDoMember(this.env, { ...(this.ctx.props || {}), id: String(id) }, path, null);
641
+ }
653
642
  }
654
643
  /**
655
- * A Durable-Object-namespace-stub for a specific id. Exposes fetch()
656
- * and will, if we later need it, forward RPC method calls through a
657
- * dispatch helper. The important invariant: EVERY call resolves the
658
- * inner DO class via getInnerDoClass() (./inner-do-registry.js) and
659
- * spins up / attaches to a facet via the supervisor's ctx.facets in
660
- * the SAME outer request context — never reusing stubs across requests.
644
+ * The session Durable Object, through the composed host namespace, and its
645
+ * one supervisorOp entrypoint: a host forwards envelopes, not private _rpc*
646
+ * methods. Throws when the binding cannot reach it; `release` drops the stub.
661
647
  */
662
- export class NimbusDOStub extends WorkerEntrypoint {
663
- /**
664
- * Resolve the supervisor DO through the composed host namespace and
665
- * dispatch the innerDoFetch op through its one supervisorOp entrypoint —
666
- * a host forwards envelopes, not private _rpc* methods.
667
- */
668
- async fetch(request) {
669
- const props = this.ctx.props || {};
670
- const supervisorDoId = String(props.supervisorDoId || '');
671
- if (!supervisorDoId)
672
- return new Response('Nimbus: supervisorDoId missing', { status: 500 });
673
- const bindingName = String(props.bindingName || '');
674
- const id = String(props.id || '');
675
- let stub = null;
676
- let dispatch;
677
- try {
678
- const ns = hostNamespaceBinding(this.env ?? {}, 'NimbusDOStub', props.route);
679
- stub = ns.get(ns.idFromString(supervisorDoId));
680
- dispatch = hostOpDispatch(stub, 'NimbusDOStub', props.route);
681
- }
682
- catch (e) {
683
- disposeRpcResource(stub);
684
- return new Response(`Nimbus: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
685
- }
686
- // Forward the full request (method, body, headers preserved) by
687
- // serializing what's needed and reconstructing on the other side.
688
- // The supervisor reconstitutes the Request from these fields and
689
- // invokes the facet.
690
- const body = request.method !== 'GET' && request.method !== 'HEAD'
691
- ? await request.arrayBuffer()
692
- : null;
648
+ function supervisorOf(env, props) {
649
+ const supervisorDoId = String(props.supervisorDoId || '');
650
+ if (!supervisorDoId)
651
+ throw new Error('supervisorDoId missing');
652
+ let stub = null;
653
+ try {
654
+ const ns = hostNamespaceBinding(env ?? {}, 'NimbusDOStub', props.route);
655
+ stub = ns.get(ns.idFromString(supervisorDoId));
656
+ const held = stub;
657
+ return { dispatch: hostOpDispatch(stub, 'NimbusDOStub', props.route), release: () => disposeRpcResource(held) };
658
+ }
659
+ catch (e) {
660
+ disposeRpcResource(stub);
661
+ throw e;
662
+ }
663
+ }
664
+ function isInnerDoFetchAnswer(value) {
665
+ return value !== null && typeof value === 'object'
666
+ && 'status' in value && typeof value.status === 'number'
667
+ && 'statusText' in value && typeof value.statusText === 'string'
668
+ && 'headers' in value && Array.isArray(value.headers)
669
+ && 'body' in value && (value.body === null || value.body instanceof ArrayBuffer);
670
+ }
671
+ /**
672
+ * The inner object's fetch: the request forwarded whole (method, body,
673
+ * headers) as fields the session reconstructs, and its response rebuilt here
674
+ * from the fields the session answers. A binding that cannot reach the
675
+ * session answers 500.
676
+ */
677
+ async function innerDoFetch(env, props, request) {
678
+ let supervisor;
679
+ try {
680
+ supervisor = supervisorOf(env, props);
681
+ }
682
+ catch (e) {
683
+ return new Response(`Nimbus: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
684
+ }
685
+ try {
686
+ const body = request.method !== 'GET' && request.method !== 'HEAD' ? await request.arrayBuffer() : null;
693
687
  const headerList = [];
694
688
  request.headers.forEach((v, k) => { headerList.push([k, v]); });
695
- try {
696
- return await useRpcResource(dispatch({
697
- op: 'innerDoFetch',
698
- args: [{
699
- bindingName,
700
- id,
701
- method: request.method,
702
- url: request.url,
703
- headers: headerList,
704
- body,
705
- }],
706
- }), (res) => {
707
- if (!(res instanceof Response)) {
708
- return new Response('Nimbus: innerDoFetch returned an invalid result', { status: 502 });
709
- }
710
- return new Response(res.body, {
711
- status: res.status,
712
- statusText: res.statusText,
713
- headers: res.headers,
714
- });
715
- });
716
- }
717
- finally {
718
- disposeRpcResource(stub);
719
- }
689
+ return await useRpcResource(supervisor.dispatch({
690
+ op: 'innerDoFetch',
691
+ args: [{ bindingName: String(props.bindingName || ''), id: String(props.id || ''), method: request.method, url: request.url, headers: headerList, body }],
692
+ }), (res) => {
693
+ if (!isInnerDoFetchAnswer(res)) {
694
+ return new Response('Nimbus: innerDoFetch returned an invalid result', { status: 502 });
695
+ }
696
+ return new Response(res.body, { status: res.status, statusText: res.statusText, headers: res.headers });
697
+ });
698
+ }
699
+ finally {
700
+ supervisor.release();
701
+ }
702
+ }
703
+ /**
704
+ * A member of the inner object, called with `args` or read (`args` null): the
705
+ * session reaches it on the object's facet and answers what it answered (a
706
+ * stub, function or stream travels as one) or rejects with what it threw,
707
+ * type and message kept.
708
+ */
709
+ async function innerDoMember(env, props, path, args) {
710
+ const supervisor = supervisorOf(env, props);
711
+ try {
712
+ return await supervisor.dispatch({
713
+ op: 'innerDoCall',
714
+ args: [{ bindingName: String(props.bindingName || ''), id: String(props.id || ''), path, args }],
715
+ });
716
+ }
717
+ finally {
718
+ supervisor.release();
719
+ }
720
+ }
721
+ /**
722
+ * A Durable-Object-namespace-stub for a specific id, for a caller of the
723
+ * binding's own `get`: its fetch. The important invariant: EVERY call
724
+ * resolves the inner DO class via getInnerDoClass() (./inner-do-registry.js)
725
+ * and spins up / attaches to a facet via the supervisor's ctx.facets in the
726
+ * SAME outer request context — never reusing stubs across requests.
727
+ */
728
+ export class NimbusDOStub extends WorkerEntrypoint {
729
+ async fetch(request) {
730
+ return innerDoFetch(this.env, this.ctx.props || {}, request);
720
731
  }
721
732
  }