@nimbus-sh/fabric 0.4.0 → 0.6.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.
@@ -25,16 +25,6 @@
25
25
  import { WorkerEntrypoint } from 'cloudflare:workers';
26
26
  import { z } from 'zod/v4';
27
27
  import type { WorkerCode } from './vendor/types.js';
28
- /**
29
- * The supervisor DO namespace a shim resolves ONE stub from, by the id its
30
- * props carry. `Stub` is that DO's RPC surface as the calling shim uses it —
31
- * the supervisor class belongs to the embedder, so each shim names the methods
32
- * it calls rather than the class.
33
- */
34
- interface SupervisorNamespace<Stub> {
35
- idFromString(id: string): DurableObjectId;
36
- get(id: DurableObjectId): Stub;
37
- }
38
28
  /**
39
29
  * A dynamic worker's entrypoint, as hop 3 relays to it. `fetch` is the
40
30
  * entrypoint contract every loaded worker answers; `handleHttpRequest` is the
@@ -69,17 +59,6 @@ interface NimbusLoaderShimEnv {
69
59
  LOADER?: OuterWorkerLoader;
70
60
  NIMBUS_INNER_LOADER_DEPTH?: string;
71
61
  }
72
- /**
73
- * What the assets shim reads off the supervisor DO: the VFS bytes of one path,
74
- * or null when it holds no such file.
75
- */
76
- interface AssetsSupervisorStub {
77
- _rpcReadFileBytes(path: string): Promise<ArrayBuffer | Uint8Array | null>;
78
- }
79
- /** `env` for the assets shim: the supervisor its VFS reads round-trip through. */
80
- interface NimbusAssetsEnv {
81
- NIMBUS_SESSION?: SupervisorNamespace<AssetsSupervisorStub>;
82
- }
83
62
  /** Props the assets shim is minted with. */
