@nimbus-sh/platform 0.5.0 → 0.5.1

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.
@@ -11,8 +11,18 @@
11
11
  * stagedBootAssembler: (env, stage) => assembleConfig(env, stage),
12
12
  * });
13
13
  *
14
- * First-write-wins, like every holder in this module: the composition
15
- * root's module scope runs once per isolate, before any request.
14
+ * The composition belongs at module scope: it is per isolate, and the
15
+ * platform serves a worker's entrypoints from whichever isolate it likes,
16
+ * so an isolate that never ran an embedder's request path must still know
17
+ * the composition. Composing again with the same values is a no-op;
18
+ * composing again with different values throws, because the second caller
19
+ * would otherwise run against a host it never named (a Worker that imports
20
+ * Nimbus's own entry and composes its own host would be that caller).
21
+ *
22
+ * The one thing the entrypoints need from the composition, the route back
23
+ * to the host, travels with every binding the fabric mints
24
+ * ({@link hostRoute}), so an entrypoint answers correctly in an isolate
25
+ * whose composition is absent or different.
16
26
  *
17
27
  * `ctx.exports` is runtime state, not composition: workerd mints it per
18
28
  * instance, so the embedder captures it where the platform hands it over —
@@ -36,15 +46,41 @@ export interface FabricComposition {
36
46
  /** RPC method the host forwards to workspace.supervisorOp. */
37
47
  hostDispatchMethod?: string;
38
48
  }
49
+ /**
50
+ * The way back to the host from a binding the fabric minted: the namespace
51
+ * binding the host's Durable Object lives under, the method it forwards
52
+ * supervisor envelopes through, and the entrypoint export that answers
53
+ * supervisor bindings. Minted from the composition in the host's isolate
54
+ * and carried in the binding's props, so the entrypoint that receives the
55
+ * call resolves the host from the props rather than from its own isolate's
56
+ * composition.
57
+ */
58
+ export interface HostRoute {
59
+ supervisorEntrypoint: string;
60
+ hostNamespace: string;
61
+ hostDispatchMethod: string;
62
+ }
39
63
  export declare const DEFAULT_HOST_NAMESPACE = "NIMBUS_SESSION";
40
64
  export declare const DEFAULT_HOST_DISPATCH_METHOD = "supervisorOp";
41
- /** Composition and loopback exports are first-write-wins for this isolate. */
65
+ /**
66
+ * Compose once per isolate. A second call with the same values is a no-op;
67
+ * a second call with different values throws, naming both, so an embedder
68
+ * whose composition lost to an earlier import learns it at startup rather
69
+ * than from a facet that reached the wrong host.
70
+ */
42
71
  export declare function composeFabric(value: FabricComposition): void;
43
72
  export declare function adoptCtxExports(value: CtxExports): void;
44
73
  export declare function getCtxExports(): CtxExports | null;
45
- export declare function supervisorEntrypoint(exportsObj?: unknown): EntrypointLoopbackFactory | null;
74
+ export declare function supervisorEntrypoint(exportsObj?: unknown, name?: string | undefined): EntrypointLoopbackFactory | null;
46
75
  export declare function supervisorEntrypointName(): string | null;
47
76
  export declare function hostNamespace(): string;
48
77
  export declare function hostDispatchMethod(): string;
78
+ /**
79
+ * The composed route, for the props of a binding minted in this isolate.
80
+ * Null when nothing is composed, like {@link supervisorEntrypoint}: a
81
+ * program run without a composition gets no supervisor binding, and needs
82
+ * no route back to a host it cannot reach.
83
+ */
84
+ export declare function hostRoute(): HostRoute | null;
49
85
  export declare function stagedBootAssembler(): StagedBootAssembler;
50
86
  //# sourceMappingURL=composition.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,IAAI,GAAG,OAAO,EAAE,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC;AAC7F,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,yBAAyB,GAAG,SAAS,CAAC,CAAC;AAC/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEpF,MAAM,WAAW,iBAAiB;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,eAAO,MAAM,sBAAsB,mBAAmB,CAAC;AACvD,eAAO,MAAM,4BAA4B,iBAAiB,CAAC;AAI3D,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAE5D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAEvD;AAED,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG,yBAAyB,GAAG,IAAI,CAO3F;AAED,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,IAAI,CAExD;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,mBAAmB,IAAI,mBAAmB,CASzD"}
1
+ {"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,IAAI,GAAG,OAAO,EAAE,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC;AAC7F,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,yBAAyB,GAAG,SAAS,CAAC,CAAC;AAC/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEpF,MAAM,WAAW,iBAAiB;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,sBAAsB,mBAAmB,CAAC;AACvD,eAAO,MAAM,4BAA4B,iBAAiB,CAAC;AAI3D;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAmB5D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAEvD;AAED,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,qBAAoC,GAAG,yBAAyB,GAAG,IAAI,CAMrI;AAED,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,IAAI,CAExD;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,IAAI,SAAS,GAAG,IAAI,CAO5C;AAED,wBAAgB,mBAAmB,IAAI,mBAAmB,CASzD"}
@@ -2,10 +2,30 @@ export const DEFAULT_HOST_NAMESPACE = 'NIMBUS_SESSION';
2
2
  export const DEFAULT_HOST_DISPATCH_METHOD = 'supervisorOp';
3
3
  let composition = null;
4
4
  let ctxExports = null;
5
- /** Composition and loopback exports are first-write-wins for this isolate. */
5
+ /**
6
+ * Compose once per isolate. A second call with the same values is a no-op;
7
+ * a second call with different values throws, naming both, so an embedder
8
+ * whose composition lost to an earlier import learns it at startup rather
9
+ * than from a facet that reached the wrong host.
10
+ */
6
11
  export function composeFabric(value) {
7
- if (!composition)
12
+ if (!composition) {
8
13
  composition = value;
14
+ return;
15
+ }
16
+ const differences = ['supervisorEntrypoint', 'hostNamespace', 'hostDispatchMethod', 'stagedBootAssembler']
17
+ .filter((key) => composition?.[key] !== value[key]);
18
+ if (differences.length === 0)
19
+ return;
20
+ const describe = (c) => JSON.stringify({
21
+ supervisorEntrypoint: c.supervisorEntrypoint,
22
+ hostNamespace: c.hostNamespace ?? DEFAULT_HOST_NAMESPACE,
23
+ hostDispatchMethod: c.hostDispatchMethod ?? DEFAULT_HOST_DISPATCH_METHOD,
24
+ stagedBootAssembler: c.stagedBootAssembler ? 'set' : 'unset',
25
+ });
26
+ throw new Error(`fabric: composed twice with different values (${differences.join(', ')}): `
27
+ + `first ${describe(composition)}, then ${describe(value)}. `
28
+ + 'One composition per isolate; a Worker that imports another Nimbus entry inherits its composition.');
9
29
  }
10
30
  export function adoptCtxExports(value) {
11
31
  if (!ctxExports)
@@ -14,9 +34,8 @@ export function adoptCtxExports(value) {
14
34
  export function getCtxExports() {
15
35
  return ctxExports;
16
36
  }
17
- export function supervisorEntrypoint(exportsObj) {
37
+ export function supervisorEntrypoint(exportsObj, name = composition?.supervisorEntrypoint) {
18
38
  const exports = exportsObj ?? ctxExports;
19
- const name = composition?.supervisorEntrypoint;
20
39
  if (!name)
21
40
  return null;
22
41
  if ((typeof exports !== 'object' && typeof exports !== 'function') || exports === null)
@@ -33,6 +52,21 @@ export function hostNamespace() {
33
52
  export function hostDispatchMethod() {
34
53
  return composition?.hostDispatchMethod ?? DEFAULT_HOST_DISPATCH_METHOD;
35
54
  }
55
+ /**
56
+ * The composed route, for the props of a binding minted in this isolate.
57
+ * Null when nothing is composed, like {@link supervisorEntrypoint}: a
58
+ * program run without a composition gets no supervisor binding, and needs
59
+ * no route back to a host it cannot reach.
60
+ */
61
+ export function hostRoute() {
62
+ if (!composition)
63
+ return null;
64
+ return {
65
+ supervisorEntrypoint: composition.supervisorEntrypoint,
66
+ hostNamespace: hostNamespace(),
67
+ hostDispatchMethod: hostDispatchMethod(),
68
+ };
69
+ }
36
70
  export function stagedBootAssembler() {
37
71
  const assembler = composition?.stagedBootAssembler;
38
72
  if (!assembler) {
@@ -75,6 +75,12 @@ export interface DiagCounters {
75
75
  * Surfaced as `breakdown.prefetchCacheBytes`. A gauge, not a delta.
76
76
  */
77
77
  prefetchCacheBytes: number;
78
+ /**
79
+ * Bytes retained by the FacetManager's ESM→CJS transform cache, bounded by
80
+ * ESM_TRANSFORM_CACHE_MAX_BYTES. Surfaced as `breakdown.transformCacheBytes`.
81
+ * A gauge, not a delta.
82
+ */
83
+ transformCacheBytes: number;
78
84
  /** Install-facet counters. Populated by npm-installer after a
79
85
  * successful batch-facet dispatch returns. Confirms the install ran
80
86
  * in the facet (tarballsCompleted > 0) and surfaces the
@@ -175,6 +181,8 @@ export declare function prefetchBundleEnd(bytes: number): void;
175
181
  * owns the number and reports it whenever it admits or evicts an entry.
176
182
  */
177
183
  export declare function setPrefetchCacheBytes(bytes: number): void;
184
+ /** Publish the transform cache's live retained total; same contract as above. */
185
+ export declare function setTransformCacheBytes(bytes: number): void;
178
186
  /** Fold facet-returned counters into the supervisor's diag state.
179
187
  * Called by npm-installer after the batch-facet returns; aggregates
180
188
  * rather than replaces so multiple install runs in the same DO
@@ -1 +1 @@
1
- {"version":3,"file":"diag-counters.d.ts","sourceRoot":"","sources":["../src/diag-counters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"diag-counters.d.ts","sourceRoot":"","sources":["../src/diag-counters.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,7 @@ const _counters = {
23
23
  inFlightRpcPayloadBytes: 0,
24
24
  prefetchBundleBytes: 0,
25
25
  prefetchCacheBytes: 0,
26
+ transformCacheBytes: 0,
26
27
  installFacet: {
27
28
  tarballsCompleted: 0,
28
29
  cumulativeBytesDecoded: 0,
@@ -122,6 +123,11 @@ export function setPrefetchCacheBytes(bytes) {
122
123
  const n = Number(bytes);
123
124
  _counters.prefetchCacheBytes = Number.isFinite(n) && n > 0 ? n : 0;
124
125
  }
126
+ /** Publish the transform cache's live retained total; same contract as above. */
127
+ export function setTransformCacheBytes(bytes) {
128
+ const n = Number(bytes);
129
+ _counters.transformCacheBytes = Number.isFinite(n) && n > 0 ? n : 0;
130
+ }
125
131
  /** Fold facet-returned counters into the supervisor's diag state.
126
132
  * Called by npm-installer after the batch-facet returns; aggregates
127
133
  * rather than replaces so multiple install runs in the same DO
@@ -180,6 +180,12 @@ export interface HeapBreakdown {
180
180
  * the cache holds rather than a reservation it might use.
181
181
  */
182
182
  prefetchCacheBytes: number;
183
+ /**
184
+ * Bytes retained by the FacetManager's ESM→CJS transform cache
185
+ * (diag-counters `transformCacheBytes`), the other cache that persists
186
+ * across execs. Bounded by ESM_TRANSFORM_CACHE_MAX_BYTES.
187
+ */
188
+ transformCacheBytes: number;
183
189
  /**
184
190
  * Shared-budget occupancy not already represented by the named transient
185
191
  * counters. This covers full-budget owners and keeps cross-DO module-local
@@ -1 +1 @@
1
- {"version":3,"file":"heap-estimate.d.ts","sourceRoot":"","sources":["../src/heap-estimate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,wBAAwB,CAAC;AAEhC;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,sFAM1B,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAAO,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,SAAS,EAAE,aAAa,CAAC;IACzB;;;OAGG;IACH,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IACrC;;;;OAIG;IACH,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,gBAAgB,EAAE,+BAA+B,CAAC;CACnD;AAED,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,uBAAuB,EAAE,MAAM,CAAC;IAChC,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,gBAAgB,EAAE,MAAM,CAAC;IACzB;;sEAEkE;IAClE,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;OAYG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,4BAA4B,EAAE,MAAM,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,aAAa,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAyBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAQnE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,CAAC,EAAE,YAAY,EACf,GAAG,EAAE,aAAa,GACjB,YAAY,CAkDd"}
1
+ {"version":3,"file":"heap-estimate.d.ts","sourceRoot":"","sources":["../src/heap-estimate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAOH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,wBAAwB,CAAC;AAEhC;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,sFAM1B,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAAO,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,SAAS,EAAE,aAAa,CAAC;IACzB;;;OAGG;IACH,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;IACrC;;;;OAIG;IACH,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,gBAAgB,EAAE,+BAA+B,CAAC;CACnD;AAED,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,uBAAuB,EAAE,MAAM,CAAC;IAChC,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,gBAAgB,EAAE,MAAM,CAAC;IACzB;;sEAEkE;IAClE,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;OAYG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,4BAA4B,EAAE,MAAM,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,aAAa,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAyBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAQnE;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,CAAC,EAAE,YAAY,EACf,GAAG,EAAE,aAAa,GACjB,YAAY,CAoDd"}
@@ -160,6 +160,7 @@ export function estimateSupervisorHeap(c, vfs) {
160
160
  streamingBuffersBytes: c.inFlightRpcPayloadBytes,
161
161
  prefetchBundleBytes: c.prefetchBundleBytes,
162
162
  prefetchCacheBytes: c.prefetchCacheBytes,
163
+ transformCacheBytes: c.transformCacheBytes,
163
164
  unattributedReservationBytes: Math.max(0, allocationBudget.current - transientAttributedBytes),
164
165
  };
165
166
  const estimatedBytes = breakdown.supervisorBaselineBytes +
@@ -169,6 +170,7 @@ export function estimateSupervisorHeap(c, vfs) {
169
170
  breakdown.streamingBuffersBytes +
170
171
  breakdown.prefetchBundleBytes +
171
172
  breakdown.prefetchCacheBytes +
173
+ breakdown.transformCacheBytes +
172
174
  breakdown.unattributedReservationBytes;
173
175
  const percentOfCeiling = Math.round((estimatedBytes / SUPERVISOR_HEAP_CEILING_BYTES) * 1000) / 10;
174
176
  return {
package/dist/limits.d.ts CHANGED
@@ -69,6 +69,28 @@ export declare const SQLITE_MAX_ROW_BYTES = 2000000;
69
69
  * fabric defers async reconciliation off the gate entirely (`onColdStart`).
70
70
  */
71
71
  export declare const BLOCK_CONCURRENCY_CANCEL_MS = 30000;
72
+ /**
73
+ * Cadence of the alarm that keeps a session Durable Object in memory while
74
+ * a resident process runs. The platform evicts an object after roughly ten
75
+ * seconds with no in-flight event; a pending promise (`ctx.waitUntil`) is
76
+ * not one, and facets die with their parent. Measured 2026-09-21 on local
77
+ * workerd and on Cloudflare: a quiet resident process survived 8 s of
78
+ * silence and was re-driven under a new pid namespace after 12 s.
79
+ */
80
+ export declare const RESIDENT_KEEPALIVE_MS = 5000;
81
+ /**
82
+ * How long the keep-alive holds a session after its last client is gone.
83
+ *
84
+ * The keep-alive exists so a quiet process survives while its user is
85
+ * present. Unbounded, it made every abandoned session with a dev server
86
+ * run forever: the probe suite's cancelled runs left staging with resident
87
+ * watchers polling `stat` at ~7.5/s in the isolate every session shares,
88
+ * and launches that pass on a fresh Worker reset there (2026-09-22). Past
89
+ * this grace with no attached socket and no request, the session idles
90
+ * out as it did before the keep-alive, and the launch journal re-drives
91
+ * the process when a client returns. Long enough for a tab reload.
92
+ */
93
+ export declare const RESIDENT_KEEPALIVE_DETACHED_MS = 60000;
72
94
  /**
73
95
  * Bytes one hibernatable WebSocket attachment may serialize to. Verified in
74
96
  * workerd source at the pinned version (v1.20260603.1,
@@ -1 +1 @@
1
- {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,QAAS,CAAC;AAEjC,eAAO,MAAM,iBAAiB,QAAkB,CAAC;AACjD,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAI5C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,cAAiB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,QAAa,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,UAAY,CAAC;AAI9C;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAIlD;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAOhD,eAAO,MAAM,0BAA0B,QAAmB,CAAC;AA6B3D,eAAO,MAAM,6BAA6B,QAAmB,CAAC;AAO9D,eAAO,MAAM,4CAA4C,QAAmB,CAAC;AAY7E,eAAO,MAAM,6BAA6B,QAAc,CAAC;AAKzD,eAAO,MAAM,oCAAoC,QAAkB,CAAC;AAQpE,eAAO,MAAM,0BAA0B,QAA6B,CAAC;AACrE,eAAO,MAAM,sBAAsB,IAAI,CAAC"}
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,QAAS,CAAC;AAEjC,eAAO,MAAM,iBAAiB,QAAkB,CAAC;AACjD,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAI5C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,cAAiB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,QAAa,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,UAAY,CAAC;AAI9C;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAIlD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,OAAQ,CAAC;AAE3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAIrD;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAOhD,eAAO,MAAM,0BAA0B,QAAmB,CAAC;AA6B3D,eAAO,MAAM,6BAA6B,QAAmB,CAAC;AAO9D,eAAO,MAAM,4CAA4C,QAAmB,CAAC;AAY7E,eAAO,MAAM,6BAA6B,QAAc,CAAC;AAKzD,eAAO,MAAM,oCAAoC,QAAkB,CAAC;AAQpE,eAAO,MAAM,0BAA0B,QAA6B,CAAC;AACrE,eAAO,MAAM,sBAAsB,IAAI,CAAC"}
package/dist/limits.js CHANGED
@@ -77,6 +77,29 @@ export const SQLITE_MAX_ROW_BYTES = 2_000_000;
77
77
  * fabric defers async reconciliation off the gate entirely (`onColdStart`).
78
78
  */
79
79
  export const BLOCK_CONCURRENCY_CANCEL_MS = 30_000;
80
+ // ── Eviction window ─────────────────────────────────────────────────────
81
+ /**
82
+ * Cadence of the alarm that keeps a session Durable Object in memory while
83
+ * a resident process runs. The platform evicts an object after roughly ten
84
+ * seconds with no in-flight event; a pending promise (`ctx.waitUntil`) is
85
+ * not one, and facets die with their parent. Measured 2026-09-21 on local
86
+ * workerd and on Cloudflare: a quiet resident process survived 8 s of
87
+ * silence and was re-driven under a new pid namespace after 12 s.
88
+ */
89
+ export const RESIDENT_KEEPALIVE_MS = 5_000;
90
+ /**
91
+ * How long the keep-alive holds a session after its last client is gone.
92
+ *
93
+ * The keep-alive exists so a quiet process survives while its user is
94
+ * present. Unbounded, it made every abandoned session with a dev server
95
+ * run forever: the probe suite's cancelled runs left staging with resident
96
+ * watchers polling `stat` at ~7.5/s in the isolate every session shares,
97
+ * and launches that pass on a fresh Worker reset there (2026-09-22). Past
98
+ * this grace with no attached socket and no request, the session idles
99
+ * out as it did before the keep-alive, and the launch journal re-drives
100
+ * the process when a client returns. Long enough for a tab reload.
101
+ */
102
+ export const RESIDENT_KEEPALIVE_DETACHED_MS = 60_000;
80
103
  // ── WebSocket attachment bound ──────────────────────────────────────────
81
104
  /**
82
105
  * Bytes one hibernatable WebSocket attachment may serialize to. Verified in
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nimbus-sh/platform",
3
- "version": "0.5.0",
4
- "description": "The measured Cloudflare platform truths Nimbus is built on — error taxonomy, limits tables, budget coordination, and wire-format primitives, with zero runtime dependencies.",
3
+ "version": "0.5.1",
4
+ "description": "The measured Cloudflare platform truths Nimbus is built on \u2014 error taxonomy, limits tables, budget coordination, and wire-format primitives, with zero runtime dependencies.",
5
5
  "keywords": [
6
6
  "cloudflare",
7
7
  "workers",
@@ -11,8 +11,18 @@
11
11
  * stagedBootAssembler: (env, stage) => assembleConfig(env, stage),
12
12
  * });
13
13
  *
14
- * First-write-wins, like every holder in this module: the composition
15
- * root's module scope runs once per isolate, before any request.
14
+ * The composition belongs at module scope: it is per isolate, and the
15
+ * platform serves a worker's entrypoints from whichever isolate it likes,
16
+ * so an isolate that never ran an embedder's request path must still know
17
+ * the composition. Composing again with the same values is a no-op;
18
+ * composing again with different values throws, because the second caller
19
+ * would otherwise run against a host it never named (a Worker that imports
20
+ * Nimbus's own entry and composes its own host would be that caller).
21
+ *
22
+ * The one thing the entrypoints need from the composition, the route back
23
+ * to the host, travels with every binding the fabric mints
24
+ * ({@link hostRoute}), so an entrypoint answers correctly in an isolate
25
+ * whose composition is absent or different.
16
26
  *
17
27
  * `ctx.exports` is runtime state, not composition: workerd mints it per
18
28
  * instance, so the embedder captures it where the platform hands it over —
@@ -36,14 +46,51 @@ export interface FabricComposition {
36
46
  hostDispatchMethod?: string;
37
47
  }
38
48
 
49
+ /**
50
+ * The way back to the host from a binding the fabric minted: the namespace
51
+ * binding the host's Durable Object lives under, the method it forwards
52
+ * supervisor envelopes through, and the entrypoint export that answers
53
+ * supervisor bindings. Minted from the composition in the host's isolate
54
+ * and carried in the binding's props, so the entrypoint that receives the
55
+ * call resolves the host from the props rather than from its own isolate's
56
+ * composition.
57
+ */
58
+ export interface HostRoute {
59
+ supervisorEntrypoint: string;
60
+ hostNamespace: string;
61
+ hostDispatchMethod: string;
62
+ }
63
+
39
64
  export const DEFAULT_HOST_NAMESPACE = 'NIMBUS_SESSION';
40
65
  export const DEFAULT_HOST_DISPATCH_METHOD = 'supervisorOp';
41
66
  let composition: FabricComposition | null = null;
42
67
  let ctxExports: CtxExports | null = null;
43
68
 
44
- /** Composition and loopback exports are first-write-wins for this isolate. */
69
+ /**
70
+ * Compose once per isolate. A second call with the same values is a no-op;
71
+ * a second call with different values throws, naming both, so an embedder
72
+ * whose composition lost to an earlier import learns it at startup rather
73
+ * than from a facet that reached the wrong host.
74
+ */
45
75
  export function composeFabric(value: FabricComposition): void {
46
- if (!composition) composition = value;
76
+ if (!composition) {
77
+ composition = value;
78
+ return;
79
+ }
80
+ const differences = (['supervisorEntrypoint', 'hostNamespace', 'hostDispatchMethod', 'stagedBootAssembler'] as const)
81
+ .filter((key) => composition?.[key] !== value[key]);
82
+ if (differences.length === 0) return;
83
+ const describe = (c: FabricComposition) => JSON.stringify({
84
+ supervisorEntrypoint: c.supervisorEntrypoint,
85
+ hostNamespace: c.hostNamespace ?? DEFAULT_HOST_NAMESPACE,
86
+ hostDispatchMethod: c.hostDispatchMethod ?? DEFAULT_HOST_DISPATCH_METHOD,
87
+ stagedBootAssembler: c.stagedBootAssembler ? 'set' : 'unset',
88
+ });
89
+ throw new Error(
90
+ `fabric: composed twice with different values (${differences.join(', ')}): `
91
+ + `first ${describe(composition)}, then ${describe(value)}. `
92
+ + 'One composition per isolate; a Worker that imports another Nimbus entry inherits its composition.',
93
+ );
47
94
  }
48
95
 
49
96
  export function adoptCtxExports(value: CtxExports): void {
@@ -54,9 +101,8 @@ export function getCtxExports(): CtxExports | null {
54
101
  return ctxExports;
55
102
  }
56
103
 
57
- export function supervisorEntrypoint(exportsObj?: unknown): EntrypointLoopbackFactory | null {
104
+ export function supervisorEntrypoint(exportsObj?: unknown, name = composition?.supervisorEntrypoint): EntrypointLoopbackFactory | null {
58
105
  const exports = exportsObj ?? ctxExports;
59
- const name = composition?.supervisorEntrypoint;
60
106
  if (!name) return null;
61
107
  if ((typeof exports !== 'object' && typeof exports !== 'function') || exports === null) return null;
62
108
  const factory = (exports as Record<string, unknown>)[name];
@@ -75,6 +121,21 @@ export function hostDispatchMethod(): string {
75
121
  return composition?.hostDispatchMethod ?? DEFAULT_HOST_DISPATCH_METHOD;
76
122
  }
77
123
 
124
+ /**
125
+ * The composed route, for the props of a binding minted in this isolate.
126
+ * Null when nothing is composed, like {@link supervisorEntrypoint}: a
127
+ * program run without a composition gets no supervisor binding, and needs
128
+ * no route back to a host it cannot reach.
129
+ */
130
+ export function hostRoute(): HostRoute | null {
131
+ if (!composition) return null;
132
+ return {
133
+ supervisorEntrypoint: composition.supervisorEntrypoint,
134
+ hostNamespace: hostNamespace(),
135
+ hostDispatchMethod: hostDispatchMethod(),
136
+ };
137
+ }
138
+
78
139
  export function stagedBootAssembler(): StagedBootAssembler {
79
140
  const assembler = composition?.stagedBootAssembler;
80
141
  if (!assembler) {
@@ -82,6 +82,12 @@ export interface DiagCounters {
82
82
  * Surfaced as `breakdown.prefetchCacheBytes`. A gauge, not a delta.
83
83
  */
84
84
  prefetchCacheBytes: number;
85
+ /**
86
+ * Bytes retained by the FacetManager's ESM→CJS transform cache, bounded by
87
+ * ESM_TRANSFORM_CACHE_MAX_BYTES. Surfaced as `breakdown.transformCacheBytes`.
88
+ * A gauge, not a delta.
89
+ */
90
+ transformCacheBytes: number;
85
91
  /** Install-facet counters. Populated by npm-installer after a
86
92
  * successful batch-facet dispatch returns. Confirms the install ran
87
93
  * in the facet (tarballsCompleted > 0) and surfaces the
@@ -141,6 +147,7 @@ const _counters: DiagCounters = {
141
147
  inFlightRpcPayloadBytes: 0,
142
148
  prefetchBundleBytes: 0,
143
149
  prefetchCacheBytes: 0,
150
+ transformCacheBytes: 0,
144
151
  installFacet: {
145
152
  tarballsCompleted: 0,
146
153
  cumulativeBytesDecoded: 0,
@@ -243,6 +250,12 @@ export function setPrefetchCacheBytes(bytes: number): void {
243
250
  _counters.prefetchCacheBytes = Number.isFinite(n) && n > 0 ? n : 0;
244
251
  }
245
252
 
253
+ /** Publish the transform cache's live retained total; same contract as above. */
254
+ export function setTransformCacheBytes(bytes: number): void {
255
+ const n = Number(bytes);
256
+ _counters.transformCacheBytes = Number.isFinite(n) && n > 0 ? n : 0;
257
+ }
258
+
246
259
  /** Fold facet-returned counters into the supervisor's diag state.
247
260
  * Called by npm-installer after the batch-facet returns; aggregates
248
261
  * rather than replaces so multiple install runs in the same DO
@@ -200,6 +200,12 @@ export interface HeapBreakdown {
200
200
  * the cache holds rather than a reservation it might use.
201
201
  */
202
202
  prefetchCacheBytes: number;
203
+ /**
204
+ * Bytes retained by the FacetManager's ESM→CJS transform cache
205
+ * (diag-counters `transformCacheBytes`), the other cache that persists
206
+ * across execs. Bounded by ESM_TRANSFORM_CACHE_MAX_BYTES.
207
+ */
208
+ transformCacheBytes: number;
203
209
  /**
204
210
  * Shared-budget occupancy not already represented by the named transient
205
211
  * counters. This covers full-budget owners and keeps cross-DO module-local
@@ -297,6 +303,7 @@ export function estimateSupervisorHeap(
297
303
  streamingBuffersBytes: c.inFlightRpcPayloadBytes,
298
304
  prefetchBundleBytes: c.prefetchBundleBytes,
299
305
  prefetchCacheBytes: c.prefetchCacheBytes,
306
+ transformCacheBytes: c.transformCacheBytes,
300
307
  unattributedReservationBytes: Math.max(
301
308
  0,
302
309
  allocationBudget.current - transientAttributedBytes,
@@ -311,6 +318,7 @@ export function estimateSupervisorHeap(
311
318
  breakdown.streamingBuffersBytes +
312
319
  breakdown.prefetchBundleBytes +
313
320
  breakdown.prefetchCacheBytes +
321
+ breakdown.transformCacheBytes +
314
322
  breakdown.unattributedReservationBytes;
315
323
 
316
324
  const percentOfCeiling = Math.round(
package/src/limits.ts CHANGED
@@ -89,6 +89,32 @@ export const SQLITE_MAX_ROW_BYTES = 2_000_000;
89
89
  */
90
90
  export const BLOCK_CONCURRENCY_CANCEL_MS = 30_000;
91
91
 
92
+ // ── Eviction window ─────────────────────────────────────────────────────
93
+
94
+ /**
95
+ * Cadence of the alarm that keeps a session Durable Object in memory while
96
+ * a resident process runs. The platform evicts an object after roughly ten
97
+ * seconds with no in-flight event; a pending promise (`ctx.waitUntil`) is
98
+ * not one, and facets die with their parent. Measured 2026-09-21 on local
99
+ * workerd and on Cloudflare: a quiet resident process survived 8 s of
100
+ * silence and was re-driven under a new pid namespace after 12 s.
101
+ */
102
+ export const RESIDENT_KEEPALIVE_MS = 5_000;
103
+
104
+ /**
105
+ * How long the keep-alive holds a session after its last client is gone.
106
+ *
107
+ * The keep-alive exists so a quiet process survives while its user is
108
+ * present. Unbounded, it made every abandoned session with a dev server
109
+ * run forever: the probe suite's cancelled runs left staging with resident
110
+ * watchers polling `stat` at ~7.5/s in the isolate every session shares,
111
+ * and launches that pass on a fresh Worker reset there (2026-09-22). Past
112
+ * this grace with no attached socket and no request, the session idles
113
+ * out as it did before the keep-alive, and the launch journal re-drives
114
+ * the process when a client returns. Long enough for a tab reload.
115
+ */
116
+ export const RESIDENT_KEEPALIVE_DETACHED_MS = 60_000;
117
+
92
118
  // ── WebSocket attachment bound ──────────────────────────────────────────
93
119
 
94
120
  /**