@nimbus-sh/fabric 0.1.0 → 0.3.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 (104) hide show
  1. package/README.md +208 -293
  2. package/dist/bindings.js +5 -5
  3. package/dist/budgets.d.ts +132 -0
  4. package/dist/budgets.d.ts.map +1 -0
  5. package/dist/budgets.js +248 -0
  6. package/dist/composition.d.ts +3 -0
  7. package/dist/composition.d.ts.map +1 -0
  8. package/dist/composition.js +2 -0
  9. package/dist/connections.d.ts +81 -0
  10. package/dist/connections.d.ts.map +1 -0
  11. package/dist/connections.js +114 -0
  12. package/dist/derived.d.ts +65 -0
  13. package/dist/derived.d.ts.map +1 -0
  14. package/dist/derived.js +95 -0
  15. package/dist/do-calls.d.ts +94 -0
  16. package/dist/do-calls.d.ts.map +1 -0
  17. package/dist/do-calls.js +111 -0
  18. package/dist/facet-pool.d.ts +90 -0
  19. package/dist/facet-pool.d.ts.map +1 -0
  20. package/dist/facet-pool.js +113 -0
  21. package/dist/{fanout-pool.d.ts → fanout.d.ts} +20 -20
  22. package/dist/fanout.d.ts.map +1 -0
  23. package/dist/{fanout-pool.js → fanout.js} +20 -20
  24. package/dist/{launch-journal.d.ts → fenced-work.d.ts} +58 -17
  25. package/dist/fenced-work.d.ts.map +1 -0
  26. package/dist/fenced-work.js +241 -0
  27. package/dist/generation.d.ts +69 -0
  28. package/dist/generation.d.ts.map +1 -0
  29. package/dist/generation.js +118 -0
  30. package/dist/{facet-image-store.d.ts → image-store.d.ts} +8 -8
  31. package/dist/image-store.d.ts.map +1 -0
  32. package/dist/{facet-image-store.js → image-store.js} +4 -4
  33. package/dist/index.d.ts +16 -8
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +16 -8
  36. package/dist/{loader-pool.d.ts → isolate-pool.d.ts} +19 -19
  37. package/dist/isolate-pool.d.ts.map +1 -0
  38. package/dist/{loader-pool.js → isolate-pool.js} +20 -20
  39. package/dist/journal.d.ts +111 -0
  40. package/dist/journal.d.ts.map +1 -0
  41. package/dist/journal.js +177 -0
  42. package/dist/outbox.d.ts +249 -0
  43. package/dist/outbox.d.ts.map +1 -0
  44. package/dist/outbox.js +355 -0
  45. package/dist/process-fabric.d.ts +33 -15
  46. package/dist/process-fabric.d.ts.map +1 -1
  47. package/dist/process-fabric.js +25 -15
  48. package/dist/process-host.d.ts +1 -1
  49. package/dist/process-host.d.ts.map +1 -1
  50. package/dist/process-host.js +19 -11
  51. package/dist/sealed.d.ts +78 -0
  52. package/dist/sealed.d.ts.map +1 -0
  53. package/dist/sealed.js +145 -0
  54. package/dist/timers.d.ts +138 -0
  55. package/dist/timers.d.ts.map +1 -0
  56. package/dist/timers.js +231 -0
  57. package/dist/{launch-pacer.d.ts → turn-budget.d.ts} +24 -21
  58. package/dist/turn-budget.d.ts.map +1 -0
  59. package/dist/{launch-pacer.js → turn-budget.js} +24 -12
  60. package/dist/workerd-facet-host.d.ts +67 -70
  61. package/dist/workerd-facet-host.d.ts.map +1 -1
  62. package/dist/workerd-facet-host.js +129 -181
  63. package/examples/agent-core-adapter.ts +191 -0
  64. package/package.json +4 -2
  65. package/src/bindings.ts +6 -6
  66. package/src/budgets.ts +308 -0
  67. package/src/composition.ts +16 -0
  68. package/src/connections.ts +140 -0
  69. package/src/derived.ts +135 -0
  70. package/src/do-calls.ts +156 -0
  71. package/src/facet-pool.ts +157 -0
  72. package/src/{fanout-pool.ts → fanout.ts} +35 -35
  73. package/src/{launch-journal.ts → fenced-work.ts} +129 -42
  74. package/src/generation.ts +144 -0
  75. package/src/{facet-image-store.ts → image-store.ts} +9 -9
  76. package/src/index.ts +16 -8
  77. package/src/{loader-pool.ts → isolate-pool.ts} +34 -34
  78. package/src/journal.ts +242 -0
  79. package/src/node-async-hooks.d.ts +14 -0
  80. package/src/outbox.ts +520 -0
  81. package/src/process-fabric.ts +43 -34
  82. package/src/process-host.ts +22 -20
  83. package/src/sealed.ts +150 -0
  84. package/src/timers.ts +294 -0
  85. package/src/{launch-pacer.ts → turn-budget.ts} +34 -27
  86. package/src/workerd-facet-host.ts +159 -208
  87. package/dist/alarms.d.ts +0 -134
  88. package/dist/alarms.d.ts.map +0 -1
  89. package/dist/alarms.js +0 -214
  90. package/dist/ctx-exports.d.ts +0 -47
  91. package/dist/ctx-exports.d.ts.map +0 -1
  92. package/dist/ctx-exports.js +0 -54
  93. package/dist/facet-image-store.d.ts.map +0 -1
  94. package/dist/fanout-pool.d.ts.map +0 -1
  95. package/dist/launch-journal.d.ts.map +0 -1
  96. package/dist/launch-journal.js +0 -154
  97. package/dist/launch-pacer.d.ts.map +0 -1
  98. package/dist/loader-ledger.d.ts +0 -57
  99. package/dist/loader-ledger.d.ts.map +0 -1
  100. package/dist/loader-ledger.js +0 -91
  101. package/dist/loader-pool.d.ts.map +0 -1
  102. package/src/alarms.ts +0 -275
  103. package/src/ctx-exports.ts +0 -77
  104. package/src/loader-ledger.ts +0 -112