84
63
  interface NimbusAssetsProps {
85
64
  /** Project root in VFS (e.g. "home/user/myapp"). */
@@ -110,7 +89,7 @@ interface NimbusAssetsProps {
110
89
  * For Phase 1, we use a simpler approach: the props carry a supervisor
111
90
  * DO id so we can round-trip through an RPC method that reads the file.
112
91
  */
113
- export declare class NimbusAssetsRPC extends WorkerEntrypoint<NimbusAssetsEnv, NimbusAssetsProps> {
92
+ export declare class NimbusAssetsRPC extends WorkerEntrypoint<object, NimbusAssetsProps> {
114
93
  /**
115
94
  * Fetch a static asset. Called by the inner Worker as
116
95
  * `env.ASSETS.fetch(request)`. The request URL's pathname is used to
@@ -269,29 +248,6 @@ export declare class NimbusDurableObjectNamespace extends WorkerEntrypoint<unkno
269
248
  /** Return a stub bound to the given id. */
270
249
  get(id: string): unknown;
271
250
  }
272
- /**
273
- * What the DO shim reads off the supervisor: one inner-DO request, answered
274
- * from the facet the supervisor resolves in its own request context.
275
- */
276
- interface InnerDoSupervisorStub {
277
- _rpcInnerDoFetch(request: {
278
- bindingName: string;
279
- id: string;
280
- method: string;
281
- url: string;
282
- headers: [string, string][];
283
- body: ArrayBuffer | null;
284
- }): Promise<{
285
- body: ArrayBuffer;
286
- status: number;
287
- statusText: string;
288
- headers: [string, string][];
289
- }>;
290
- }
291
- /** `env` for the DO shim: the supervisor that owns the facet. */
292
- interface NimbusInnerDoEnv {
293
- NIMBUS_SESSION?: SupervisorNamespace<InnerDoSupervisorStub>;
294
- }
295
251
  /** Props the DO stub carries: which binding, which supervisor, which id. */
296
252
  interface NimbusDoStubProps extends NimbusDoNamespaceProps {
297
253
  id?: string;
@@ -304,11 +260,11 @@ interface NimbusDoStubProps extends NimbusDoNamespaceProps {
304
260
  * spins up / attaches to a facet via the supervisor's ctx.facets in
305
261
  * the SAME outer request context — never reusing stubs across requests.
306
262
  */
307
- export declare class NimbusDOStub extends WorkerEntrypoint<NimbusInnerDoEnv, NimbusDoStubProps> {
263
+ export declare class NimbusDOStub extends WorkerEntrypoint<object, NimbusDoStubProps> {
308
264
  /**
309
- * Resolve the supervisor DO from env.NIMBUS_SESSION and route through
310
- * its _rpcInnerDoFetch RPC method, which runs ctx.facets.get(...) in
311
- * its own context and forwards the request.
265
+ * Resolve the supervisor DO through the composed host namespace and
266
+ * dispatch the innerDoFetch op through its one supervisorOp entrypoint —
267
+ * a host forwards envelopes, not private _rpc* methods.
312
268
  */
313
269
  fetch(request: Request): Promise<Response>;
314
270
  }
@@ -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 } from './composition.js';
29
29
  import { stagedBootAssembler } from './composition.js';
30
+ import { hostNamespaceBinding, hostOpDispatch } from './host-dispatch.js';
30
31
  import { assertModuleMapWithinCodeLimit } from './budgets.js';
31
32
  /**
32
33
  * `ctx.exports` — workerd's loopback bag, which the installed
@@ -88,12 +89,23 @@ export class NimbusAssetsRPC extends WorkerEntrypoint {
88
89
  let clean = url.pathname.replace(/^\/+/, '');
89
90
  const parts = clean.split('/').filter((p) => p && p !== '..' && p !== '.');
90
91
  clean = parts.join('/');
91
- // Resolve the supervisor DO stub so we can call its VFS read RPC.
92
- const ns = this.env.NIMBUS_SESSION;
93
- if (!ns || !doId) {
94
- return new Response('ASSETS binding not wired: missing NIMBUS_SESSION or doId', { status: 500 });
92
+ // Resolve the supervisor DO through the composed host namespace and
93
+ // dispatch readFileBytes through its supervisorOp entrypoint — a host
94
+ // forwards envelopes, not private _rpc* methods.
95
+ if (!doId) {
96
+ return new Response('ASSETS binding not wired: missing doId', { status: 500 });
97
+ }
98
+ let stub = null;
99
+ let dispatch;
100
+ try {
101
+ const ns = hostNamespaceBinding(this.env, 'NimbusAssetsRPC');
102
+ stub = ns.get(ns.idFromString(doId));
103
+ dispatch = hostOpDispatch(stub, 'NimbusAssetsRPC');
104
+ }
105
+ catch (e) {
106
+ disposeRpcResource(stub);
107
+ return new Response(`ASSETS binding not wired: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
95
108
  }
96
- const stub = ns.get(ns.idFromString(doId));
97
109
  // Candidate VFS paths, tried in order. The assetsDir is relative to
98
110
  // the project root in VFS. Trailing-slash and bare dir → index.html.
99
111
  const base = (vfsRoot ? vfsRoot + '/' : '') + (assetsDir ? assetsDir + '/' : '');
@@ -112,10 +124,15 @@ export class NimbusAssetsRPC extends WorkerEntrypoint {
112
124
  try {
113
125
  for (const candidate of candidates) {
114
126
  try {
115
- const response = await useRpcResource(stub._rpcReadFileBytes(candidate), (bytes) => {
116
- if (!bytes || bytes.byteLength === undefined)
127
+ const response = await useRpcResource(dispatch({ op: 'readFileBytes', args: [candidate] }), (bytes) => {
128
+ if (bytes === null)
117
129
  return null;
118
- return new Response(bytes, {
130
+ const body = bytes instanceof ArrayBuffer ? bytes
131
+ : bytes instanceof Uint8Array && bytes.buffer instanceof ArrayBuffer
132
+ ? new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength) : null;
133
+ if (!body)
134
+ return new Response('Nimbus: readFileBytes returned invalid bytes', { status: 502 });
135
+ return new Response(body, {
119
136
  status: 200,
120
137
  headers: {
121
138
  'Content-Type': mimeTypeForPath(candidate),
@@ -633,21 +650,28 @@ export class NimbusDurableObjectNamespace extends WorkerEntrypoint {
633
650
  */
634
651
  export class NimbusDOStub extends WorkerEntrypoint {
635
652
  /**
636
- * Resolve the supervisor DO from env.NIMBUS_SESSION and route through
637
- * its _rpcInnerDoFetch RPC method, which runs ctx.facets.get(...) in
638
- * its own context and forwards the request.
653
+ * Resolve the supervisor DO through the composed host namespace and
654
+ * dispatch the innerDoFetch op through its one supervisorOp entrypoint —
655
+ * a host forwards envelopes, not private _rpc* methods.
639
656
  */
640
657
  async fetch(request) {
641
658
  const props = this.ctx.props || {};
642
- const ns = this.env?.NIMBUS_SESSION;
643
- if (!ns)
644
- return new Response('Nimbus: env.NIMBUS_SESSION unavailable', { status: 500 });
645
659
  const supervisorDoId = String(props.supervisorDoId || '');
646
660
  if (!supervisorDoId)
647
661
  return new Response('Nimbus: supervisorDoId missing', { status: 500 });
648
662
  const bindingName = String(props.bindingName || '');
649
663
  const id = String(props.id || '');
650
- const stub = ns.get(ns.idFromString(supervisorDoId));
664
+ let stub = null;
665
+ let dispatch;
666
+ try {
667
+ const ns = hostNamespaceBinding(this.env ?? {}, 'NimbusDOStub');
668
+ stub = ns.get(ns.idFromString(supervisorDoId));
669
+ dispatch = hostOpDispatch(stub, 'NimbusDOStub');
670
+ }
671
+ catch (e) {
672
+ disposeRpcResource(stub);
673
+ return new Response(`Nimbus: ${e instanceof Error ? e.message : String(e)}`, { status: 500 });
674
+ }
651
675
  // Forward the full request (method, body, headers preserved) by
652
676
  // serializing what's needed and reconstructing on the other side.
653
677
  // The supervisor reconstitutes the Request from these fields and
@@ -658,18 +682,26 @@ export class NimbusDOStub extends WorkerEntrypoint {
658
682
  const headerList = [];
659
683
  request.headers.forEach((v, k) => { headerList.push([k, v]); });
660
684
  try {
661
- return await useRpcResource(stub._rpcInnerDoFetch({
662
- bindingName,
663
- id,
664
- method: request.method,
665
- url: request.url,
666
- headers: headerList,
667
- body,
668
- }), (res) => new Response(res.body, {
669
- status: res.status,
670
- statusText: res.statusText,
671
- headers: res.headers,
672
- }));
685
+ return await useRpcResource(dispatch({
686
+ op: 'innerDoFetch',
687
+ args: [{
688
+ bindingName,
689
+ id,
690
+ method: request.method,
691
+ url: request.url,
692
+ headers: headerList,
693
+ body,
694
+ }],
695
+ }), (res) => {
696
+ if (!(res instanceof Response)) {
697
+ return new Response('Nimbus: innerDoFetch returned an invalid result', { status: 502 });
698
+ }
699
+ return new Response(res.body, {
700
+ status: res.status,
701
+ statusText: res.statusText,
702
+ headers: res.headers,
703
+ });
704
+ });
673
705
  }
674
706
  finally {
675
707
  disposeRpcResource(stub);
package/dist/fanout.d.ts CHANGED
@@ -15,18 +15,12 @@
15
15
  */
16
16
  import { type FacetTaskFn } from './isolate-pool.js';
17
17
  import type { WorkerLoader } from './vendor/types.js';
18
- /**
19
- * The sibling-session namespace the peer-DO topology routes through. Ids are
20
- * derived from a name so a task key always lands on the same peer.
21
- */
22
- interface PeerSessionNamespace {
23
- idFromName(name: string): DurableObjectId;
24
- get(id: DurableObjectId): unknown;
25
- }
26
- /** The bindings a fan-out needs off the coordinator DO's env. */
18
+ /** The bindings a fan-out needs off the coordinator DO's env. The host
19
+ * namespace key is the composed one — whatever this host named its own
20
+ * binding (default NIMBUS_SESSION); peers dispatch supervisorOp envelopes. */
27
21
  export interface FanoutEnv {
28
22
  LOADER?: WorkerLoader;
29
- NIMBUS_SESSION?: PeerSessionNamespace;
23
+ NIMBUS_SESSION?: unknown;
30
24
  }
31
25
  /**
32
26
  * Threshold at which routing switches from coordinator-local loaders to
@@ -219,5 +213,4 @@ export declare class Fanout {
219
213
  * Tests use this to predict placement.
220
214
  */
221
215
  export declare function hashKeyToShard(key: string, peerCount: number): number;
222
- export {};
223
216
  //# sourceMappingURL=fanout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fanout.d.ts","sourceRoot":"","sources":["../src/fanout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAOtD;;+EAE+E;AAC/E,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC;;;GAGG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,qBAAqB,UAAmB,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,UAAqB,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,uCAAuC;AACvC,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,IAAI,EAAE,CAAC,CAAC;CACT;AAED,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC1C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAWD;;;;;;;;GAQG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa;IAoBzE;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,UAAU,CAAC,CAAC,EAAE,CAAC,EACnB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EACtB,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,EAAE,CAAC;IASf,mEAAmE;IACnE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO;IAK7D;;;;;;OAMG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;YAOvC,aAAa;YAoCb,eAAe;CA2J9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAUrE"}
package/dist/fanout.js CHANGED
@@ -18,6 +18,7 @@ import { BindingError } from './vendor/errors.js';
18
18
  import { IsolatePool } from './isolate-pool.js';
19
19
  import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
20
20
  import { describeError, isDoOverloaded, isTransientDoReset } from '@nimbus-sh/platform/oom-classify.js';
21
+ import { hostNamespaceBinding, hostOpDispatch, } from './host-dispatch.js';
21
22
  /**
22
23
  * Threshold at which routing switches from coordinator-local loaders to
23
24
  * sibling Durable Objects.
@@ -70,21 +71,9 @@ export const PEER_OVERLOAD_BACKOFF_MS = [1000, 3000, 6000];
70
71
  * tolerates rather than by the barrier count.
71
72
  */
72
73
  export const FANOUT_PHASE_SIZE = 4;
73
- function isFanoutPeerStub(value) {
74
- if ((typeof value !== 'object' && typeof value !== 'function') || value === null) {
75
- return false;
76
- }
77
- const execute = Reflect.get(value, '_rpcFanoutExecute');
78
- return typeof execute === 'function';
79
- }
80
- function fanoutPeerStub(value) {
81
- if ((typeof value !== 'object' && typeof value !== 'function') || value === null) {
82
- throw new BindingError('Fanout: NIMBUS_SESSION.get() did not return a peer stub.');
83
- }
84
- if (!isFanoutPeerStub(value)) {
85
- throw new BindingError('Fanout: peer stub does not expose _rpcFanoutExecute().');
86
- }
87
- return value;
74
+ // Task values retain the submitted function's type; validate the wire envelope.
75
+ function isPeerResult(value) {
76
+ return value !== null && typeof value === 'object' && 'results' in value && Array.isArray(value.results);
88
77
  }
89
78
  /**
90
79
  * Two-tier fan-out pool. Constructed by the supervisor DO; routes
@@ -201,12 +190,10 @@ export class Fanout {
201
190
  }
202
191
  // ── Private: peer-DO dispatch (peer-DO fanout) ────────────────────────────
203
192
  async _dispatchPeerDo(tasks, fn) {
204
- const ns = this.env?.NIMBUS_SESSION;
205
- if (!ns || typeof ns.idFromName !== 'function' || typeof ns.get !== 'function') {
206
- throw new BindingError('Fanout: env.NIMBUS_SESSION binding missing or invalid. ' +
207
- 'The peer-DO topology requires it. ' +
208
- 'Add the binding via durable_objects.bindings in wrangler.jsonc.');
209
- }
193
+ // The peer-DO topology routes through the composed host namespace —
194
+ // a workspace host names its own binding, and its stub forwards one
195
+ // supervisorOp entrypoint, not private _rpc* methods.
196
+ const ns = hostNamespaceBinding(this.env ?? {}, 'Fanout');
210
197
  // Serialize the user function ONCE here on the supervisor side.
211
198
  // Each peer DO receives the same fnSource string; warm peer
212
199
  // loader isolates (keyed on fnHash) reuse across calls with
@@ -252,34 +239,43 @@ export class Fanout {
252
239
  // stub points at a torn-down object, so a retry re-resolves the
253
240
  // sibling by its stable id.
254
241
  const peerStub = ns.get(id);
255
- const stub = fanoutPeerStub(peerStub);
256
242
  try {
243
+ const dispatch = hostOpDispatch(peerStub, `Fanout peer ${siblingName}`);
257
244
  // Each peer DO RPC call uses ONE LOADER worker on its side.
258
245
  // Supervisor → peer DO is a stub.fetch / RPC method call,
259
246
  // NOT an env.LOADER.get(); that's the cap-sidestep that
260
247
  // makes peer-DO fanout work.
261
- const rpcResp = await stub._rpcFanoutExecute(fnSource, peerArgs, {
262
- tag: this.opts.tag,
263
- timeoutMs: this.opts.timeoutMs,
264
- preamble: this.opts.preamble,
265
- wasmModules: this.opts.wasmModules,
266
- extraBindings: this.opts.extraBindings,
267
- omitSupervisor: this.opts.omitSupervisor,
268
- // INSTALL-HONESTY: forward the COORDINATOR's full doId so
269
- // the peer's IsolatePool can mint a SUPERVISOR
270
- // binding that routes back HERE (the user's session DO),
271
- // not to the peer DO itself. Without this, peer DOs'
272
- // env.SUPERVISOR.writeBatch / writeBatchStream / stdout /
273
- // ... write into the peer's own VFS — invisible to the
274
- // user. See INSTALL-HONESTY-retro.md.
275
- coordinatorDoId: this.coordDoId,
276
- // Credential source for peer-side writeBatchStream — the
277
- // invoking process pid, so package writes are authorized
278
- // as the user (not rejected as pid:0).
279
- supervisorPid: this.opts.supervisorPid,
248
+ const rpcResp = await dispatch({
249
+ op: 'fanoutExecute',
250
+ args: [
251
+ fnSource,
252
+ peerArgs,
253
+ {
254
+ tag: this.opts.tag,
255
+ timeoutMs: this.opts.timeoutMs,
256
+ preamble: this.opts.preamble,
257
+ wasmModules: this.opts.wasmModules,
258
+ extraBindings: this.opts.extraBindings,
259
+ omitSupervisor: this.opts.omitSupervisor,
260
+ // INSTALL-HONESTY: forward the COORDINATOR's full doId so
261
+ // the peer's IsolatePool can mint a SUPERVISOR
262
+ // binding that routes back HERE (the user's session DO),
263
+ // not to the peer DO itself. Without this, peer DOs'
264
+ // env.SUPERVISOR.writeBatch / writeBatchStream / stdout /
265
+ // ... write into the peer's own VFS — invisible to the
266
+ // user. See INSTALL-HONESTY-retro.md.
267
+ coordinatorDoId: this.coordDoId,
268
+ // Credential source for peer-side writeBatchStream — the
269
+ // invoking process pid, so package writes are authorized
270
+ // as the user (not rejected as pid:0).
271
+ supervisorPid: this.opts.supervisorPid,
272
+ },
273
+ ],
280
274
  });
281
275
  try {
282
- const peerResults = rpcResp.results ?? [];
276
+ if (!isPeerResult(rpcResp))
277
+ throw new BindingError(`Fanout peer '${siblingName}' returned no result array`);
278
+ const peerResults = rpcResp.results;
283
279
  if (peerResults.length !== bucket.length) {
284
280
  throw new Error(`peer DO returned ${peerResults.length} results for ${bucket.length} tasks ` +
285
281
  `(siblingName=${siblingName})`);
@@ -14,6 +14,12 @@
14
14
  * counter, and bumps it once. The embedder reads `generation(ctx)` wherever it
15
15
  * needs the incarnation number — one source of truth instead of mirrored host
16
16
  * fields.
17
+ *
18
+ * This is also the allocator for a workspace's generation: an embedder
19
+ * composing NimbusWorkspace over its own Durable Object calls
20
+ * `adoptGeneration(ctx)` once per incarnation and passes `generation(ctx)`
21
+ * as the workspace's `generation` — the same persisted counter the session
22
+ * derives its pid generation from, so there is no second counter to keep.
17
23
  */
18
24
  /**
19
25
  * Storage key for the isolate-generation counter (cold-start +
@@ -1 +1 @@
1
- {"version":3,"file":"generation.d.ts","sourceRoot":"","sources":["../src/generation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,2DAA2D;AAC3D,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAsBD,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,yEAAyE;AACzE,wBAAsB,eAAe,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B3E;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAE3E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvD"}
1
+ {"version":3,"file":"generation.d.ts","sourceRoot":"","sources":["../src/generation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,2DAA2D;AAC3D,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAsBD,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,yEAAyE;AACzE,wBAAsB,eAAe,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B3E;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAE3E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvD"}
@@ -14,6 +14,12 @@
14
14
  * counter, and bumps it once. The embedder reads `generation(ctx)` wherever it
15
15
  * needs the incarnation number — one source of truth instead of mirrored host
16
16
  * fields.
17
+ *
18
+ * This is also the allocator for a workspace's generation: an embedder
19
+ * composing NimbusWorkspace over its own Durable Object calls
20
+ * `adoptGeneration(ctx)` once per incarnation and passes `generation(ctx)`
21
+ * as the workspace's `generation` — the same persisted counter the session
22
+ * derives its pid generation from, so there is no second counter to keep.
17
23
  */
18
24
  import { errorText } from '@nimbus-sh/core/_shared/error-text.js';
19
25
  /**
@@ -0,0 +1,6 @@
1
+ import type { createSupervisorOpHandler } from '@nimbus-sh/core/workspace/supervisor-op.js';
2
+ export type HostNamespaceBinding = Pick<DurableObjectNamespace, 'get' | 'idFromName' | 'idFromString'>;
3
+ export type HostOpDispatch = ReturnType<typeof createSupervisorOpHandler>;
4
+ export declare function hostNamespaceBinding(env: object | null | undefined, usage: string): HostNamespaceBinding;
5
+ export declare function hostOpDispatch(stub: object, usage: string): HostOpDispatch;
6
+ //# sourceMappingURL=host-dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-dispatch.d.ts","sourceRoot":"","sources":["../src/host-dispatch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AAE5F,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,sBAAsB,EAAE,KAAK,GAAG,YAAY,GAAG,cAAc,CAAC,CAAC;AACvG,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAQ1E,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,CAOxG;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,CAW1E"}
@@ -0,0 +1,27 @@
1
+ import { BindingError } from './vendor/errors.js';
2
+ import { hostDispatchMethod, hostNamespace } from './composition.js';
3
+ function isNamespace(value) {
4
+ return typeof Reflect.get(value, 'idFromName') === 'function'
5
+ && typeof Reflect.get(value, 'idFromString') === 'function'
6
+ && typeof Reflect.get(value, 'get') === 'function';
7
+ }
8
+ export function hostNamespaceBinding(env, usage) {
9
+ const name = hostNamespace();
10
+ const binding = env ? Reflect.get(env, name) : undefined;
11
+ if (binding === null || (typeof binding !== 'object' && typeof binding !== 'function') || !isNamespace(binding)) {
12
+ throw new BindingError(`${usage}: env.${name} must be the Durable Object namespace configured by composeFabric`);
13
+ }
14
+ return binding;
15
+ }
16
+ export function hostOpDispatch(stub, usage) {
17
+ const name = hostDispatchMethod();
18
+ if (stub === null || (typeof stub !== 'object' && typeof stub !== 'function')) {
19
+ throw new BindingError(`${usage}: the workspace namespace returned no stub`);
20
+ }
21
+ const dispatch = Reflect.get(stub, name);
22
+ if (typeof dispatch !== 'function') {
23
+ throw new BindingError(`${usage}: the workspace host must forward ${name}(envelope) to the runtime`);
24
+ }
25
+ // RpcStub.call would invoke a remote method named "call".
26
+ return (envelope) => Promise.resolve(Reflect.apply(dispatch, stub, [envelope]));
27
+ }
@@ -92,10 +92,20 @@ export declare class ImageStore {
92
92
  * Materialize generated module sources in the content-addressed image store
93
93
  * and return the module-name → path map naming them.
94
94
  *
95
+ * Takes the images as a SEQUENCE, produced on demand and released as each
96
+ * one's slices land, so exactly one image's text is resident here. The old
97
+ * record-shaped parameter held every source for the whole call, and the
98
+ * caller held its own copy beside it: measured on a real-vite launch, the
99
+ * second image reported not one slice — ~25 MB of module text as UTF-16 in
100
+ * two places, plus the first image's just-freed 10.36 MB encode buffer, on a
101
+ * 128 MiB isolate. Yielding and dropping is what makes the peak one image
102
+ * instead of all of them; it is not a pacing question, and a fresh turn does
103
+ * not shrink a live heap.
104
+ *
95
105
  * Writing the sources here, once, is what lets the session stop holding
96
106
  * them: after this returns, the only thing it keeps is a path.
97
107
  */
98
- materialize(pid: number, modules: Record<string, string>, pacer: TurnBudget): Promise<Record<string, string>>;
108
+ materialize(pid: number, images: AsyncIterable<readonly [string, string]> | Iterable<readonly [string, string]>, pacer: TurnBudget): Promise<Record<string, string>>;
99
109
  /**
100
110
  * Drop every image no running process boots from.
101
111
  *
@@ -1 +1 @@
1
- {"version":3,"file":"image-store.d.ts","sourceRoot":"","sources":["../src/image-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,QAA0D,CAAC;AAErG;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,wEAAwE;IACxE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACjD,kDAAkD;IAClD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAClE,2EAA2E;IAC3E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,qDAAqD;IACrD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,qBAAa,UAAU;IAanB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAbzB,8EAA8E;IAC9E,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;;;OAMG;gBAEgB,KAAK,EAAE,MAAM,cAAc,EAC3B,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO;IAGnD;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,IAAI,IAAI;IAOjB;;;;;;OAMG;IACG,WAAW,CACf,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAoDlC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,KAAK;CAgBd"}
1
+ {"version":3,"file":"image-store.d.ts","sourceRoot":"","sources":["../src/image-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,QAA0D,CAAC;AAErG;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,wEAAwE;IACxE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACjD,kDAAkD;IAClD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAClE,2EAA2E;IAC3E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,qDAAqD;IACrD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,qBAAa,UAAU;IAanB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAbzB,8EAA8E;IAC9E,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;;;OAMG;gBAEgB,KAAK,EAAE,MAAM,cAAc,EAC3B,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO;IAGnD;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,IAAI,IAAI;IAOjB;;;;;;;;;;;;;;;;OAgBG;IACG,WAAW,CACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACtF,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAuDlC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,KAAK;CAgBd"}
@@ -84,32 +84,42 @@ export class ImageStore {
84
84
  * Materialize generated module sources in the content-addressed image store
85
85
  * and return the module-name → path map naming them.
86
86
  *
87
+ * Takes the images as a SEQUENCE, produced on demand and released as each
88
+ * one's slices land, so exactly one image's text is resident here. The old
89
+ * record-shaped parameter held every source for the whole call, and the
90
+ * caller held its own copy beside it: measured on a real-vite launch, the
91
+ * second image reported not one slice — ~25 MB of module text as UTF-16 in
92
+ * two places, plus the first image's just-freed 10.36 MB encode buffer, on a
93
+ * 128 MiB isolate. Yielding and dropping is what makes the peak one image
94
+ * instead of all of them; it is not a pacing question, and a fresh turn does
95
+ * not shrink a live heap.
96
+ *
87
97
  * Writing the sources here, once, is what lets the session stop holding
88
98
  * them: after this returns, the only thing it keeps is a path.
89
99
  */
90
- async materialize(pid, modules, pacer) {
100
+ async materialize(pid, images, pacer) {
91
101
  const fs = this.blobs();
92
- const images = {};
93
- const sources = new Map();
94
- for (const [moduleName, source] of Object.entries(modules)) {
95
- const path = facetImagePath(await facetImageDigest(source));
96
- images[moduleName] = path;
97
- sources.set(path, source);
98
- await pacer.spend(source.length);
99
- }
100
- // Register the WHOLE root set here, in one synchronous step, before any
101
- // byte of it exists on disk. That ordering is the entire protocol between
102
- // a launch and the sweep: an image is rooted from before it is written, so
103
- // a sweep can never observe a file this launch has written but not yet
104
- // claimed. The old loop achieved it by not awaiting at all, which read as
105
- // "the writes must not be interrupted" — they may be. What must not be
106
- // interrupted is the gap between writing and rooting, and rooting first
107
- // closes it for every write that follows, however many turns they span.
108
- this.residentImages.set(pid, [...sources.keys()]);
102
+ const paths = {};
103
+ // The root set is this ARRAY, held by the sweep's map from before the
104
+ // first byte and appended to as each image is named. Rooting an image
105
+ // before its own first byte is the whole of the protocol: a sweep that
106
+ // runs while this launch is suspended sees every image already written as
107
+ // rooted, and an image not yet written is not yet a file. The array
108
+ // identity is what makes an append visible — do not replace it.
109
+ const rooted = [];
110
+ this.residentImages.set(pid, rooted);
109
111
  fs.mkdirp(FACET_IMAGE_DIR);
110
- for (const [path, source] of sources) {
112
+ let count = 0;
113
+ for await (const [moduleName, source] of images) {
114
+ const path = facetImagePath(await facetImageDigest(source));
115
+ paths[moduleName] = path;
116
+ rooted.push(path);
117
+ count++;
111
118
  const stored = path.replace(/^\/+/, '');
112
119
  const bytes = new TextEncoder().encode(source);
120
+ console.log('[image-store] pid=' + pid + ' image ' + count + ' ' + moduleName + ' → '
121
+ + path.slice(-12) + ' ' + bytes.byteLength + ' bytes, slice=' + FACET_IMAGE_WRITE_SLICE_BYTES
122
+ + ' turns=' + pacer.chunks);
113
123
  // An image at its full size is a COMPLETE one: a write only ever grows
114
124
  // the file from offset zero, so a write cut short by a reset leaves a
115
125
  // strictly shorter file and fails this test. Size is enough of a check
@@ -138,7 +148,8 @@ export class ImageStore {
138
148
  } while (offset < bytes.byteLength);
139
149
  }
140
150
  this.sweep(fs);
141
- return images;
151
+ console.log('[image-store] pid=' + pid + ' materialized ' + count + ' image(s) in ' + pacer.chunks + ' turn(s)');
152
+ return paths;
142
153
  }
143
154
  /**
144
155
  * Drop every image no running process boots from.