@nimbus-sh/platform 0.4.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.
- package/dist/composition.d.ts +40 -4
- package/dist/composition.d.ts.map +1 -1
- package/dist/composition.js +38 -4
- package/dist/diag-counters.d.ts +8 -0
- package/dist/diag-counters.d.ts.map +1 -1
- package/dist/diag-counters.js +6 -0
- package/dist/heap-estimate.d.ts +6 -0
- package/dist/heap-estimate.d.ts.map +1 -1
- package/dist/heap-estimate.js +2 -0
- package/dist/limits.d.ts +29 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +30 -0
- package/dist/w7-frame.js +19 -4
- package/package.json +2 -2
- package/src/composition.ts +67 -6
- package/src/diag-counters.ts +13 -0
- package/src/heap-estimate.ts +8 -0
- package/src/limits.ts +34 -0
- package/src/w7-frame.ts +20 -4
package/dist/composition.d.ts
CHANGED
|
@@ -11,8 +11,18 @@
|
|
|
11
11
|
* stagedBootAssembler: (env, stage) => assembleConfig(env, stage),
|
|
12
12
|
* });
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
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"}
|
package/dist/composition.js
CHANGED
|
@@ -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
|
-
/**
|
|
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) {
|
package/dist/diag-counters.d.ts
CHANGED
|
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,YAAY,EAAE,YAAY,CAAC;IAC3B;;;;;;;;;;;;;;;;;OAiBG;IACH,uBAAuB,EAAE,MAAM,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;OAWG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;qCAGiC;IACjC,YAAY,EAAE;QACZ,wEAAwE;QACxE,iBAAiB,EAAE,MAAM,CAAC;QAC1B,sEAAsE;QACtE,sBAAsB,EAAE,MAAM,CAAC;QAC/B,2EAA2E;QAC3E,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF;yEACqE;IACrE,cAAc,EAAE;QACd,4DAA4D;QAC5D,SAAS,EAAE,MAAM,CAAC;QAClB,8DAA8D;QAC9D,gBAAgB,EAAE,MAAM,CAAC;QACzB,8DAA8D;QAC9D,MAAM,EAAE,MAAM,CAAC;QACf,uEAAuE;QACvE,OAAO,EAAE,MAAM,CAAC;QAChB;;;wBAGgB;QAChB,aAAa,EAAE,MAAM,CAAC;QACtB;;;;;;;gEAOwD;QACxD,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACxC,CAAC;IACF;2EACuE;IACvE,EAAE,EAAE;QACF;8BACsB;QACtB,wBAAwB,EAAE,MAAM,CAAC;QACjC;6CACqC;QACrC,0BAA0B,EAAE,MAAM,CAAC;QACnC,8CAA8C;QAC9C,0BAA0B,EAAE,MAAM,CAAC;QACnC,gDAAgD;QAChD,4BAA4B,EAAE,MAAM,CAAC;KACtC,CAAC;CACH;
|
|
1
|
+
{"version":3,"file":"diag-counters.d.ts","sourceRoot":"","sources":["../src/diag-counters.ts"],"names":[],"mappings":"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"}
|
package/dist/diag-counters.js
CHANGED
|
@@ -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
|
package/dist/heap-estimate.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/heap-estimate.js
CHANGED
|
@@ -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
|
@@ -18,6 +18,13 @@ export declare const CHUNK_SIZE = 65536;
|
|
|
18
18
|
export declare const MAX_TX_BLOB_BYTES: number;
|
|
19
19
|
export declare const MAX_TX_LOGICAL_ROWS = 256;
|
|
20
20
|
export declare const MAX_TX_SQL_EXECS = 64;
|
|
21
|
+
/**
|
|
22
|
+
* Bound parameters per SQL statement on Durable Objects SQLite. Measured
|
|
23
|
+
* 2026-09-21 on workerd: 100 bind, 101 fail as `too many SQL variables`
|
|
24
|
+
* (a 9-row insert of 12 columns fails, 8 rows succeed). Every IN-list and
|
|
25
|
+
* multi-row VALUES statement is sized from this.
|
|
26
|
+
*/
|
|
27
|
+
export declare const SQL_MAX_BOUND_PARAMETERS = 100;
|
|
21
28
|
/**
|
|
22
29
|
* Storage per SQLite-backed Durable Object: 10 decimal GB documented
|
|
23
30
|
* (Workers Paid), shared by the root object, every facet beneath it, and
|
|
@@ -62,6 +69,28 @@ export declare const SQLITE_MAX_ROW_BYTES = 2000000;
|
|
|
62
69
|
* fabric defers async reconciliation off the gate entirely (`onColdStart`).
|
|
63
70
|
*/
|
|
64
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;
|
|
65
94
|
/**
|
|
66
95
|
* Bytes one hibernatable WebSocket attachment may serialize to. Verified in
|
|
67
96
|
* workerd source at the pinned version (v1.20260603.1,
|
package/dist/limits.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
@@ -24,6 +24,13 @@ export const CHUNK_SIZE = 65_536;
|
|
|
24
24
|
export const MAX_TX_BLOB_BYTES = 1 * 1024 * 1024;
|
|
25
25
|
export const MAX_TX_LOGICAL_ROWS = 256;
|
|
26
26
|
export const MAX_TX_SQL_EXECS = 64;
|
|
27
|
+
/**
|
|
28
|
+
* Bound parameters per SQL statement on Durable Objects SQLite. Measured
|
|
29
|
+
* 2026-09-21 on workerd: 100 bind, 101 fail as `too many SQL variables`
|
|
30
|
+
* (a 9-row insert of 12 columns fails, 8 rows succeed). Every IN-list and
|
|
31
|
+
* multi-row VALUES statement is sized from this.
|
|
32
|
+
*/
|
|
33
|
+
export const SQL_MAX_BOUND_PARAMETERS = 100;
|
|
27
34
|
// ── SQLite storage walls ────────────────────────────────────────────────
|
|
28
35
|
/**
|
|
29
36
|
* Storage per SQLite-backed Durable Object: 10 decimal GB documented
|
|
@@ -70,6 +77,29 @@ export const SQLITE_MAX_ROW_BYTES = 2_000_000;
|
|
|
70
77
|
* fabric defers async reconciliation off the gate entirely (`onColdStart`).
|
|
71
78
|
*/
|
|
72
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;
|
|
73
103
|
// ── WebSocket attachment bound ──────────────────────────────────────────
|
|
74
104
|
/**
|
|
75
105
|
* Bytes one hibernatable WebSocket attachment may serialize to. Verified in
|
package/dist/w7-frame.js
CHANGED
|
@@ -77,7 +77,7 @@ export async function decodeWriteBatchStream(stream, options = {}) {
|
|
|
77
77
|
catch {
|
|
78
78
|
throw new Error('w7-frame: stream must be a byte-oriented ReadableStream');
|
|
79
79
|
}
|
|
80
|
-
const buffer = new ExactByteReader(reader);
|
|
80
|
+
const buffer = new ExactByteReader(reader, options.signal);
|
|
81
81
|
let handedOff = false;
|
|
82
82
|
try {
|
|
83
83
|
throwIfAborted(options.signal);
|
|
@@ -796,9 +796,24 @@ async function cancelReader(reader, reason) {
|
|
|
796
796
|
}
|
|
797
797
|
class ExactByteReader {
|
|
798
798
|
reader;
|
|
799
|
+
signal;
|
|
799
800
|
done = false;
|
|
800
|
-
constructor(reader) {
|
|
801
|
+
constructor(reader, signal) {
|
|
801
802
|
this.reader = reader;
|
|
803
|
+
this.signal = signal;
|
|
804
|
+
}
|
|
805
|
+
async read(read, cancel) {
|
|
806
|
+
throwIfAborted(this.signal);
|
|
807
|
+
const abort = () => { void cancel().catch(() => { }); };
|
|
808
|
+
this.signal?.addEventListener('abort', abort, { once: true });
|
|
809
|
+
try {
|
|
810
|
+
const result = await read();
|
|
811
|
+
throwIfAborted(this.signal);
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
finally {
|
|
815
|
+
this.signal?.removeEventListener('abort', abort);
|
|
816
|
+
}
|
|
802
817
|
}
|
|
803
818
|
async readExact(length, label) {
|
|
804
819
|
if (length === 0)
|
|
@@ -809,7 +824,7 @@ class ExactByteReader {
|
|
|
809
824
|
if (this.done) {
|
|
810
825
|
throw new Error(`w7-frame: stream ended ${offset} bytes into expected ${length}-byte ${label}`);
|
|
811
826
|
}
|
|
812
|
-
const next = await this.reader.read(new Uint8Array(length - offset));
|
|
827
|
+
const next = await this.read(() => this.reader.read(new Uint8Array(length - offset)), () => this.reader.cancel(this.signal?.reason));
|
|
813
828
|
if (next.done)
|
|
814
829
|
this.done = true;
|
|
815
830
|
else if (next.value.byteLength > 0) {
|
|
@@ -825,7 +840,7 @@ class ExactByteReader {
|
|
|
825
840
|
this.reader.releaseLock();
|
|
826
841
|
const reader = stream.getReader();
|
|
827
842
|
try {
|
|
828
|
-
const next = await reader.read();
|
|
843
|
+
const next = await this.read(() => reader.read(), () => reader.cancel(this.signal?.reason));
|
|
829
844
|
if (!next.done && next.value.byteLength > 0) {
|
|
830
845
|
await reader.cancel(new Error('w7-frame: trailing bytes after batch-end'));
|
|
831
846
|
throw new Error('w7-frame: trailing bytes after batch-end');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbus-sh/platform",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The measured Cloudflare platform truths Nimbus is built on
|
|
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",
|
package/src/composition.ts
CHANGED
|
@@ -11,8 +11,18 @@
|
|
|
11
11
|
* stagedBootAssembler: (env, stage) => assembleConfig(env, stage),
|
|
12
12
|
* });
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
/**
|
|
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)
|
|
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) {
|
package/src/diag-counters.ts
CHANGED
|
@@ -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
|
package/src/heap-estimate.ts
CHANGED
|
@@ -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
|
@@ -28,6 +28,14 @@ export const MAX_TX_BLOB_BYTES = 1 * 1024 * 1024;
|
|
|
28
28
|
export const MAX_TX_LOGICAL_ROWS = 256;
|
|
29
29
|
export const MAX_TX_SQL_EXECS = 64;
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Bound parameters per SQL statement on Durable Objects SQLite. Measured
|
|
33
|
+
* 2026-09-21 on workerd: 100 bind, 101 fail as `too many SQL variables`
|
|
34
|
+
* (a 9-row insert of 12 columns fails, 8 rows succeed). Every IN-list and
|
|
35
|
+
* multi-row VALUES statement is sized from this.
|
|
36
|
+
*/
|
|
37
|
+
export const SQL_MAX_BOUND_PARAMETERS = 100;
|
|
38
|
+
|
|
31
39
|
// ── SQLite storage walls ────────────────────────────────────────────────
|
|
32
40
|
|
|
33
41
|
/**
|
|
@@ -81,6 +89,32 @@ export const SQLITE_MAX_ROW_BYTES = 2_000_000;
|
|
|
81
89
|
*/
|
|
82
90
|
export const BLOCK_CONCURRENCY_CANCEL_MS = 30_000;
|
|
83
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
|
+
|
|
84
118
|
// ── WebSocket attachment bound ──────────────────────────────────────────
|
|
85
119
|
|
|
86
120
|
/**
|
package/src/w7-frame.ts
CHANGED
|
@@ -220,7 +220,7 @@ export async function decodeWriteBatchStream(
|
|
|
220
220
|
} catch {
|
|
221
221
|
throw new Error('w7-frame: stream must be a byte-oriented ReadableStream');
|
|
222
222
|
}
|
|
223
|
-
const buffer = new ExactByteReader(reader);
|
|
223
|
+
const buffer = new ExactByteReader(reader, options.signal);
|
|
224
224
|
let handedOff = false;
|
|
225
225
|
try {
|
|
226
226
|
throwIfAborted(options.signal);
|
|
@@ -1006,7 +1006,20 @@ async function cancelReader(
|
|
|
1006
1006
|
class ExactByteReader {
|
|
1007
1007
|
private done = false;
|
|
1008
1008
|
|
|
1009
|
-
constructor(private readonly reader: ReadableStreamBYOBReader) {}
|
|
1009
|
+
constructor(private readonly reader: ReadableStreamBYOBReader, private readonly signal?: AbortSignal) {}
|
|
1010
|
+
|
|
1011
|
+
private async read<T>(read: () => Promise<T>, cancel: () => Promise<void>): Promise<T> {
|
|
1012
|
+
throwIfAborted(this.signal);
|
|
1013
|
+
const abort = () => { void cancel().catch(() => {}); };
|
|
1014
|
+
this.signal?.addEventListener('abort', abort, { once: true });
|
|
1015
|
+
try {
|
|
1016
|
+
const result = await read();
|
|
1017
|
+
throwIfAborted(this.signal);
|
|
1018
|
+
return result;
|
|
1019
|
+
} finally {
|
|
1020
|
+
this.signal?.removeEventListener('abort', abort);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1010
1023
|
|
|
1011
1024
|
async readExact(length: number, label: string): Promise<Uint8Array> {
|
|
1012
1025
|
if (length === 0) return new Uint8Array(0);
|
|
@@ -1018,7 +1031,10 @@ class ExactByteReader {
|
|
|
1018
1031
|
`w7-frame: stream ended ${offset} bytes into expected ${length}-byte ${label}`,
|
|
1019
1032
|
);
|
|
1020
1033
|
}
|
|
1021
|
-
const next = await this.
|
|
1034
|
+
const next = await this.read(
|
|
1035
|
+
() => this.reader.read(new Uint8Array(length - offset)),
|
|
1036
|
+
() => this.reader.cancel(this.signal?.reason),
|
|
1037
|
+
);
|
|
1022
1038
|
if (next.done) this.done = true;
|
|
1023
1039
|
else if (next.value.byteLength > 0) {
|
|
1024
1040
|
output.set(next.value, offset);
|
|
@@ -1033,7 +1049,7 @@ class ExactByteReader {
|
|
|
1033
1049
|
this.reader.releaseLock();
|
|
1034
1050
|
const reader = stream.getReader();
|
|
1035
1051
|
try {
|
|
1036
|
-
const next = await reader.read();
|
|
1052
|
+
const next = await this.read(() => reader.read(), () => reader.cancel(this.signal?.reason));
|
|
1037
1053
|
if (!next.done && next.value.byteLength > 0) {
|
|
1038
1054
|
await reader.cancel(new Error('w7-frame: trailing bytes after batch-end'));
|
|
1039
1055
|
throw new Error('w7-frame: trailing bytes after batch-end');
|