@@ -0,0 +1,156 @@
1
+ /**
2
+ * do-calls.ts — the two verbs for calling another Durable Object, split by
3
+ * the one property that decides whether a retry is safe.
4
+ *
5
+ * Both consumers asked for this. Proteus hand-wrote the retry
6
+ * (`cf-backend/src/lib/do-rpc.ts`) with the rule its header states:
7
+ * "An operation that appends, sends, charges or mints is never wrapped: a
8
+ * dropped call there may already have run, so a retry is a correctness bug
9
+ * wearing resilience as a costume." agent-core has no retry machinery at all
10
+ * and its backlog calls the gap "the most production-proven gap in the
11
+ * corpus". Here the rule is a type: `idempotent` retries, `mutating` cannot.
12
+ *
13
+ * What the platform contract requires, and this keeps:
14
+ * - a FRESH stub per attempt. Cloudflare documents that many exceptions
15
+ * leave a stub permanently broken, so both verbs take a stub RESOLVER,
16
+ * not a stub — which is also what lets placement pins and auth wrappers
17
+ * compose (agent-core's PlacementResolver pins an Actor to one
18
+ * jurisdiction for life; the resolver seam is where that lives).
19
+ * - `overloaded` is never retried, by either verb: retrying an overloaded
20
+ * object is what overloaded it.
21
+ * - attempts and backoff are the consumer-proven bounds: 3 attempts total,
22
+ * full-jitter delays in [0, 2**attempt * 60ms).
23
+ *
24
+ * The resolver MINTS a stub per call and the verb disposes each one it
25
+ * minted — that ownership is what makes the fresh-stub retry real.
26
+ *
27
+ * The stub method call itself happens inside the CALLER's closure
28
+ * (`(stub) => stub.method(args)`): nothing here proxies property resolution
29
+ * or dispatches by method name. Do NOT use these verbs around a dynamically
30
+ * loaded worker's entrypoint stub — those calls must stay direct property
31
+ * calls bracketed by `beginLoaderFetch` (budgets.ts records the 7/7 staging
32
+ * poisoning that rule comes from). These verbs are for Durable Object
33
+ * namespace stubs, where the thunk shape is production-proven in Proteus.
34
+ */
35
+
36
+ import { classifyDoCall, isRetryableDoCall, type DoCallClass } from '@nimbus-sh/platform/oom-classify.js';
37
+ import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
38
+
39
+ /** Total attempts. Two retries is what a dropped connection or a deploy
40
+ * bounce needs; beyond that the object is not coming back inside this
41
+ * request (the consumer's measured bound). */
42
+ const MAX_ATTEMPTS = 3;
43
+ /** Full-jitter base, in the shape the Agents SDK itself uses. */
44
+ const BASE_DELAY_MS = 60;
45
+
46
+ export interface DoCallRetryPolicy {
47
+ maxAttempts?: number;
48
+ baseDelayMs?: number;
49
+ /**
50
+ * Called once per retry, before its backoff delay, with the failure the
51
+ * retry is answering. The consumer's logging seam: Proteus's hand-rolled
52
+ * predecessor logged every retry so a flaky object is visible in Workers
53
+ * Logs rather than silently absorbed, and `operation` names it there.
54
+ */
55
+ onRetry?(info: DoCallRetryInfo): void;
56
+ }
57
+
58
+ /** What one retry is answering: which call, which platform class, which
59
+ * attempt just failed out of how many. */
60
+ export interface DoCallRetryInfo {
61
+ operation: string;
62
+ classification: DoCallClass;
63
+ /** The 1-based attempt that failed; the retry about to run is attempt+1. */
64
+ attempt: number;
65
+ maxAttempts: number;
66
+ error: unknown;
67
+ }
68
+
69
+ /**
70
+ * Mints one stub per call. `idempotent` calls it once per attempt.
71
+ *
72
+ * MINT means mint. Both verbs dispose the stub they were handed when the
73
+ * call settles — on success as much as on failure (`disposeRpcResource`).
74
+ * A resolver that returns a shared, long-lived stub hands its other users
75
+ * a disposed stub, and only in production: a test double is a plain object
76
+ * with nothing to dispose, so the test stays green while the deployed
77
+ * Worker breaks on the second call.
78
+ */
79
+ export type DoStubResolver<S> = () => S | Promise<S>;
80
+
81
+ /**
82
+ * A failed `mutating` call, typed so the caller can act on WHAT failed:
83
+ * `classification` names the platform condition, and a transient class on a
84
+ * mutating call means the call may already have run — the indeterminacy the
85
+ * consumer's rule exists to surface rather than paper over.
86
+ */
87
+ export class DoCallError extends Error {
88
+ constructor(
89
+ readonly operation: string,
90
+ readonly verb: 'idempotent' | 'mutating',
91
+ readonly classification: DoCallClass,
92
+ cause: unknown,
93
+ ) {
94
+ const text = cause instanceof Error ? cause.message : String(cause);
95
+ const indeterminate = isRetryableDoCall(classification)
96
+ ? ' — a dropped mutating call may already have run, so it is not retried'
97
+ : '';
98
+ super(`${verb} call '${operation}' failed [${classification}]: ${text}${indeterminate}`, { cause });
99
+ this.name = 'DoCallError';
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Call another Durable Object with an operation that is safe to repeat: a
105
+ * read, or a converge-to-a-value write. Transient failures retry on a fresh
106
+ * stub with full-jitter backoff; overloaded and permanent failures surface
107
+ * unchanged, as does the last error at exhaustion.
108
+ */
109
+ export async function idempotent<S, T>(
110
+ operation: string,
111
+ stub: DoStubResolver<S>,
112
+ call: (stub: S) => Promise<T>,
113
+ policy: DoCallRetryPolicy = {},
114
+ ): Promise<T> {
115
+ const maxAttempts = policy.maxAttempts ?? MAX_ATTEMPTS;
116
+ const baseDelayMs = policy.baseDelayMs ?? BASE_DELAY_MS;
117
+ for (let attempt = 1; ; attempt++) {
118
+ const minted = await stub();
119
+ try {
120
+ const result = await call(minted);
121
+ disposeRpcResource(minted);
122
+ return result;
123
+ } catch (error) {
124
+ // A stub that threw may be permanently broken; it is never reused.
125
+ disposeRpcResource(minted);
126
+ const classification = classifyDoCall(error);
127
+ if (!isRetryableDoCall(classification) || attempt >= maxAttempts) throw error;
128
+ policy.onRetry?.({ operation, classification, attempt, maxAttempts, error });
129
+ await new Promise<void>((resolve) => {
130
+ setTimeout(resolve, Math.floor(Math.random() * 2 ** attempt * baseDelayMs));
131
+ });
132
+ }
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Call another Durable Object with an operation that appends, sends, charges
138
+ * or mints. NEVER retried — a dropped call may already have run. Failure
139
+ * surfaces as a {@link DoCallError} carrying the classification, so the
140
+ * caller can tell a refusal from an indeterminate drop.
141
+ */
142
+ export async function mutating<S, T>(
143
+ operation: string,
144
+ stub: DoStubResolver<S>,
145
+ call: (stub: S) => Promise<T>,
146
+ ): Promise<T> {
147
+ const minted = await stub();
148
+ try {
149
+ const result = await call(minted);
150
+ disposeRpcResource(minted);
151
+ return result;
152
+ } catch (error) {
153
+ disposeRpcResource(minted);
154
+ throw new DoCallError(operation, 'mutating', classifyDoCall(error), error);
155
+ }
156
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * facet-pool.ts — leased facets, so reclaiming storage is the default and
3
+ * leaking it takes intent.
4
+ *
5
+ * Proteus's facet-spawn.ts (313 lines) exists because the platform's two
6
+ * teardown verbs are indistinguishable to a caller and only one gives
7
+ * storage back: `abort` is mid-flight eviction with storage KEPT, `delete`
8
+ * is terminal with storage WIPED. Its docstring records the cost of
9
+ * confusing them: "the leak this module previously had, in which every head
10
+ * and every MCTS branch abandoned a permanent database inside the
11
+ * orchestrator DO". The lease makes that leak unreachable: disposal retires
12
+ * the facet (evict, then wipe), and keeping storage is the explicit opt-in
13
+ * (`detach()`, today's abort).
14
+ *
15
+ * The constraints a caller must not be surprised by, from Proteus's platform
16
+ * catalog (all proven by probe or by source):
17
+ * - a facet cannot set alarms (`do.facet.no_alarms`) — a head cannot
18
+ * schedule its own resumption; everything time-driven routes through the
19
+ * root's single alarm (`timers`).
20
+ * - a facet stub is coordinator-local (`do.facet.stub_local`) — it cannot
21
+ * be transferred, stored, or re-invoked indirectly.
22
+ * - facet storage is charged to the ROOT's shared budget, and a clone that
23
+ * crosses it is an uncatchable reset, not an error (`do.storage.bytes`).
24
+ * - a parent and its facets are evicted JOINTLY after minutes idle, so
25
+ * in-memory facet state is never safe to assume between two RPCs.
26
+ * - 65,536 facet ids per DO lifetime (`do.facet.count`), append-only and
27
+ * never reclaimed — the binding constraint for the leak, reached an
28
+ * order of magnitude before the byte quota. The pool counts first-use
29
+ * names in the durable ledger (budgets.ts) and refuses a NEW name at the
30
+ * wall by name, instead of letting the platform fail opaquely. Refusal
31
+ * at the wall is exact, not a threshold: the ledger never overcounts.
32
+ *
33
+ * A failed reclaim stays loud (facet-spawn's `runOnceAndReclaim`): storage
34
+ * that was not given back is a permanent charge against the root's quota,
35
+ * and swallowing that is how the original leak stayed invisible.
36
+ *
37
+ * The pool drives the RAW `ctx.facets` container and assumes it is the only
38
+ * thing naming facets on this actor. The Agents SDK's sub-agent layer makes
39
+ * the same assumption from the other side — it owns facet naming and runs
40
+ * its own cleanup — so the two are mutually exclusive on one actor:
41
+ * whichever acts second aborts or retires facets the other still tracks,
42
+ * and the facet-id ledger here counts only the names this pool minted.
43
+ */
44
+
45
+ import {
46
+ FACET_ID_LIFETIME_BUDGET,
47
+ facetNameCount,
48
+ recordFacetNameMinted,
49
+ withFacetBudgetNamed,
50
+ } from './budgets.js';
51
+
52
+ /** `ctx.facets`, as the pool drives it — same surface the facet host uses. */
53
+ export interface FacetPoolContainer {
54
+ get(name: string, start: () => Promise<{ class: unknown }>): unknown;
55
+ abort(name: string, reason?: unknown): void;
56
+ delete(name: string): void;
57
+ }
58
+
59
+ /** The hosting actor's context: its facet container, and the storage the
60
+ * facet-id ledger persists through. */
61
+ export interface FacetPoolContext {
62
+ facets?: FacetPoolContainer;
63
+ storage: {
64
+ get(key: string): Promise<unknown> | unknown;
65
+ put(key: string, value: unknown): Promise<void>;
66
+ };
67
+ }
68
+
69
+ /**
70
+ * One leased facet. Dispose (or `retire()`) evicts the instance and WIPES
71
+ * its storage; `detach()` first to keep the storage — after it, disposal
72
+ * only evicts. The stub is coordinator-local: do not store it past the turn
73
+ * or hand it to anything else.
74
+ */
75
+ export interface FacetLease<S> {
76
+ readonly name: string;
77
+ readonly stub: S;
78
+ /** Keep the facet's storage: disposal becomes eviction only. */
79
+ detach(): void;
80
+ /** Idempotent. Throws, loudly, when the platform refuses the wipe. */
81
+ retire(): Promise<void>;
82
+ [Symbol.asyncDispose](): Promise<void>;
83
+ }
84
+
85
+ /** Names this ctx's pool has already charged to the lifetime ledger. */
86
+ const chargedNames = new WeakMap<object, Set<string>>();
87
+
88
+ /** The facet pool of one hosting actor. Cheap accessor, like `timers()`. */
89
+ export function facetPool(ctx: FacetPoolContext): FacetPool {
90
+ return new FacetPool(ctx);
91
+ }
92
+
93
+ export class FacetPool {
94
+ constructor(private readonly ctx: FacetPoolContext) {}
95
+
96
+ /**
97
+ * Open (or re-enter) the named facet under a lease. A first-use name
98
+ * consumes one of the object's 65,536 lifetime facet ids and is refused at
99
+ * the wall; a reused name costs nothing, in this incarnation or any other.
100
+ */
101
+ async acquire<S = unknown>(
102
+ name: string,
103
+ start: () => Promise<{ class: unknown }>,
104
+ ): Promise<FacetLease<S>> {
105
+ const facets = this.ctx.facets;
106
+ if (!facets || typeof facets.get !== 'function') {
107
+ throw new Error('fabric: ctx.facets is unavailable in this Durable Object; facets cannot be leased');
108
+ }
109
+ let charged = chargedNames.get(this.ctx);
110
+ if (!charged) {
111
+ charged = new Set();
112
+ chargedNames.set(this.ctx, charged);
113
+ }
114
+ if (!charged.has(name)) {
115
+ const consumed = facetNameCount(this.ctx);
116
+ if (consumed >= FACET_ID_LIFETIME_BUDGET) {
117
+ throw withFacetBudgetNamed(
118
+ consumed,
119
+ new Error(`facet '${name}' refused before creation: no lifetime ids remain`),
120
+ );
121
+ }
122
+ recordFacetNameMinted(this.ctx, consumed + 1);
123
+ charged.add(name);
124
+ }
125
+ const stub = facets.get(name, start) as S;
126
+ let settled = false;
127
+ let keepStorage = false;
128
+ const retire = async (): Promise<void> => {
129
+ if (settled) return;
130
+ settled = true;
131
+ // Evict first so the wipe never lands under a live writer; a facet
132
+ // already gone makes the abort a no-op.
133
+ try { facets.abort(name, new Error('fabric: facet lease retired')); } catch { /* already gone */ }
134
+ if (keepStorage) return;
135
+ try {
136
+ facets.delete(name);
137
+ } catch (e) {
138
+ throw new Error(
139
+ `fabric: facet '${name}' was evicted but its storage was not reclaimed — `
140
+ + `it is leaked into the root Durable Object's shared quota: ${errorText(e)}`,
141
+ { cause: e },
142
+ );
143
+ }
144
+ };
145
+ return {
146
+ name,
147
+ stub,
148
+ detach(): void { keepStorage = true; },
149
+ retire,
150
+ [Symbol.asyncDispose]: retire,
151
+ };
152
+ }
153
+ }
154
+
155
+ function errorText(error: unknown): string {
156
+ return error instanceof Error ? error.message : String(error);
157
+ }
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * A single Durable Object method can drive at most four concurrent
6
6
  * Worker Loader fetches before extra dispatches serialize or fail. Small
7
- * batches therefore run in the coordinator DO through LoaderPool.
7
+ * batches therefore run in the coordinator DO through IsolatePool.
8
8
  * Wider batches are sharded across sibling NimbusSession DOs, each of
9
9
  * which owns its own four-loader budget.
10
10
  *
@@ -16,9 +16,9 @@
16
16
 
17
17
  import { serializeFunction } from './vendor/serialize.js';
18
18
  import { BindingError } from './vendor/errors.js';
19
- import { LoaderPool, type FacetTaskFn } from './loader-pool.js';
20
- import { disposeRpcResource } from '@nimbus-sh/core/_shared/rpc-dispose.js';
21
- import { describeError, isDoOverloaded, isTransientDoReset } from '@nimbus-sh/core/observability/oom-classify.js';
19
+ import { IsolatePool, type FacetTaskFn } from './isolate-pool.js';
20
+ import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
21
+ import { describeError, isDoOverloaded, isTransientDoReset } from '@nimbus-sh/platform/oom-classify.js';
22
22
  import type { WorkerLoader } from './vendor/types.js';
23
23
 
24
24
  /**
@@ -31,7 +31,7 @@ interface PeerSessionNamespace {
31
31
  }
32
32
 
33
33
  /** The bindings a fan-out needs off the coordinator DO's env. */
34
- export interface FanoutPoolEnv {
34
+ export interface FanoutEnv {
35
35
  LOADER?: WorkerLoader;
36
36
  NIMBUS_SESSION?: PeerSessionNamespace;
37
37
  }
@@ -104,45 +104,45 @@ export interface FanoutTask<A> {
104
104
  args: A;
105
105
  }
106
106
 
107
- /** Options handed to FanoutPool's constructor. */
108
- export interface FanoutPoolOptions {
107
+ /** Options handed to Fanout's constructor. */
108
+ export interface FanoutOptions {
109
109
  /**
110
110
  * Tag prepended to peer-DO ids and in-DO loader ids for debugging
111
111
  * (e.g. "npm-install-batch"). Affects neither isolate identity (in-DO
112
- * path uses the existing LoaderPool's tag-fold) nor peer-DO
112
+ * path uses the existing IsolatePool's tag-fold) nor peer-DO
113
113
  * deterministic placement (peer ids fold tag + key).
114
114
  */
115
115
  tag: string;
116
116
  /**
117
117
  * Per-task timeout in ms. Default 60_000. Forwarded to the in-DO
118
- * LoaderPool's submit calls and to the peer-DO RPC's own
119
- * LoaderPool.
118
+ * IsolatePool's submit calls and to the peer-DO RPC's own
119
+ * IsolatePool.
120
120
  */
121
121
  timeoutMs?: number;
122
122
  /**
123
123
  * Preamble bundled into every facet (in-DO and inside each peer
124
- * DO). Same semantics as LoaderPool's preamble option.
124
+ * DO). Same semantics as IsolatePool's preamble option.
125
125
  */
126
126
  preamble?: string;
127
127
  /**
128
128
  * Wasm modules forwarded to every facet. Same semantics as
129
- * LoaderPool's wasmModules option.
129
+ * IsolatePool's wasmModules option.
130
130
  */
131
131
  wasmModules?: Record<string, ArrayBuffer>;
132
132
  /**
133
133
  * Extra bindings forwarded to every facet. Same semantics as
134
- * LoaderPool's extraBindings option.
134
+ * IsolatePool's extraBindings option.
135
135
  */
136
136
  extraBindings?: Record<string, unknown>;
137
137
  /**
138
138
  * If set, skip the supervisor-RPC binding injection (mirrors
139
- * LoaderPool's omitSupervisor flag).
139
+ * IsolatePool's omitSupervisor flag).
140
140
  */
141
141
  omitSupervisor?: boolean;
142
142
  /**
143
143
  * Invoking process pid, baked into each facet's SUPERVISOR binding so
144
144
  * filesystem RPCs (writeBatchStream) are authorized under the caller's
145
- * credential (mirrors LoaderPool's supervisorPid). Threaded to both
145
+ * credential (mirrors IsolatePool's supervisorPid). Threaded to both
146
146
  * the in-DO loader pool and, via `_rpcFanoutExecute`, the peer-DO pools.
147
147
  * npm install passes the shell command's `ctx.pid`; resolve leaves it 0.
148
148
  */
@@ -186,10 +186,10 @@ function isFanoutPeerStub(value: unknown): value is FanoutPeerStub {
186
186
 
187
187
  function fanoutPeerStub(value: unknown): FanoutPeerStub {
188
188
  if ((typeof value !== 'object' && typeof value !== 'function') || value === null) {
189
- throw new BindingError('FanoutPool: NIMBUS_SESSION.get() did not return a peer stub.');
189
+ throw new BindingError('Fanout: NIMBUS_SESSION.get() did not return a peer stub.');
190
190
  }
191
191
  if (!isFanoutPeerStub(value)) {
192
- throw new BindingError('FanoutPool: peer stub does not expose _rpcFanoutExecute().');
192
+ throw new BindingError('Fanout: peer stub does not expose _rpcFanoutExecute().');
193
193
  }
194
194
  return value;
195
195
  }
@@ -203,23 +203,23 @@ function fanoutPeerStub(value: unknown): FanoutPeerStub {
203
203
  * exists primarily as a clean API surface; per-call dispatch state
204
204
  * lives only inside submitMany's promise.
205
205
  */
206
- export class FanoutPool {
207
- private readonly env: FanoutPoolEnv;
206
+ export class Fanout {
207
+ private readonly env: FanoutEnv;
208
208
  private readonly ctx: DurableObjectState;
209
- private readonly opts: FanoutPoolOptions;
209
+ private readonly opts: FanoutOptions;
210
210
  private readonly coordDoId: string;
211
211
  private readonly coordDoIdShort: string;
212
212
 
213
- constructor(rawEnv: unknown, ctx: DurableObjectState, opts: FanoutPoolOptions) {
213
+ constructor(rawEnv: unknown, ctx: DurableObjectState, opts: FanoutOptions) {
214
214
  // A host hands its whole env over; the bindings are claimed here and the
215
215
  // LOADER claim is checked immediately. Hard-fail on a missing LOADER —
216
- // LoaderPool also enforces this, but checking up front points the
217
- // diagnostic at the fanout-pool construction site rather than the
218
- // deferred loader-pool one.
219
- const env = (rawEnv as FanoutPoolEnv | null | undefined) ?? {};
216
+ // IsolatePool also enforces this, but checking up front points the
217
+ // diagnostic at the fanout construction site rather than the
218
+ // deferred isolate-pool one.
219
+ const env = (rawEnv as FanoutEnv | null | undefined) ?? {};
220
220
  if (!env.LOADER || typeof env.LOADER.get !== 'function') {
221
221
  throw new BindingError(
222
- 'FanoutPool: env.LOADER binding missing or invalid. ' +
222
+ 'Fanout: env.LOADER binding missing or invalid. ' +
223
223
  'Add a [[worker_loaders]] entry to wrangler.jsonc.',
224
224
  );
225
225
  }
@@ -235,21 +235,21 @@ export class FanoutPool {
235
235
  * results in input order.
236
236
  *
237
237
  * Routing:
238
- * tasks.length < 5 -> coordinator-local LoaderPool
238
+ * tasks.length < 5 -> coordinator-local IsolatePool
239
239
  * tasks.length >= 5 -> sibling NimbusSession DOs
240
240
  *
241
241
  * Backpressure: if `tasks.length > MAX_PEER_FANOUT (32)`, tasks
242
242
  * are sharded modulo `MAX_PEER_FANOUT` and each shard's bucket
243
243
  * runs serially inside its assigned peer DO via the in-peer
244
- * LoaderPool's concurrency (capped at 4 there too). A
244
+ * IsolatePool's concurrency (capped at 4 there too). A
245
245
  * single submitMany call returns when ALL tasks complete (or any
246
246
  * throws).
247
247
  *
248
248
  * `fn` is the user function executed per task. It runs INSIDE a
249
249
  * Worker Loader isolate (in the in-DO path) or inside a peer DO's
250
250
  * Worker Loader isolate (in the peer-DO path); same trust posture
251
- * as LoaderPool.submit. The function is serialized via
252
- * the vendored serializeFunction (same as LoaderPool#prepare).
251
+ * as IsolatePool.submit. The function is serialized via
252
+ * the vendored serializeFunction (same as IsolatePool#prepare).
253
253
  */
254
254
  async submitMany<A, R>(
255
255
  tasks: FanoutTask<A>[],
@@ -287,12 +287,12 @@ export class FanoutPool {
287
287
  tasks: FanoutTask<A>[],
288
288
  fn: FacetTaskFn<A, R>,
289
289
  ): Promise<R[]> {
290
- // Use the existing LoaderPool. Concurrency = task count
290
+ // Use the existing IsolatePool. Concurrency = task count
291
291
  // (capped at 4 by constructor — tasks.length is already < 5
292
292
  // here, so the cap won't bite). Each task = one pool.submit;
293
293
  // pool.map runs them with stable-slot reuse.
294
294
  const concurrency = Math.min(tasks.length, IN_DO_THRESHOLD - 1);
295
- const pool = new LoaderPool(this.env, this.ctx, {
295
+ const pool = new IsolatePool(this.env, this.ctx, {
296
296
  concurrency,
297
297
  timeoutMs: this.opts.timeoutMs,
298
298
  tag: this.opts.tag,
@@ -326,7 +326,7 @@ export class FanoutPool {
326
326
  const ns = this.env?.NIMBUS_SESSION;
327
327
  if (!ns || typeof ns.idFromName !== 'function' || typeof ns.get !== 'function') {
328
328
  throw new BindingError(
329
- 'FanoutPool: env.NIMBUS_SESSION binding missing or invalid. ' +
329
+ 'Fanout: env.NIMBUS_SESSION binding missing or invalid. ' +
330
330
  'The peer-DO topology requires it. ' +
331
331
  'Add the binding via durable_objects.bindings in wrangler.jsonc.',
332
332
  );
@@ -340,7 +340,7 @@ export class FanoutPool {
340
340
 
341
341
  // Cap peer count at MAX_PEER_FANOUT. Tasks beyond N=32 are
342
342
  // bucketed into existing shards — each shard's peer DO then
343
- // runs its bucket through its in-DO LoaderPool.map
343
+ // runs its bucket through its in-DO IsolatePool.map
344
344
  // (concurrency capped at 4 there).
345
345
  const peerCount = Math.min(tasks.length, this.opts.maxPeers ?? MAX_PEER_FANOUT);
346
346
  // Group tasks by deterministic shard. Same key → same shard, so
@@ -397,7 +397,7 @@ export class FanoutPool {
397
397
  extraBindings: this.opts.extraBindings,
398
398
  omitSupervisor: this.opts.omitSupervisor,
399
399
  // INSTALL-HONESTY: forward the COORDINATOR's full doId so
400
- // the peer's LoaderPool can mint a SUPERVISOR
400
+ // the peer's IsolatePool can mint a SUPERVISOR
401
401
  // binding that routes back HERE (the user's session DO),
402
402
  // not to the peer DO itself. Without this, peer DOs'
403
403
  // env.SUPERVISOR.writeBatch / writeBatchStream / stdout /