@nimbus-sh/fabric 0.9.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.
@@ -24,6 +24,7 @@
24
24
  * and binding types used by this implementation.
25
25
  */
26
26
  import { type HostRoute } from './composition.js';
27
+ import { type DynamicWorkerClaim } from './budgets.js';
27
28
  import type { FacetBindings } from '@nimbus-sh/core/runtime/facet-host.js';
28
29
  import type { WorkerLoader } from './vendor/types.js';
29
30
  /**
@@ -50,6 +51,12 @@ export interface IsolatePoolOptions {
50
51
  * caller that wants more sizes it against that budget (Fanout does).
51
52
  */
52
53
  concurrency?: number;
54
+ /**
55
+ * The width the caller claimed for this pool's dispatches
56
+ * (`claimDynamicWorkers`): they are held inside it rather than on top of
57
+ * it, and a refused one waits for a slot of the claim.
58
+ */
59
+ claim?: DynamicWorkerClaim;
53
60
  /** Per-task timeout in ms. Default 60_000. */
54
61
  timeoutMs?: number;
55
62
  /**
@@ -254,6 +261,8 @@ export declare class IsolatePool {
254
261
  private readonly loader;
255
262
  /** The hosting actor, as the loader budget ledger's per-DO key. */
256
263
  private readonly ctx;
264
+ /** The width this pool's dispatches are held inside (IsolatePoolOptions.claim). */
265
+ private readonly claim;
257
266
  private readonly concurrency;
258
267
  private readonly defaultTimeoutMs;
259
268
  private readonly defaultRetries;
@@ -1 +1 @@
1
- {"version":3,"file":"isolate-pool.d.ts","sourceRoot":"","sources":["../src/isolate-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAcxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGrE;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,IAAI;IAC9B,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACnD,CAAC,MAAM,CAAC,CAAC;AAEV,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,mDAAmD;AACnD,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAClC;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;;;;OAgBG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;CAChE;AAED,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC3C;AAED,oEAAoE;AACpE,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;CACrC;AA0CD,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,8EAA8E;AAC9E,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,+BAA+B,GACvC,MAAM,CAgFR;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,WAAW;;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAC7D;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAC9D,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAsC;IAEtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAOzB;IACH;;;;wDAIoD;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,2EAA2E;IAC3E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAE7B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,kBAAkB,EACvB,IAAI,CAAC,EAAE,kBAAkB;IAmH3B,wEAAwE;IACxE,IAAI,kBAAkB,IAAI,MAAM,CAE/B;IAqaD;;;OAGG;IACG,MAAM,CAAC,CAAC,EAAE,CAAC,EACf,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACrB,GAAG,EAAE,CAAC,EACN,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAatB;;;;;;;;OAQG;IACG,aAAa,CACjB,EAAE,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,EAC1E,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC,QAAQ,CAAC;IAwBpB;;;;;OAKG;IACG,GAAG,CAAC,CAAC,EAAE,CAAC,EACZ,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACrB,KAAK,EAAE,CAAC,EAAE,EACV,IAAI,CAAC,EAAE,iBAAiB,GACvB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAOpC;;;;;;;;;;;;;;;;OAgBG;IACG,SAAS,CAAC,CAAC,EAAE,CAAC,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,CAAC,EAAE,EACV,IAAI,CAAC,EAAE,iBAAiB,GACvB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IA+DpC;;;;;;;;;;;;;;OAcG;IACH,OAAO,IAAI,IAAI;CAShB"}
1
+ {"version":3,"file":"isolate-pool.d.ts","sourceRoot":"","sources":["../src/isolate-pool.ts"],"names":[],"mappings":"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"}
@@ -28,17 +28,18 @@ import { supervisorEntrypoint } from './composition.js';
28
28
  import { supervisorBindingProps, supervisorLoaderKey } from './supervisor-props.js';
29
29
  import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
30
30
  import { serializeFunction, hashSource } from './vendor/serialize.js';
31
- import { beginLoaderFetch, withDynamicWorkerCapNamed } from './budgets.js';
31
+ import { beginLoaderFetch, beginLoaderFetchWhenFree, withDynamicWorkerCapNamed, } from './budgets.js';
32
32
  import { assertModuleMapWithinCodeLimit } from './budgets.js';
33
33
  import { recordFailure, setLastFacetId, getLastRpcFrame } from '@nimbus-sh/platform/oom-discriminator.js';
34
34
  import { classifyError } from '@nimbus-sh/platform/oom-classify.js';
35
35
  import { BindingError, ExecutionError, RetryExhaustedError, TimeoutError, } from './vendor/errors.js';
36
36
  import { hostWasmIdentity } from './host-wasm.js';
37
37
  /**
38
- * How long one call waits, in all, for the platform to admit it after
39
- * "Dynamic worker concurrency limit exceeded" (doubling from 50 ms, at most
40
- * 2 s a wait). A deployed Durable Object admitted the refused batch after a
41
- * 6 s pause; 15 s bounds a call that would never be admitted.
38
+ * How long one call waits, in all, on the Dynamic Worker ledger after
39
+ * "Dynamic worker concurrency limit exceeded" before the refusal surfaces
40
+ * (beginLoaderFetchWhenFree: let in when a hold ends or the refusal's pause
41
+ * passes). A deployed Durable Object admitted the refused batch after a 6 s
42
+ * pause; 15 s bounds a call that would never be admitted.
42
43
  */
43
44
  const CAP_REFUSAL_WAIT_MS = 15_000;
44
45
  /**
@@ -120,6 +121,8 @@ export class IsolatePool {
120
121
  loader;
121
122
  /** The hosting actor, as the loader budget ledger's per-DO key. */
122
123
  ctx;
124
+ /** The width this pool's dispatches are held inside (IsolatePoolOptions.claim). */
125
+ claim;
123
126
  concurrency;
124
127
  defaultTimeoutMs;
125
128
  defaultRetries;
@@ -183,6 +186,7 @@ export class IsolatePool {
183
186
  }
184
187
  this.loader = loader;
185
188
  this.ctx = ctx;
189
+ this.claim = opts?.claim;
186
190
  this.concurrency = Math.max(1, opts?.concurrency ?? 1);
187
191
  this.defaultTimeoutMs = opts?.timeoutMs ?? 60_000;
188
192
  this.defaultRetries = Math.max(0, opts?.retries ?? 0);
@@ -455,7 +459,7 @@ export class IsolatePool {
455
459
  * the attempt index lets a fetch invoke mint a fresh clone for retries,
456
460
  * since a Request's body is consumed once.
457
461
  */
458
- async #dispatchSlot(fnSource, fnHash, slotIndex, invoke, resilience, perCallWasm, wasAborted) {
462
+ async #dispatchSlot(fnSource, fnHash, slotIndex, invoke, resilience, perCallWasm, signal) {
459
463
  // A warm slot executes one dispatch at a time: queue behind the
460
464
  // previous owner, then record this dispatch as the new tail. The
461
465
  // tail outlives the caller's outcome: a timeout rejects the
@@ -472,7 +476,7 @@ export class IsolatePool {
472
476
  }
473
477
  const inFlight = [];
474
478
  try {
475
- return await this.#dispatchSlotOwned(fnSource, fnHash, slotIndex, invoke, resilience, perCallWasm, inFlight, wasAborted);
479
+ return await this.#dispatchSlotOwned(fnSource, fnHash, slotIndex, invoke, resilience, perCallWasm, inFlight, signal);
476
480
  }
477
481
  finally {
478
482
  // Do not delay the caller's own outcome — the tail releases when
@@ -480,7 +484,7 @@ export class IsolatePool {
480
484
  void Promise.allSettled(inFlight).then(() => release());
481
485
  }
482
486
  }
483
- async #dispatchSlotOwned(fnSource, fnHash, slotIndex, invoke, resilience, perCallWasm, inFlight, wasAborted) {
487
+ async #dispatchSlotOwned(fnSource, fnHash, slotIndex, invoke, resilience, perCallWasm, inFlight, signal) {
484
488
  // Per-call wasm fingerprint. Mixed into the cache key so two calls
485
489
  // with different bytes hit different slots (no cache poisoning).
486
490
  // For the common case (no per-call wasm) the fingerprint is '0',
@@ -502,6 +506,9 @@ export class IsolatePool {
502
506
  setLastFacetId(id, slotIndex);
503
507
  }
504
508
  catch { /* best-effort */ }
509
+ // A hold the ledger already took for the next attempt, when a refused
510
+ // call waited on it for room; otherwise the attempt begins its own.
511
+ let admitted;
505
512
  const runOnce = async () => {
506
513
  // loader.get() is synchronous from the caller's POV; the callback
507
514
  // is only invoked on cache miss. We wrap the callback tightly so a
@@ -527,15 +534,21 @@ export class IsolatePool {
527
534
  // fine and stays — it only tears down the long-lived SUPERVISOR
528
535
  // binding stub once the whole pool is done, which does NOT
529
536
  // invalidate any in-flight slot's entrypoint reference.
530
- const stub = this.loader.get(id, async () => code);
531
- const entrypoint = stub.getEntrypoint();
532
- // Direct property call, awaited by this frame — bracketed, never
533
- // wrapped. See beginLoaderFetch for the measured DO-poisoning hazard.
534
- const endFetch = beginLoaderFetch(this.ctx, id);
537
+ //
538
+ // The hold comes first, so it ends whatever setup throws: a retry's
539
+ // was taken when the ledger let it in.
540
+ const endFetch = admitted ?? beginLoaderFetch(this.ctx, id, this.claim);
541
+ admitted = undefined;
535
542
  try {
543
+ const stub = this.loader.get(id, async () => code);
544
+ const entrypoint = stub.getEntrypoint();
545
+ // Direct property call, awaited by this frame — bracketed, never
546
+ // wrapped. See beginLoaderFetch for the measured DO-poisoning hazard.
536
547
  return await invoke(entrypoint, attempt);
537
548
  }
538
549
  catch (err) {
550
+ // The ledger learns a limit refusal from the hold it ends.
551
+ endFetch(err);
539
552
  if (err instanceof Error) {
540
553
  throw new ExecutionError(err.message, err.stack);
541
554
  }
@@ -548,8 +561,8 @@ export class IsolatePool {
548
561
  const maxAttempts = 1 + resilience.retries;
549
562
  let lastError;
550
563
  let retriedCloneRefusal = false;
551
- let capRefusals = 0;
552
- let capWaitedMs = 0;
564
+ // When this call's waits for room after a limit refusal run out.
565
+ let capDeadline;
553
566
  let attempt = 0;
554
567
  while (attempt < maxAttempts) {
555
568
  try {
@@ -609,7 +622,7 @@ export class IsolatePool {
609
622
  });
610
623
  }
611
624
  catch { /* fail-soft */ }
612
- if (wasAborted?.()) {
625
+ if (signal?.aborted) {
613
626
  // The caller aborted this dispatch's Request. workerd cancelled
614
627
  // the isolate's execution context wherever it was suspended —
615
628
  // mid-syscall, mid-stream — so the interpreter's heap may hold
@@ -635,16 +648,31 @@ export class IsolatePool {
635
648
  // reverse direction. This refresh targets the stale-loader case.
636
649
  continue;
637
650
  }
638
- if (cause === 'dynamic_worker_cap' && capWaitedMs < CAP_REFUSAL_WAIT_MS) {
639
- // The platform refused to start this call: it still counts a
640
- // worker this Durable Object's ledger has already given back (a
641
- // fan-out's workers stay counted for a moment after their calls
642
- // return). Nothing ran, so the call waits, as the platform asks,
643
- // and is sent again; it does not spend an attempt.
644
- const delay = Math.min(CAP_REFUSAL_WAIT_MS - capWaitedMs, 50 * 2 ** capRefusals++, 2000);
645
- capWaitedMs += delay;
646
- await new Promise((resolve) => setTimeout(resolve, delay));
647
- continue;
651
+ if (cause === 'dynamic_worker_cap') {
652
+ // The platform refused to start this call. Nothing ran, so the
653
+ // call waits, as the platform asks, and is sent again without
654
+ // spending an attempt: on the ledger, which lets it in when a
655
+ // hold ends, or when the pause this refusal started has passed
656
+ // (the platform still counts a worker the ledger has given back:
657
+ // a fan-out's workers stay counted for a moment after their
658
+ // calls return).
659
+ capDeadline ??= Date.now() + CAP_REFUSAL_WAIT_MS;
660
+ const remainingMs = capDeadline - Date.now();
661
+ if (remainingMs > 0) {
662
+ // The deadline's timer is cleared once the wait settles: a
663
+ // pending timer keeps the hosting object from hibernating.
664
+ const deadline = new AbortController();
665
+ const timer = setTimeout(() => deadline.abort(), remainingMs);
666
+ const waitFor = signal ? AbortSignal.any([deadline.signal, signal]) : deadline.signal;
667
+ admitted = await beginLoaderFetchWhenFree(this.ctx, id, { claim: this.claim, signal: waitFor })
668
+ .catch(() => undefined)
669
+ .finally(() => clearTimeout(timer));
670
+ if (admitted)
671
+ continue;
672
+ // The caller aborted while waiting: surface its abort as above.
673
+ if (signal?.aborted)
674
+ throw lastError;
675
+ }
648
676
  }
649
677
  if (attempt < maxAttempts - 1) {
650
678
  // 100 * 2^attempt, capped at 2s so retries don't compound waiting.
@@ -696,7 +724,7 @@ export class IsolatePool {
696
724
  // Every attempt fetches a clone: a Request's body is consumed once,
697
725
  // so the caller's original stays unspent and retries get a fresh
698
726
  // body that follows the same signal.
699
- return this.#dispatchSlot(fnSource, fnHash, 0, (entrypoint) => entrypoint.fetch(request.clone()), resilience, opts?.wasmModules, () => request.signal.aborted);
727
+ return this.#dispatchSlot(fnSource, fnHash, 0, (entrypoint) => entrypoint.fetch(request.clone()), resilience, opts?.wasmModules, request.signal);
700
728
  }
701
729
  /**
702
730
  * Run `fn` on every item in `items`, at most `concurrency` at a time,
@@ -1 +1 @@
1
- {"version":3,"file":"workerd-facet-host.d.ts","sourceRoot":"","sources":["../src/workerd-facet-host.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workerd-facet-host.d.ts","sourceRoot":"","sources":["../src/workerd-facet-host.ts"],"names":[],"mappings":"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"}
@@ -470,6 +470,11 @@ async function runOneShot(ctx, env, supervisor, params, consume) {
470
470
  disposeRpcResource(response);
471
471
  }
472
472
  }
473
+ catch (error) {
474
+ // A limit refusal pauses the ledger's admissions (beginLoaderFetchWhenFree).
475
+ endFetch(error);
476
+ throw error;
477
+ }
473
478
  finally {
474
479
  endFetch();
475
480
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbus-sh/fabric",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "The Cloudflare half of Nimbus \u2014 Durable Object facet hosting, dynamic-worker loader pools, the resident-process fabric, and DO alarm/hibernation machinery.",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -50,7 +50,7 @@
50
50
  "typecheck": "tsc --noEmit"
51
51
  },
52
52
  "dependencies": {
53
- "@nimbus-sh/core": "^0.14.0",
53
+ "@nimbus-sh/core": "^0.15.0",
54
54
  "@nimbus-sh/platform": "^0.7.0",
55
55
  "zod": "^4.4.3"
56
56
  },
package/src/bindings.ts CHANGED
@@ -29,6 +29,7 @@ import { disposeRpcResource, useRpcResource } from '@nimbus-sh/platform/rpc-disp
29
29
  import { hostNamespace, supervisorEntrypoint, supervisorEntrypointName, stagedBootAssembler } from './composition.js';
30
30
  import type { HostRoute } from './composition.js';
31
31
  import { hostNamespaceBinding, hostOpDispatch, type HostOpDispatch } from './host-dispatch.js';
32
+ import { innerDoIdFromName } from './inner-do-env.js';
32
33
  import { assertModuleMapWithinCodeLimit } from './budgets.js';
33
34
  import type { EntrypointLoopbackFactory } from './composition.js';
34
35
  import type { WorkerCode } from './vendor/types.js';
@@ -708,46 +709,23 @@ interface NimbusDoNamespaceProps {
708
709
  }
709
710
 
710
711
  /**
711
- * `env.MY_DO` shim — a DurableObjectNamespace-like WorkerEntrypoint.
712
+ * The binding the loader passes for `env.MY_DO` (a WorkerEntrypoint of the
713
+ * session's isolate). The inner Worker does not use it as its namespace: a
714
+ * DurableObjectNamespace's API is synchronous, and this one's answers are
715
+ * RpcPromises, which cannot travel as arguments. The inner Worker replaces it
716
+ * with a local namespace (inner-do-env.ts, innerWorkerModules) that makes ids
717
+ * and stubs itself, and relays each member a stub's caller reaches, its
718
+ * fetch included, to `callOn` or `getOn`.
712
719
  *
713
- * Usage from inner Worker:
714
- * const id = await env.MY_DO.idFromName('x'); // AWAIT required
715
- * const stub = env.MY_DO.get(id);
716
- * await stub.fetch(request);
717
- *
718
- * IMPORTANT: unlike the real DurableObjectNamespace, idFromName /
719
- * newUniqueId / idFromString here return **Promises**, because they're
720
- * RPC-backed WorkerEntrypoint methods. The inner caller MUST `await`
721
- * them before passing the result to `.get()`. Workers RPC pipelining
722
- * does not currently allow passing an RpcPromise as a method argument
723
- * — the no-await form fails with:
724
- * "Could not serialize object of type \"RpcPromise\"."
725
- *
726
- * Typical real-Worker code written for Cloudflare's synchronous
727
- * DurableObjectNamespace needs a one-word change (add `await`).
728
- *
729
- * idFromName produces prefix `name:` (deterministic FNV-style hash);
730
- * newUniqueId uses `uniq:` (random). The prefixes keep the two id
731
- * spaces distinct so a name-derived id can't collide with a random
732
- * one.
720
+ * `idFromName`, `newUniqueId`, `idFromString` and `get` answer as before, for
721
+ * a caller that awaits them. idFromName produces prefix `name:` (a
722
+ * deterministic hash, innerDoIdFromName); newUniqueId uses `uniq:`; the
723
+ * prefixes keep the two id spaces distinct.
733
724
  */
734
- export class NimbusDurableObjectNamespace extends WorkerEntrypoint<unknown, NimbusDoNamespaceProps> {
725
+ export class NimbusDurableObjectNamespace extends WorkerEntrypoint<object, NimbusDoNamespaceProps> {
735
726
  /** Stable string id derived from a name. Hash is deterministic. */
736
727
  idFromName(name: string): string {
737
- // Simple 64-bit-ish FNV-style hash → hex. Stable across runs;
738
- // distinct names → distinct strings; same name → same string.
739
- let h1 = 0xdeadbeef ^ name.length;
740
- let h2 = 0x41c6ce57 ^ name.length;
741
- for (let i = 0; i < name.length; i++) {
742
- const ch = name.charCodeAt(i);
743
- h1 = Math.imul(h1 ^ ch, 2654435761);
744
- h2 = Math.imul(h2 ^ ch, 1597334677);
745
- }
746
- h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
747
- h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
748
- const high = (h1 >>> 0).toString(16).padStart(8, '0');
749
- const low = (h2 >>> 0).toString(16).padStart(8, '0');
750
- return 'name:' + high + low;
728
+ return innerDoIdFromName(name);
751
729
  }
752
730
 
753
731
  /** Fresh random id (matches DurableObjectNamespace.newUniqueId()). */
@@ -774,6 +752,19 @@ export class NimbusDurableObjectNamespace extends WorkerEntrypoint<unknown, Nimb
774
752
  },
775
753
  });
776
754
  }
755
+
756
+ /**
757
+ * The member of object `id` at `path` (names from the object down), called
758
+ * with `args`: its answer, or what it throws, as the object gave it.
759
+ */
760
+ callOn(id: string, path: string[], args: unknown[]): Promise<unknown> {
761
+ return innerDoMember(this.env, { ...(this.ctx.props || {}), id: String(id) }, path, args);
762
+ }
763
+
764
+ /** The member of object `id` at `path`, read. */
765
+ getOn(id: string, path: string[]): Promise<unknown> {
766
+ return innerDoMember(this.env, { ...(this.ctx.props || {}), id: String(id) }, path, null);
767
+ }
777
768
  }
778
769
 
779
770
  /** Props the DO stub carries: which binding, which supervisor, which id. */
@@ -782,70 +773,102 @@ interface NimbusDoStubProps extends NimbusDoNamespaceProps {
782
773
  }
783
774
 
784
775
  /**
785
- * A Durable-Object-namespace-stub for a specific id. Exposes fetch()
786
- * and will, if we later need it, forward RPC method calls through a
787
- * dispatch helper. The important invariant: EVERY call resolves the
788
- * inner DO class via getInnerDoClass() (./inner-do-registry.js) and
789
- * spins up / attaches to a facet via the supervisor's ctx.facets in
790
- * the SAME outer request context — never reusing stubs across requests.
776
+ * The session Durable Object, through the composed host namespace, and its
777
+ * one supervisorOp entrypoint: a host forwards envelopes, not private _rpc*
778
+ * methods. Throws when the binding cannot reach it; `release` drops the stub.
791
779
  */
792
- export class NimbusDOStub extends WorkerEntrypoint<object, NimbusDoStubProps> {
793
- /**
794
- * Resolve the supervisor DO through the composed host namespace and
795
- * dispatch the innerDoFetch op through its one supervisorOp entrypoint —
796
- * a host forwards envelopes, not private _rpc* methods.
797
- */
798
- async fetch(request: Request): Promise<Response> {
799
- const props: NimbusDoStubProps = this.ctx.props || {};
800
- const supervisorDoId = String(props.supervisorDoId || '');
801
- if (!supervisorDoId) return new Response('Nimbus: supervisorDoId missing', { status: 500 });
802
- const bindingName = String(props.bindingName || '');
803
- const id = String(props.id || '');
804
- let stub: DurableObjectStub | null = null;
805
- let dispatch: HostOpDispatch;
806
- try {
807
- const ns = hostNamespaceBinding(this.env ?? {}, 'NimbusDOStub', props.route);
808
- stub = ns.get(ns.idFromString(supervisorDoId));
809
- dispatch = hostOpDispatch(stub, 'NimbusDOStub', props.route);
810
- } catch (e) {
811
- disposeRpcResource(stub);
812
- return new Response(`Nimbus: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
813
- }
814
- // Forward the full request (method, body, headers preserved) by
815
- // serializing what's needed and reconstructing on the other side.
816
- // The supervisor reconstitutes the Request from these fields and
817
- // invokes the facet.
818
- const body = request.method !== 'GET' && request.method !== 'HEAD'
819
- ? await request.arrayBuffer()
820
- : null;
780
+ function supervisorOf(env: object | undefined, props: NimbusDoStubProps): { dispatch: HostOpDispatch; release(): void } {
781
+ const supervisorDoId = String(props.supervisorDoId || '');
782
+ if (!supervisorDoId) throw new Error('supervisorDoId missing');
783
+ let stub: DurableObjectStub | null = null;
784
+ try {
785
+ const ns = hostNamespaceBinding(env ?? {}, 'NimbusDOStub', props.route);
786
+ stub = ns.get(ns.idFromString(supervisorDoId));
787
+ const held = stub;
788
+ return { dispatch: hostOpDispatch(stub, 'NimbusDOStub', props.route), release: () => disposeRpcResource(held) };
789
+ } catch (e) {
790
+ disposeRpcResource(stub);
791
+ throw e;
792
+ }
793
+ }
794
+
795
+ /** What the session's innerDoFetch answers: the object's response, as fields. */
796
+ export interface InnerDoFetchAnswer {
797
+ status: number;
798
+ statusText: string;
799
+ headers: [string, string][];
800
+ body: ArrayBuffer | null;
801
+ }
802
+
803
+ function isInnerDoFetchAnswer(value: unknown): value is InnerDoFetchAnswer {
804
+ return value !== null && typeof value === 'object'
805
+ && 'status' in value && typeof value.status === 'number'
806
+ && 'statusText' in value && typeof value.statusText === 'string'
807
+ && 'headers' in value && Array.isArray(value.headers)
808
+ && 'body' in value && (value.body === null || value.body instanceof ArrayBuffer);
809
+ }
810
+
811
+ /**
812
+ * The inner object's fetch: the request forwarded whole (method, body,
813
+ * headers) as fields the session reconstructs, and its response rebuilt here
814
+ * from the fields the session answers. A binding that cannot reach the
815
+ * session answers 500.
816
+ */
817
+ async function innerDoFetch(env: object | undefined, props: NimbusDoStubProps, request: Request): Promise<Response> {
818
+ let supervisor: ReturnType<typeof supervisorOf>;
819
+ try {
820
+ supervisor = supervisorOf(env, props);
821
+ } catch (e) {
822
+ return new Response(`Nimbus: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
823
+ }
824
+ try {
825
+ const body = request.method !== 'GET' && request.method !== 'HEAD' ? await request.arrayBuffer() : null;
821
826
  const headerList: [string, string][] = [];
822
827
  request.headers.forEach((v, k) => { headerList.push([k, v]); });
823
- try {
824
- return await useRpcResource(
825
- dispatch({
826
- op: 'innerDoFetch',
827
- args: [{
828
- bindingName,
829
- id,
830
- method: request.method,
831
- url: request.url,
832
- headers: headerList,
833
- body,
834
- }],
835
- }),
836
- (res) => {
837
- if (!(res instanceof Response)) {
838
- return new Response('Nimbus: innerDoFetch returned an invalid result', { status: 502 });
839
- }
840
- return new Response(res.body, {
841
- status: res.status,
842
- statusText: res.statusText,
843
- headers: res.headers,
844
- });
845
- },
846
- );
847
- } finally {
848
- disposeRpcResource(stub);
849
- }
828
+ return await useRpcResource(
829
+ supervisor.dispatch({
830
+ op: 'innerDoFetch',
831
+ args: [{ bindingName: String(props.bindingName || ''), id: String(props.id || ''), method: request.method, url: request.url, headers: headerList, body }],
832
+ }),
833
+ (res) => {
834
+ if (!isInnerDoFetchAnswer(res)) {
835
+ return new Response('Nimbus: innerDoFetch returned an invalid result', { status: 502 });
836
+ }
837
+ return new Response(res.body, { status: res.status, statusText: res.statusText, headers: res.headers });
838
+ },
839
+ );
840
+ } finally {
841
+ supervisor.release();
842
+ }
843
+ }
844
+
845
+ /**
846
+ * A member of the inner object, called with `args` or read (`args` null): the
847
+ * session reaches it on the object's facet and answers what it answered (a
848
+ * stub, function or stream travels as one) or rejects with what it threw,
849
+ * type and message kept.
850
+ */
851
+ async function innerDoMember(env: object | undefined, props: NimbusDoStubProps, path: string[], args: unknown[] | null): Promise<unknown> {
852
+ const supervisor = supervisorOf(env, props);
853
+ try {
854
+ return await supervisor.dispatch({
855
+ op: 'innerDoCall',
856
+ args: [{ bindingName: String(props.bindingName || ''), id: String(props.id || ''), path, args }],
857
+ });
858
+ } finally {
859
+ supervisor.release();
860
+ }
861
+ }
862
+
863
+ /**
864
+ * A Durable-Object-namespace-stub for a specific id, for a caller of the
865
+ * binding's own `get`: its fetch. The important invariant: EVERY call
866
+ * resolves the inner DO class via getInnerDoClass() (./inner-do-registry.js)
867
+ * and spins up / attaches to a facet via the supervisor's ctx.facets in the
868
+ * SAME outer request context — never reusing stubs across requests.
869
+ */
870
+ export class NimbusDOStub extends WorkerEntrypoint<object, NimbusDoStubProps> {
871
+ async fetch(request: Request): Promise<Response> {
872
+ return innerDoFetch(this.env, this.ctx.props || {}, request);
850
873
  }
851
874
  }