@nimbus-sh/platform 0.5.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.
- package/dist/composition.d.ts +40 -4
- package/dist/composition.d.ts.map +1 -1
- package/dist/composition.js +38 -4
- package/dist/crc32.d.ts +19 -0
- package/dist/crc32.d.ts.map +1 -0
- package/dist/crc32.js +67 -0
- 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 +7 -1
- package/dist/heap-estimate.d.ts.map +1 -1
- package/dist/heap-estimate.js +3 -1
- package/dist/limits.d.ts +25 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +51 -0
- package/dist/oom-classify.d.ts +1 -1
- package/dist/oom-classify.d.ts.map +1 -1
- package/dist/oom-classify.js +2 -1
- package/dist/w7-frame.d.ts.map +1 -1
- package/dist/w7-frame.js +12 -35
- package/package.json +4 -4
- package/src/composition.ts +67 -6
- package/src/crc32.ts +71 -0
- package/src/diag-counters.ts +13 -0
- package/src/heap-estimate.ts +9 -1
- package/src/limits.ts +57 -0
- package/src/oom-classify.ts +2 -1
- package/src/w7-frame.ts +12 -38
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/crc32.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRC-32 (IEEE 802.3, the zip/gzip/PNG polynomial), the one implementation
|
|
3
|
+
* every Nimbus caller uses: the W7 write-batch framing and zip archives.
|
|
4
|
+
*
|
|
5
|
+
* `crc32(bytes, previous)` has node:zlib's contract: `previous` is the
|
|
6
|
+
* finished CRC of the bytes before these, so a checksum computed in pieces
|
|
7
|
+
* equals the checksum of the whole.
|
|
8
|
+
*
|
|
9
|
+
* Measured 2026-09-24 (5k files x 2 KiB; 64 MiB in 1 MiB buffers):
|
|
10
|
+
* node:zlib's crc32 is 5-40x faster than any JavaScript loop in bun, node and
|
|
11
|
+
* workerd (compat 2026-04-01, nodejs_compat), but each call costs ~0.14 us in
|
|
12
|
+
* workerd, so below 128 bytes the table loop wins there. It is reached through
|
|
13
|
+
* `process.getBuiltinModule`, like core's incremental SHA-256, so this module
|
|
14
|
+
* imports nothing and still loads where node:zlib is absent (a browser,
|
|
15
|
+
* workerd without nodejs_compat). The JavaScript path is slicing-by-8: 2-3x
|
|
16
|
+
* the byte-at-a-time table loop in bun and node, and level with it in workerd.
|
|
17
|
+
*/
|
|
18
|
+
export declare function crc32(bytes: Uint8Array, previous?: number): number;
|
|
19
|
+
//# sourceMappingURL=crc32.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crc32.d.ts","sourceRoot":"","sources":["../src/crc32.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAqCH,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,SAAI,GAAG,MAAM,CAiB7D"}
|
package/dist/crc32.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRC-32 (IEEE 802.3, the zip/gzip/PNG polynomial), the one implementation
|
|
3
|
+
* every Nimbus caller uses: the W7 write-batch framing and zip archives.
|
|
4
|
+
*
|
|
5
|
+
* `crc32(bytes, previous)` has node:zlib's contract: `previous` is the
|
|
6
|
+
* finished CRC of the bytes before these, so a checksum computed in pieces
|
|
7
|
+
* equals the checksum of the whole.
|
|
8
|
+
*
|
|
9
|
+
* Measured 2026-09-24 (5k files x 2 KiB; 64 MiB in 1 MiB buffers):
|
|
10
|
+
* node:zlib's crc32 is 5-40x faster than any JavaScript loop in bun, node and
|
|
11
|
+
* workerd (compat 2026-04-01, nodejs_compat), but each call costs ~0.14 us in
|
|
12
|
+
* workerd, so below 128 bytes the table loop wins there. It is reached through
|
|
13
|
+
* `process.getBuiltinModule`, like core's incremental SHA-256, so this module
|
|
14
|
+
* imports nothing and still loads where node:zlib is absent (a browser,
|
|
15
|
+
* workerd without nodejs_compat). The JavaScript path is slicing-by-8: 2-3x
|
|
16
|
+
* the byte-at-a-time table loop in bun and node, and level with it in workerd.
|
|
17
|
+
*/
|
|
18
|
+
/** Below this many bytes the native call's overhead exceeds the JavaScript loop's work (workerd). */
|
|
19
|
+
const CRC_NATIVE_MIN_BYTES = 128;
|
|
20
|
+
const nativeCrc32 = (() => {
|
|
21
|
+
try {
|
|
22
|
+
const crc32 = globalThis.process?.getBuiltinModule?.('node:zlib')?.crc32;
|
|
23
|
+
return typeof crc32 === 'function' ? crc32 : null;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
29
|
+
/** Eight 256-entry tables: table k maps a byte to its CRC after k further zero bytes. */
|
|
30
|
+
let crcTables = null;
|
|
31
|
+
function createCrcTables() {
|
|
32
|
+
const table = new Uint32Array(256 * 8);
|
|
33
|
+
for (let index = 0; index < 256; index++) {
|
|
34
|
+
let value = index;
|
|
35
|
+
for (let bit = 0; bit < 8; bit++)
|
|
36
|
+
value = (value & 1) !== 0 ? 0xedb8_8320 ^ (value >>> 1) : value >>> 1;
|
|
37
|
+
table[index] = value;
|
|
38
|
+
}
|
|
39
|
+
for (let index = 0; index < 256; index++) {
|
|
40
|
+
let value = table[index];
|
|
41
|
+
for (let k = 1; k < 8; k++) {
|
|
42
|
+
value = table[value & 0xff] ^ (value >>> 8);
|
|
43
|
+
table[k * 256 + index] = value;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return table;
|
|
47
|
+
}
|
|
48
|
+
export function crc32(bytes, previous = 0) {
|
|
49
|
+
if (nativeCrc32 !== null && bytes.length >= CRC_NATIVE_MIN_BYTES)
|
|
50
|
+
return nativeCrc32(bytes, previous) >>> 0;
|
|
51
|
+
const t = crcTables ??= createCrcTables();
|
|
52
|
+
let value = ~previous;
|
|
53
|
+
const length = bytes.length;
|
|
54
|
+
const whole = length - (length & 7);
|
|
55
|
+
let i = 0;
|
|
56
|
+
for (; i < whole; i += 8) {
|
|
57
|
+
const low = value ^ (bytes[i] | (bytes[i + 1] << 8) | (bytes[i + 2] << 16) | (bytes[i + 3] << 24));
|
|
58
|
+
const high = bytes[i + 4] | (bytes[i + 5] << 8) | (bytes[i + 6] << 16) | (bytes[i + 7] << 24);
|
|
59
|
+
value = t[1792 + (low & 0xff)] ^ t[1536 + ((low >>> 8) & 0xff)]
|
|
60
|
+
^ t[1280 + ((low >>> 16) & 0xff)] ^ t[1024 + (low >>> 24)]
|
|
61
|
+
^ t[768 + (high & 0xff)] ^ t[512 + ((high >>> 8) & 0xff)]
|
|
62
|
+
^ t[256 + ((high >>> 16) & 0xff)] ^ t[high >>> 24];
|
|
63
|
+
}
|
|
64
|
+
for (; i < length; i++)
|
|
65
|
+
value = t[(value ^ bytes[i]) & 0xff] ^ (value >>> 8);
|
|
66
|
+
return ~value >>> 0;
|
|
67
|
+
}
|
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":"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;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;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;AA6BD,0EAA0E;AAC1E,wBAAgB,gBAAgB,IAAI,YAAY,CAE/C;AAED,6BAA6B;AAC7B,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,IAAI,CAErD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAInD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAOjD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIvD;AAED,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAKrD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAGzD;AAED,iFAAiF;AACjF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG1D;AAED;;;qDAGqD;AACrD,wBAAgB,0BAA0B,CAAC,CAAC,EAAE;IAC5C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,IAAI,CAMP;AAED;;;;;aAKa;AACb,wBAAgB,sBAAsB,CAAC,CAAC,EAAE;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC,GAAG,IAAI,CAiBP;AAED;;sCAEsC;AACtC,wBAAgB,oBAAoB,CAAC,CAAC,EAAE;IACtC,wBAAwB,EAAE,MAAM,CAAC;IACjC,0BAA0B,EAAE,MAAM,CAAC;IACnC,0BAA0B,EAAE,MAAM,CAAC;IACnC,4BAA4B,EAAE,MAAM,CAAC;CACtC,GAAG,IAAI,CAKP"}
|
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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `process.memoryUsage()` returns 0 for every field inside a Durable
|
|
7
7
|
* Object class context (only dynamic-worker isolates under nodejs_compat
|
|
8
8
|
* get the real implementation). The previous `readNodeMem` /
|
|
9
|
-
* `sampleMemory` helpers in
|
|
9
|
+
* `sampleMemory` helpers in session/diag.ts called it anyway and
|
|
10
10
|
* therefore reported zero forever — useless for verifying memory-
|
|
11
11
|
* containment work in plan §3 Track A'.
|
|
12
12
|
*
|
|
@@ -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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `process.memoryUsage()` returns 0 for every field inside a Durable
|
|
7
7
|
* Object class context (only dynamic-worker isolates under nodejs_compat
|
|
8
8
|
* get the real implementation). The previous `readNodeMem` /
|
|
9
|
-
* `sampleMemory` helpers in
|
|
9
|
+
* `sampleMemory` helpers in session/diag.ts called it anyway and
|
|
10
10
|
* therefore reported zero forever — useless for verifying memory-
|
|
11
11
|
* containment work in plan §3 Track A'.
|
|
12
12
|
*
|
|
@@ -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
|
@@ -38,6 +38,7 @@ export declare const SQL_MAX_BOUND_PARAMETERS = 100;
|
|
|
38
38
|
* clone, with reserve.
|
|
39
39
|
*/
|
|
40
40
|
export declare const DO_STORAGE_LIMIT_BYTES = 10000000000;
|
|
41
|
+
export declare const HYDRATION_DEADLINE_MS = 30000;
|
|
41
42
|
/**
|
|
42
43
|
* Maximum SQL statement text per exec. Documented "100 KB"; read as binary
|
|
43
44
|
* KiB because the value is SQLite's compile-time SQLITE_MAX_SQL_LENGTH
|
|
@@ -69,6 +70,28 @@ export declare const SQLITE_MAX_ROW_BYTES = 2000000;
|
|
|
69
70
|
* fabric defers async reconciliation off the gate entirely (`onColdStart`).
|
|
70
71
|
*/
|
|
71
72
|
export declare const BLOCK_CONCURRENCY_CANCEL_MS = 30000;
|
|
73
|
+
/**
|
|
74
|
+
* Cadence of the alarm that keeps a session Durable Object in memory while
|
|
75
|
+
* a resident process runs. The platform evicts an object after roughly ten
|
|
76
|
+
* seconds with no in-flight event; a pending promise (`ctx.waitUntil`) is
|
|
77
|
+
* not one, and facets die with their parent. Measured 2026-09-21 on local
|
|
78
|
+
* workerd and on Cloudflare: a quiet resident process survived 8 s of
|
|
79
|
+
* silence and was re-driven under a new pid namespace after 12 s.
|
|
80
|
+
*/
|
|
81
|
+
export declare const RESIDENT_KEEPALIVE_MS = 5000;
|
|
82
|
+
/**
|
|
83
|
+
* How long the keep-alive holds a session after its last client is gone.
|
|
84
|
+
*
|
|
85
|
+
* The keep-alive exists so a quiet process survives while its user is
|
|
86
|
+
* present. Unbounded, it made every abandoned session with a dev server
|
|
87
|
+
* run forever: the probe suite's cancelled runs left staging with resident
|
|
88
|
+
* watchers polling `stat` at ~7.5/s in the isolate every session shares,
|
|
89
|
+
* and launches that pass on a fresh Worker reset there (2026-09-22). Past
|
|
90
|
+
* this grace with no attached socket and no request, the session idles
|
|
91
|
+
* out as it did before the keep-alive, and the launch journal re-drives
|
|
92
|
+
* the process when a client returns. Long enough for a tab reload.
|
|
93
|
+
*/
|
|
94
|
+
export declare const RESIDENT_KEEPALIVE_DETACHED_MS = 60000;
|
|
72
95
|
/**
|
|
73
96
|
* Bytes one hibernatable WebSocket attachment may serialize to. Verified in
|
|
74
97
|
* workerd source at the pinned version (v1.20260603.1,
|
|
@@ -81,7 +104,9 @@ export declare const BLOCK_CONCURRENCY_CANCEL_MS = 30000;
|
|
|
81
104
|
*/
|
|
82
105
|
export declare const WS_ATTACHMENT_LIMIT_BYTES = 16384;
|
|
83
106
|
export declare const MAX_RPC_SAFE_PAYLOAD_BYTES: number;
|
|
107
|
+
export declare const ONE_SHOT_MODULE_MAP_MAX_BYTES: number;
|
|
84
108
|
export declare const SUPERVISOR_HEAP_CEILING_BYTES: number;
|
|
109
|
+
export declare const FACET_OWN_WRITE_MEMORY_BYTES: number;
|
|
85
110
|
export declare const SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES: number;
|
|
86
111
|
export declare const SUPERVISOR_READ_RESERVE_BYTES: number;
|
|
87
112
|
export declare const MAX_GLOBAL_WRITE_STREAM_CREDIT_BYTES: number;
|
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;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAI5C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,cAAiB,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;AAMrD,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C;;;;;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;AAmB3D,eAAO,MAAM,6BAA6B,QAAmB,CAAC;AA6B9D,eAAO,MAAM,6BAA6B,QAAmB,CAAC;AAM9D,eAAO,MAAM,4BAA4B,QAAmB,CAAC;AAO7D,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
|
@@ -45,6 +45,11 @@ export const SQL_MAX_BOUND_PARAMETERS = 100;
|
|
|
45
45
|
* clone, with reserve.
|
|
46
46
|
*/
|
|
47
47
|
export const DO_STORAGE_LIMIT_BYTES = 10_000_000_000;
|
|
48
|
+
// N17: how long a launch that reads synchronously (WASI) waits for the paths
|
|
49
|
+
// it names to hydrate out of a lazy import before it fails with EIO naming
|
|
50
|
+
// the first that is not local. Bounded so a fetch that never resolves cannot
|
|
51
|
+
// hold a launch forever.
|
|
52
|
+
export const HYDRATION_DEADLINE_MS = 30_000;
|
|
48
53
|
/**
|
|
49
54
|
* Maximum SQL statement text per exec. Documented "100 KB"; read as binary
|
|
50
55
|
* KiB because the value is SQLite's compile-time SQLITE_MAX_SQL_LENGTH
|
|
@@ -77,6 +82,29 @@ export const SQLITE_MAX_ROW_BYTES = 2_000_000;
|
|
|
77
82
|
* fabric defers async reconciliation off the gate entirely (`onColdStart`).
|
|
78
83
|
*/
|
|
79
84
|
export const BLOCK_CONCURRENCY_CANCEL_MS = 30_000;
|
|
85
|
+
// ── Eviction window ─────────────────────────────────────────────────────
|
|
86
|
+
/**
|
|
87
|
+
* Cadence of the alarm that keeps a session Durable Object in memory while
|
|
88
|
+
* a resident process runs. The platform evicts an object after roughly ten
|
|
89
|
+
* seconds with no in-flight event; a pending promise (`ctx.waitUntil`) is
|
|
90
|
+
* not one, and facets die with their parent. Measured 2026-09-21 on local
|
|
91
|
+
* workerd and on Cloudflare: a quiet resident process survived 8 s of
|
|
92
|
+
* silence and was re-driven under a new pid namespace after 12 s.
|
|
93
|
+
*/
|
|
94
|
+
export const RESIDENT_KEEPALIVE_MS = 5_000;
|
|
95
|
+
/**
|
|
96
|
+
* How long the keep-alive holds a session after its last client is gone.
|
|
97
|
+
*
|
|
98
|
+
* The keep-alive exists so a quiet process survives while its user is
|
|
99
|
+
* present. Unbounded, it made every abandoned session with a dev server
|
|
100
|
+
* run forever: the probe suite's cancelled runs left staging with resident
|
|
101
|
+
* watchers polling `stat` at ~7.5/s in the isolate every session shares,
|
|
102
|
+
* and launches that pass on a fresh Worker reset there (2026-09-22). Past
|
|
103
|
+
* this grace with no attached socket and no request, the session idles
|
|
104
|
+
* out as it did before the keep-alive, and the launch journal re-drives
|
|
105
|
+
* the process when a client returns. Long enough for a tab reload.
|
|
106
|
+
*/
|
|
107
|
+
export const RESIDENT_KEEPALIVE_DETACHED_MS = 60_000;
|
|
80
108
|
// ── WebSocket attachment bound ──────────────────────────────────────────
|
|
81
109
|
/**
|
|
82
110
|
* Bytes one hibernatable WebSocket attachment may serialize to. Verified in
|
|
@@ -94,6 +122,24 @@ export const WS_ATTACHMENT_LIMIT_BYTES = 16_384;
|
|
|
94
122
|
// The platform limit is 32 MiB; 28 MiB leaves room for structured-clone
|
|
95
123
|
// metadata and matches the proven on-demand facet transfer envelope.
|
|
96
124
|
export const MAX_RPC_SAFE_PAYLOAD_BYTES = 28 * 1024 * 1024;
|
|
125
|
+
// ── A one-shot's module map [#13] ───────────────────────────────────────
|
|
126
|
+
//
|
|
127
|
+
// The largest one-shot module map (its closure's raw bytes) the session DO
|
|
128
|
+
// builds and hands to the Worker Loader. What limits it is the DO, not the
|
|
129
|
+
// loader (whose documented limits are concurrency only): the map is built,
|
|
130
|
+
// serialized and passed on from the DO's 128 MB isolate.
|
|
131
|
+
//
|
|
132
|
+
// Measured on a throwaway (2026-09-26; session DO max memoryUsageBytes from
|
|
133
|
+
// GraphQL durableObjectsPeriodicGroups, one launch per minute; a package of
|
|
134
|
+
// 256 KiB distinct modules required whole, verify/release/do-headroom.md),
|
|
135
|
+
// with the map serialized one side module at a time:
|
|
136
|
+
// 4 MiB 64 MB, 8 MiB 81 MB, 12 MiB 97 MB, 16 MiB 110 MB, 20 MiB 126 MB,
|
|
137
|
+
// 24 MiB 145 MB, 28 MiB 164 MB, over an idle 40-55 MB.
|
|
138
|
+
// That is about 4.5 MB of DO per MiB of map. 18 MiB, about 118 MB, is the
|
|
139
|
+
// largest size that leaves about 10 MB of the 128 MB for the rest of the
|
|
140
|
+
// session's work. (Before the single-copy build it was 6-7 MB per MiB: 24 MiB
|
|
141
|
+
// reset the DO.)
|
|
142
|
+
export const ONE_SHOT_MODULE_MAP_MAX_BYTES = 18 * 1024 * 1024;
|
|
97
143
|
// ── Supervisor heap budget [C'.1] ───────────────────────────────────────
|
|
98
144
|
//
|
|
99
145
|
// Three distinct memory regimes, not one shared pool:
|
|
@@ -122,6 +168,11 @@ export const MAX_RPC_SAFE_PAYLOAD_BYTES = 28 * 1024 * 1024;
|
|
|
122
168
|
// sites known to be missing are listed in HEAP_BLIND_SPOTS. Read a low
|
|
123
169
|
// percentOfCeiling accordingly.
|
|
124
170
|
export const SUPERVISOR_HEAP_CEILING_BYTES = 64 * 1024 * 1024;
|
|
171
|
+
// A process facet's own writes its store could not hold (N18: the session's
|
|
172
|
+
// storage is full) are kept in its heap instead, so a write-then-read still
|
|
173
|
+
// works: at most this many bytes, least recently used out first. An eighth
|
|
174
|
+
// of a facet's measured ~208-256 MiB, the rest left to the program.
|
|
175
|
+
export const FACET_OWN_WRITE_MEMORY_BYTES = 32 * 1024 * 1024;
|
|
125
176
|
// Shared allowance for transient allocations in the supervisor DO. With the
|
|
126
177
|
// VFS LRU shrunk to 8 MiB during an active reservation, 40 MiB of admitted
|
|
127
178
|
// payload plus the 9 MiB bundle baseline stays below the 64 MiB soft ceiling
|
package/dist/oom-classify.d.ts
CHANGED
|
@@ -123,7 +123,7 @@ export declare function isTransientDoReset(input: unknown): boolean;
|
|
|
123
123
|
export declare function isDoOverloaded(input: unknown): boolean;
|
|
124
124
|
/**
|
|
125
125
|
* How one failed Durable Object call relates to a retry. The taxonomy
|
|
126
|
-
* Proteus hand-wrote in `cf-backend/src/lib/do-rpc.ts
|
|
126
|
+
* Proteus originally hand-wrote in `cf-backend/src/lib/do-rpc.ts` because the
|
|
127
127
|
* Agents SDK does not export its own, plus the `overloaded` class both
|
|
128
128
|
* consumers need: Cloudflare documents that errors carry `.retryable` and
|
|
129
129
|
* `.overloaded`, that a retryable error should be retried with backoff on a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oom-classify.d.ts","sourceRoot":"","sources":["../src/oom-classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,QAAA,MAAM,UAAU,kLAYN,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAEjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAItD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAkErD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAO1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW;AACrB,8CAA8C;AAC5C,oBAAoB;AACtB,8CAA8C;GAC5C,iBAAiB;AACnB,uEAAuE;GACrE,eAAe;AACjB,wDAAwD;GACtD,gBAAgB;AAClB;kDACkD;GAChD,YAAY;AACd,kEAAkE;GAChE,WAAW,CAAC;AAEhB;2DAC2D;AAC3D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAK7D;AAmBD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAsB1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"oom-classify.d.ts","sourceRoot":"","sources":["../src/oom-classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,QAAA,MAAM,UAAU,kLAYN,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAEjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAItD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAkErD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAO1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW;AACrB,8CAA8C;AAC5C,oBAAoB;AACtB,8CAA8C;GAC5C,iBAAiB;AACnB,uEAAuE;GACrE,eAAe;AACjB,wDAAwD;GACtD,gBAAgB;AAClB;kDACkD;GAChD,YAAY;AACd,kEAAkE;GAChE,WAAW,CAAC;AAEhB;2DAC2D;AAC3D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAK7D;AAmBD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAsB1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiBpD"}
|
package/dist/oom-classify.js
CHANGED
|
@@ -311,7 +311,8 @@ export function describeError(input) {
|
|
|
311
311
|
const suffix = cause !== 'unknown' ? ` [${cause}]`
|
|
312
312
|
: isTransientDoReset(input) ? ' [transient-do-reset]'
|
|
313
313
|
: isDoOverloaded(input) ? ' [do-overloaded]'
|
|
314
|
-
: ''
|
|
314
|
+
: classifyDoCall(input) === 'retryable_flag' ? ' [retryable]'
|
|
315
|
+
: '';
|
|
315
316
|
return `${name}${message}${suffix}`;
|
|
316
317
|
}
|
|
317
318
|
function readMessage(input) {
|
package/dist/w7-frame.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"w7-frame.d.ts","sourceRoot":"","sources":["../src/w7-frame.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"w7-frame.d.ts","sourceRoot":"","sources":["../src/w7-frame.ts"],"names":[],"mappings":"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"}
|
package/dist/w7-frame.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* W7 v3 — incremental typed records for streamed bulk filesystem writes.
|
|
3
3
|
* The format is internal: every producer and consumer deploys together.
|
|
4
4
|
*/
|
|
5
|
+
import { crc32 } from './crc32.js';
|
|
5
6
|
import { CHUNK_SIZE } from './limits.js';
|
|
6
7
|
export const W7_MAGIC = new Uint8Array([0x4e, 0x57, 0x37, 0x03]);
|
|
7
8
|
const ENCODER_QUEUE_HWM = 0;
|
|
@@ -101,7 +102,7 @@ export async function decodeWriteBatchStream(stream, options = {}) {
|
|
|
101
102
|
}
|
|
102
103
|
const beginPayload = await buffer.readExact(beginEnvelope.length, 'batch-begin payload');
|
|
103
104
|
const begin = parseBatchBegin(beginPayload);
|
|
104
|
-
const initialCheck = updateRecordCheck(
|
|
105
|
+
const initialCheck = updateRecordCheck(0, beginEnvelope.header, beginPayload);
|
|
105
106
|
handedOff = true;
|
|
106
107
|
return {
|
|
107
108
|
batchId: begin.id,
|
|
@@ -180,7 +181,7 @@ async function* decodeRecords(stream, reader, buffer, options, initialCheck) {
|
|
|
180
181
|
throwIfAborted(options.signal);
|
|
181
182
|
const data = await buffer.readExact(dataLength, 'file-chunk data');
|
|
182
183
|
batchCheck = updateRecordCheck(batchCheck, envelope.header, headerPayload, data);
|
|
183
|
-
active.check =
|
|
184
|
+
active.check = crc32(data, active.check);
|
|
184
185
|
active.nextChunkId++;
|
|
185
186
|
active.receivedBytes += dataLength;
|
|
186
187
|
summary.recordCount++;
|
|
@@ -242,7 +243,7 @@ async function* decodeRecords(stream, reader, buffer, options, initialCheck) {
|
|
|
242
243
|
inode,
|
|
243
244
|
nextChunkId: 0,
|
|
244
245
|
receivedBytes: 0,
|
|
245
|
-
check:
|
|
246
|
+
check: 0,
|
|
246
247
|
};
|
|
247
248
|
yield { type: 'file-begin', streamContentId: metadata.contentId, inode };
|
|
248
249
|
break;
|
|
@@ -251,7 +252,7 @@ async function* decodeRecords(stream, reader, buffer, options, initialCheck) {
|
|
|
251
252
|
if (!active)
|
|
252
253
|
throw new Error('w7-frame: file-end without active file');
|
|
253
254
|
const metadata = parseFileEnd(payload);
|
|
254
|
-
const actualCheck =
|
|
255
|
+
const actualCheck = active.check;
|
|
255
256
|
if (metadata.contentId !== active.metadata.contentId) {
|
|
256
257
|
throw new Error(`w7-frame: file-end content id mismatch for ${active.inode.path}`);
|
|
257
258
|
}
|
|
@@ -281,7 +282,7 @@ async function* decodeRecords(stream, reader, buffer, options, initialCheck) {
|
|
|
281
282
|
if (active)
|
|
282
283
|
throw new Error(`w7-frame: batch-end while file ${active.inode.path} is active`);
|
|
283
284
|
const actual = parseBatchEnd(payload);
|
|
284
|
-
const expected = { ...summary, check:
|
|
285
|
+
const expected = { ...summary, check: batchCheck };
|
|
285
286
|
if (!sameSummary(actual, expected)) {
|
|
286
287
|
throw new Error(`w7-frame: batch-end summary mismatch; expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
287
288
|
}
|
|
@@ -315,7 +316,7 @@ async function* decodeRecords(stream, reader, buffer, options, initialCheck) {
|
|
|
315
316
|
}
|
|
316
317
|
function* encodeRecords(batchId, deletes, directories, files) {
|
|
317
318
|
const state = {
|
|
318
|
-
batchCheck:
|
|
319
|
+
batchCheck: 0,
|
|
319
320
|
summary: {
|
|
320
321
|
recordCount: 0,
|
|
321
322
|
pathCount: 0,
|
|
@@ -350,7 +351,7 @@ function* encodeRecords(batchId, deletes, directories, files) {
|
|
|
350
351
|
size: file.inode.size,
|
|
351
352
|
chunkCount: file.inode.chunkCount,
|
|
352
353
|
}, state);
|
|
353
|
-
let fileCheck =
|
|
354
|
+
let fileCheck = 0;
|
|
354
355
|
for (const chunk of file.chunks) {
|
|
355
356
|
const data = chunk.data;
|
|
356
357
|
const contentBytes = new TextEncoder().encode(file.contentId);
|
|
@@ -364,19 +365,19 @@ function* encodeRecords(batchId, deletes, directories, files) {
|
|
|
364
365
|
state.summary.recordCount++;
|
|
365
366
|
state.summary.chunkCount++;
|
|
366
367
|
state.summary.byteCount += data.byteLength;
|
|
367
|
-
fileCheck =
|
|
368
|
+
fileCheck = crc32(data, fileCheck);
|
|
368
369
|
yield [concatBytes(header, prefix), data];
|
|
369
370
|
}
|
|
370
371
|
yield encodeMetadataRecord(RecordTag.FileEnd, {
|
|
371
372
|
contentId: file.contentId,
|
|
372
373
|
size: file.inode.size,
|
|
373
374
|
chunkCount: file.inode.chunkCount,
|
|
374
|
-
check:
|
|
375
|
+
check: fileCheck,
|
|
375
376
|
}, state);
|
|
376
377
|
}
|
|
377
378
|
const end = {
|
|
378
379
|
...state.summary,
|
|
379
|
-
check:
|
|
380
|
+
check: state.batchCheck,
|
|
380
381
|
};
|
|
381
382
|
yield encodeMetadataRecord(RecordTag.BatchEnd, end);
|
|
382
383
|
}
|
|
@@ -718,33 +719,9 @@ function readU32LE(bytes, offset) {
|
|
|
718
719
|
function updateRecordCheck(seed, ...parts) {
|
|
719
720
|
let check = seed;
|
|
720
721
|
for (const part of parts)
|
|
721
|
-
check =
|
|
722
|
+
check = crc32(part, check);
|
|
722
723
|
return check;
|
|
723
724
|
}
|
|
724
|
-
const CRC_SEED = 0xffff_ffff;
|
|
725
|
-
let crcTable = null;
|
|
726
|
-
function crc32Update(check, bytes) {
|
|
727
|
-
crcTable ??= createCrcTable();
|
|
728
|
-
const table = crcTable;
|
|
729
|
-
let value = check;
|
|
730
|
-
for (const byte of bytes)
|
|
731
|
-
value = table[(value ^ byte) & 0xff] ^ (value >>> 8);
|
|
732
|
-
return value >>> 0;
|
|
733
|
-
}
|
|
734
|
-
function crc32Finish(check) {
|
|
735
|
-
return (check ^ 0xffff_ffff) >>> 0;
|
|
736
|
-
}
|
|
737
|
-
function createCrcTable() {
|
|
738
|
-
const table = new Uint32Array(256);
|
|
739
|
-
for (let index = 0; index < table.length; index++) {
|
|
740
|
-
let value = index;
|
|
741
|
-
for (let bit = 0; bit < 8; bit++) {
|
|
742
|
-
value = (value & 1) !== 0 ? 0xedb8_8320 ^ (value >>> 1) : value >>> 1;
|
|
743
|
-
}
|
|
744
|
-
table[index] = value >>> 0;
|
|
745
|
-
}
|
|
746
|
-
return table;
|
|
747
|
-
}
|
|
748
725
|
function noopRetention(bytes) {
|
|
749
726
|
return { bytes, release() { } };
|
|
750
727
|
}
|
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.6.0",
|
|
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",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"LICENSE"
|
|
44
44
|
],
|
|
45
45
|
"scripts": {
|
|
46
|
-
"build": "tsc -p tsconfig.json --noCheck --noEmit false --declaration true --declarationMap true --outDir dist --rootDir src",
|
|
47
|
-
"
|
|
46
|
+
"build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.json --noCheck --noEmit false --declaration true --declarationMap true --outDir dist --rootDir src",
|
|
47
|
+
"prepublishOnly": "bun ../../scripts/dist-integrity.mjs --publish",
|
|
48
48
|
"typecheck": "tsc --noEmit"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
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/crc32.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRC-32 (IEEE 802.3, the zip/gzip/PNG polynomial), the one implementation
|
|
3
|
+
* every Nimbus caller uses: the W7 write-batch framing and zip archives.
|
|
4
|
+
*
|
|
5
|
+
* `crc32(bytes, previous)` has node:zlib's contract: `previous` is the
|
|
6
|
+
* finished CRC of the bytes before these, so a checksum computed in pieces
|
|
7
|
+
* equals the checksum of the whole.
|
|
8
|
+
*
|
|
9
|
+
* Measured 2026-09-24 (5k files x 2 KiB; 64 MiB in 1 MiB buffers):
|
|
10
|
+
* node:zlib's crc32 is 5-40x faster than any JavaScript loop in bun, node and
|
|
11
|
+
* workerd (compat 2026-04-01, nodejs_compat), but each call costs ~0.14 us in
|
|
12
|
+
* workerd, so below 128 bytes the table loop wins there. It is reached through
|
|
13
|
+
* `process.getBuiltinModule`, like core's incremental SHA-256, so this module
|
|
14
|
+
* imports nothing and still loads where node:zlib is absent (a browser,
|
|
15
|
+
* workerd without nodejs_compat). The JavaScript path is slicing-by-8: 2-3x
|
|
16
|
+
* the byte-at-a-time table loop in bun and node, and level with it in workerd.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type NodeZlib = { crc32?(data: Uint8Array, value?: number): number };
|
|
20
|
+
type BuiltinModules = { process?: { getBuiltinModule?(id: 'node:zlib'): NodeZlib | undefined } };
|
|
21
|
+
|
|
22
|
+
/** Below this many bytes the native call's overhead exceeds the JavaScript loop's work (workerd). */
|
|
23
|
+
const CRC_NATIVE_MIN_BYTES = 128;
|
|
24
|
+
|
|
25
|
+
const nativeCrc32 = (() => {
|
|
26
|
+
try {
|
|
27
|
+
const crc32 = (globalThis as BuiltinModules).process?.getBuiltinModule?.('node:zlib')?.crc32;
|
|
28
|
+
return typeof crc32 === 'function' ? crc32 : null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
})();
|
|
33
|
+
|
|
34
|
+
/** Eight 256-entry tables: table k maps a byte to its CRC after k further zero bytes. */
|
|
35
|
+
let crcTables: Uint32Array | null = null;
|
|
36
|
+
|
|
37
|
+
function createCrcTables(): Uint32Array {
|
|
38
|
+
const table = new Uint32Array(256 * 8);
|
|
39
|
+
for (let index = 0; index < 256; index++) {
|
|
40
|
+
let value = index;
|
|
41
|
+
for (let bit = 0; bit < 8; bit++) value = (value & 1) !== 0 ? 0xedb8_8320 ^ (value >>> 1) : value >>> 1;
|
|
42
|
+
table[index] = value;
|
|
43
|
+
}
|
|
44
|
+
for (let index = 0; index < 256; index++) {
|
|
45
|
+
let value = table[index];
|
|
46
|
+
for (let k = 1; k < 8; k++) {
|
|
47
|
+
value = table[value & 0xff] ^ (value >>> 8);
|
|
48
|
+
table[k * 256 + index] = value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return table;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function crc32(bytes: Uint8Array, previous = 0): number {
|
|
55
|
+
if (nativeCrc32 !== null && bytes.length >= CRC_NATIVE_MIN_BYTES) return nativeCrc32(bytes, previous) >>> 0;
|
|
56
|
+
const t = crcTables ??= createCrcTables();
|
|
57
|
+
let value = ~previous;
|
|
58
|
+
const length = bytes.length;
|
|
59
|
+
const whole = length - (length & 7);
|
|
60
|
+
let i = 0;
|
|
61
|
+
for (; i < whole; i += 8) {
|
|
62
|
+
const low = value ^ (bytes[i] | (bytes[i + 1] << 8) | (bytes[i + 2] << 16) | (bytes[i + 3] << 24));
|
|
63
|
+
const high = bytes[i + 4] | (bytes[i + 5] << 8) | (bytes[i + 6] << 16) | (bytes[i + 7] << 24);
|
|
64
|
+
value = t[1792 + (low & 0xff)] ^ t[1536 + ((low >>> 8) & 0xff)]
|
|
65
|
+
^ t[1280 + ((low >>> 16) & 0xff)] ^ t[1024 + (low >>> 24)]
|
|
66
|
+
^ t[768 + (high & 0xff)] ^ t[512 + ((high >>> 8) & 0xff)]
|
|
67
|
+
^ t[256 + ((high >>> 16) & 0xff)] ^ t[high >>> 24];
|
|
68
|
+
}
|
|
69
|
+
for (; i < length; i++) value = t[(value ^ bytes[i]) & 0xff] ^ (value >>> 8);
|
|
70
|
+
return ~value >>> 0;
|
|
71
|
+
}
|
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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `process.memoryUsage()` returns 0 for every field inside a Durable
|
|
7
7
|
* Object class context (only dynamic-worker isolates under nodejs_compat
|
|
8
8
|
* get the real implementation). The previous `readNodeMem` /
|
|
9
|
-
* `sampleMemory` helpers in
|
|
9
|
+
* `sampleMemory` helpers in session/diag.ts called it anyway and
|
|
10
10
|
* therefore reported zero forever — useless for verifying memory-
|
|
11
11
|
* containment work in plan §3 Track A'.
|
|
12
12
|
*
|
|
@@ -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
|
@@ -52,6 +52,12 @@ export const SQL_MAX_BOUND_PARAMETERS = 100;
|
|
|
52
52
|
*/
|
|
53
53
|
export const DO_STORAGE_LIMIT_BYTES = 10_000_000_000;
|
|
54
54
|
|
|
55
|
+
// N17: how long a launch that reads synchronously (WASI) waits for the paths
|
|
56
|
+
// it names to hydrate out of a lazy import before it fails with EIO naming
|
|
57
|
+
// the first that is not local. Bounded so a fetch that never resolves cannot
|
|
58
|
+
// hold a launch forever.
|
|
59
|
+
export const HYDRATION_DEADLINE_MS = 30_000;
|
|
60
|
+
|
|
55
61
|
/**
|
|
56
62
|
* Maximum SQL statement text per exec. Documented "100 KB"; read as binary
|
|
57
63
|
* KiB because the value is SQLite's compile-time SQLITE_MAX_SQL_LENGTH
|
|
@@ -89,6 +95,32 @@ export const SQLITE_MAX_ROW_BYTES = 2_000_000;
|
|
|
89
95
|
*/
|
|
90
96
|
export const BLOCK_CONCURRENCY_CANCEL_MS = 30_000;
|
|
91
97
|
|
|
98
|
+
// ── Eviction window ─────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Cadence of the alarm that keeps a session Durable Object in memory while
|
|
102
|
+
* a resident process runs. The platform evicts an object after roughly ten
|
|
103
|
+
* seconds with no in-flight event; a pending promise (`ctx.waitUntil`) is
|
|
104
|
+
* not one, and facets die with their parent. Measured 2026-09-21 on local
|
|
105
|
+
* workerd and on Cloudflare: a quiet resident process survived 8 s of
|
|
106
|
+
* silence and was re-driven under a new pid namespace after 12 s.
|
|
107
|
+
*/
|
|
108
|
+
export const RESIDENT_KEEPALIVE_MS = 5_000;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* How long the keep-alive holds a session after its last client is gone.
|
|
112
|
+
*
|
|
113
|
+
* The keep-alive exists so a quiet process survives while its user is
|
|
114
|
+
* present. Unbounded, it made every abandoned session with a dev server
|
|
115
|
+
* run forever: the probe suite's cancelled runs left staging with resident
|
|
116
|
+
* watchers polling `stat` at ~7.5/s in the isolate every session shares,
|
|
117
|
+
* and launches that pass on a fresh Worker reset there (2026-09-22). Past
|
|
118
|
+
* this grace with no attached socket and no request, the session idles
|
|
119
|
+
* out as it did before the keep-alive, and the launch journal re-drives
|
|
120
|
+
* the process when a client returns. Long enough for a tab reload.
|
|
121
|
+
*/
|
|
122
|
+
export const RESIDENT_KEEPALIVE_DETACHED_MS = 60_000;
|
|
123
|
+
|
|
92
124
|
// ── WebSocket attachment bound ──────────────────────────────────────────
|
|
93
125
|
|
|
94
126
|
/**
|
|
@@ -110,6 +142,25 @@ export const WS_ATTACHMENT_LIMIT_BYTES = 16_384;
|
|
|
110
142
|
// metadata and matches the proven on-demand facet transfer envelope.
|
|
111
143
|
export const MAX_RPC_SAFE_PAYLOAD_BYTES = 28 * 1024 * 1024;
|
|
112
144
|
|
|
145
|
+
// ── A one-shot's module map [#13] ───────────────────────────────────────
|
|
146
|
+
//
|
|
147
|
+
// The largest one-shot module map (its closure's raw bytes) the session DO
|
|
148
|
+
// builds and hands to the Worker Loader. What limits it is the DO, not the
|
|
149
|
+
// loader (whose documented limits are concurrency only): the map is built,
|
|
150
|
+
// serialized and passed on from the DO's 128 MB isolate.
|
|
151
|
+
//
|
|
152
|
+
// Measured on a throwaway (2026-09-26; session DO max memoryUsageBytes from
|
|
153
|
+
// GraphQL durableObjectsPeriodicGroups, one launch per minute; a package of
|
|
154
|
+
// 256 KiB distinct modules required whole, verify/release/do-headroom.md),
|
|
155
|
+
// with the map serialized one side module at a time:
|
|
156
|
+
// 4 MiB 64 MB, 8 MiB 81 MB, 12 MiB 97 MB, 16 MiB 110 MB, 20 MiB 126 MB,
|
|
157
|
+
// 24 MiB 145 MB, 28 MiB 164 MB, over an idle 40-55 MB.
|
|
158
|
+
// That is about 4.5 MB of DO per MiB of map. 18 MiB, about 118 MB, is the
|
|
159
|
+
// largest size that leaves about 10 MB of the 128 MB for the rest of the
|
|
160
|
+
// session's work. (Before the single-copy build it was 6-7 MB per MiB: 24 MiB
|
|
161
|
+
// reset the DO.)
|
|
162
|
+
export const ONE_SHOT_MODULE_MAP_MAX_BYTES = 18 * 1024 * 1024;
|
|
163
|
+
|
|
113
164
|
// ── Supervisor heap budget [C'.1] ───────────────────────────────────────
|
|
114
165
|
//
|
|
115
166
|
// Three distinct memory regimes, not one shared pool:
|
|
@@ -139,6 +190,12 @@ export const MAX_RPC_SAFE_PAYLOAD_BYTES = 28 * 1024 * 1024;
|
|
|
139
190
|
// percentOfCeiling accordingly.
|
|
140
191
|
export const SUPERVISOR_HEAP_CEILING_BYTES = 64 * 1024 * 1024;
|
|
141
192
|
|
|
193
|
+
// A process facet's own writes its store could not hold (N18: the session's
|
|
194
|
+
// storage is full) are kept in its heap instead, so a write-then-read still
|
|
195
|
+
// works: at most this many bytes, least recently used out first. An eighth
|
|
196
|
+
// of a facet's measured ~208-256 MiB, the rest left to the program.
|
|
197
|
+
export const FACET_OWN_WRITE_MEMORY_BYTES = 32 * 1024 * 1024;
|
|
198
|
+
|
|
142
199
|
// Shared allowance for transient allocations in the supervisor DO. With the
|
|
143
200
|
// VFS LRU shrunk to 8 MiB during an active reservation, 40 MiB of admitted
|
|
144
201
|
// payload plus the 9 MiB bundle baseline stays below the 64 MiB soft ceiling
|
package/src/oom-classify.ts
CHANGED
|
@@ -224,7 +224,7 @@ export function isDoOverloaded(input: unknown): boolean {
|
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
226
|
* How one failed Durable Object call relates to a retry. The taxonomy
|
|
227
|
-
* Proteus hand-wrote in `cf-backend/src/lib/do-rpc.ts
|
|
227
|
+
* Proteus originally hand-wrote in `cf-backend/src/lib/do-rpc.ts` because the
|
|
228
228
|
* Agents SDK does not export its own, plus the `overloaded` class both
|
|
229
229
|
* consumers need: Cloudflare documents that errors carry `.retryable` and
|
|
230
230
|
* `.overloaded`, that a retryable error should be retried with backoff on a
|
|
@@ -329,6 +329,7 @@ export function describeError(input: unknown): string {
|
|
|
329
329
|
const suffix = cause !== 'unknown' ? ` [${cause}]`
|
|
330
330
|
: isTransientDoReset(input) ? ' [transient-do-reset]'
|
|
331
331
|
: isDoOverloaded(input) ? ' [do-overloaded]'
|
|
332
|
+
: classifyDoCall(input) === 'retryable_flag' ? ' [retryable]'
|
|
332
333
|
: '';
|
|
333
334
|
return `${name}${message}${suffix}`;
|
|
334
335
|
}
|
package/src/w7-frame.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* The format is internal: every producer and consumer deploys together.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { crc32 } from './crc32.js';
|
|
6
7
|
import { CHUNK_SIZE } from './limits.js';
|
|
7
8
|
|
|
8
9
|
// The batch payload types live WITH the wire format that encodes them: every
|
|
@@ -246,7 +247,7 @@ export async function decodeWriteBatchStream(
|
|
|
246
247
|
}
|
|
247
248
|
const beginPayload = await buffer.readExact(beginEnvelope.length, 'batch-begin payload');
|
|
248
249
|
const begin = parseBatchBegin(beginPayload);
|
|
249
|
-
const initialCheck = updateRecordCheck(
|
|
250
|
+
const initialCheck = updateRecordCheck(0, beginEnvelope.header, beginPayload);
|
|
250
251
|
handedOff = true;
|
|
251
252
|
return {
|
|
252
253
|
batchId: begin.id,
|
|
@@ -343,7 +344,7 @@ async function* decodeRecords(
|
|
|
343
344
|
throwIfAborted(options.signal);
|
|
344
345
|
const data = await buffer.readExact(dataLength, 'file-chunk data');
|
|
345
346
|
batchCheck = updateRecordCheck(batchCheck, envelope.header, headerPayload, data);
|
|
346
|
-
active.check =
|
|
347
|
+
active.check = crc32(data, active.check);
|
|
347
348
|
active.nextChunkId++;
|
|
348
349
|
active.receivedBytes += dataLength;
|
|
349
350
|
summary.recordCount++;
|
|
@@ -406,7 +407,7 @@ async function* decodeRecords(
|
|
|
406
407
|
inode,
|
|
407
408
|
nextChunkId: 0,
|
|
408
409
|
receivedBytes: 0,
|
|
409
|
-
check:
|
|
410
|
+
check: 0,
|
|
410
411
|
};
|
|
411
412
|
yield { type: 'file-begin', streamContentId: metadata.contentId, inode };
|
|
412
413
|
break;
|
|
@@ -414,7 +415,7 @@ async function* decodeRecords(
|
|
|
414
415
|
case RecordTag.FileEnd: {
|
|
415
416
|
if (!active) throw new Error('w7-frame: file-end without active file');
|
|
416
417
|
const metadata = parseFileEnd(payload);
|
|
417
|
-
const actualCheck =
|
|
418
|
+
const actualCheck = active.check;
|
|
418
419
|
if (metadata.contentId !== active.metadata.contentId) {
|
|
419
420
|
throw new Error(`w7-frame: file-end content id mismatch for ${active.inode.path}`);
|
|
420
421
|
}
|
|
@@ -443,7 +444,7 @@ async function* decodeRecords(
|
|
|
443
444
|
case RecordTag.BatchEnd: {
|
|
444
445
|
if (active) throw new Error(`w7-frame: batch-end while file ${active.inode.path} is active`);
|
|
445
446
|
const actual = parseBatchEnd(payload);
|
|
446
|
-
const expected = { ...summary, check:
|
|
447
|
+
const expected = { ...summary, check: batchCheck };
|
|
447
448
|
if (!sameSummary(actual, expected)) {
|
|
448
449
|
throw new Error(
|
|
449
450
|
`w7-frame: batch-end summary mismatch; expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
|
|
@@ -479,7 +480,7 @@ function* encodeRecords(
|
|
|
479
480
|
files: EncoderFile[],
|
|
480
481
|
): Generator<Uint8Array[]> {
|
|
481
482
|
const state: EncoderState = {
|
|
482
|
-
batchCheck:
|
|
483
|
+
batchCheck: 0,
|
|
483
484
|
summary: {
|
|
484
485
|
recordCount: 0,
|
|
485
486
|
pathCount: 0,
|
|
@@ -514,7 +515,7 @@ function* encodeRecords(
|
|
|
514
515
|
size: file.inode.size,
|
|
515
516
|
chunkCount: file.inode.chunkCount,
|
|
516
517
|
}, state);
|
|
517
|
-
let fileCheck =
|
|
518
|
+
let fileCheck = 0;
|
|
518
519
|
for (const chunk of file.chunks) {
|
|
519
520
|
const data = chunk.data;
|
|
520
521
|
const contentBytes = new TextEncoder().encode(file.contentId);
|
|
@@ -528,19 +529,19 @@ function* encodeRecords(
|
|
|
528
529
|
state.summary.recordCount++;
|
|
529
530
|
state.summary.chunkCount++;
|
|
530
531
|
state.summary.byteCount += data.byteLength;
|
|
531
|
-
fileCheck =
|
|
532
|
+
fileCheck = crc32(data, fileCheck);
|
|
532
533
|
yield [concatBytes(header, prefix), data];
|
|
533
534
|
}
|
|
534
535
|
yield encodeMetadataRecord(RecordTag.FileEnd, {
|
|
535
536
|
contentId: file.contentId,
|
|
536
537
|
size: file.inode.size,
|
|
537
538
|
chunkCount: file.inode.chunkCount,
|
|
538
|
-
check:
|
|
539
|
+
check: fileCheck,
|
|
539
540
|
}, state);
|
|
540
541
|
}
|
|
541
542
|
const end: W7BatchSummary = {
|
|
542
543
|
...state.summary,
|
|
543
|
-
check:
|
|
544
|
+
check: state.batchCheck,
|
|
544
545
|
};
|
|
545
546
|
yield encodeMetadataRecord(RecordTag.BatchEnd, end);
|
|
546
547
|
}
|
|
@@ -916,37 +917,10 @@ function readU32LE(bytes: Uint8Array, offset: number): number {
|
|
|
916
917
|
|
|
917
918
|
function updateRecordCheck(seed: number, ...parts: Uint8Array[]): number {
|
|
918
919
|
let check = seed;
|
|
919
|
-
for (const part of parts) check =
|
|
920
|
+
for (const part of parts) check = crc32(part, check);
|
|
920
921
|
return check;
|
|
921
922
|
}
|
|
922
923
|
|
|
923
|
-
const CRC_SEED = 0xffff_ffff;
|
|
924
|
-
let crcTable: Uint32Array | null = null;
|
|
925
|
-
|
|
926
|
-
function crc32Update(check: number, bytes: Uint8Array): number {
|
|
927
|
-
crcTable ??= createCrcTable();
|
|
928
|
-
const table = crcTable;
|
|
929
|
-
let value = check;
|
|
930
|
-
for (const byte of bytes) value = table[(value ^ byte) & 0xff] ^ (value >>> 8);
|
|
931
|
-
return value >>> 0;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
function crc32Finish(check: number): number {
|
|
935
|
-
return (check ^ 0xffff_ffff) >>> 0;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
function createCrcTable(): Uint32Array {
|
|
939
|
-
const table = new Uint32Array(256);
|
|
940
|
-
for (let index = 0; index < table.length; index++) {
|
|
941
|
-
let value = index;
|
|
942
|
-
for (let bit = 0; bit < 8; bit++) {
|
|
943
|
-
value = (value & 1) !== 0 ? 0xedb8_8320 ^ (value >>> 1) : value >>> 1;
|
|
944
|
-
}
|
|
945
|
-
table[index] = value >>> 0;
|
|
946
|
-
}
|
|
947
|
-
return table;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
924
|
function noopRetention(bytes: number): W7ChunkRetention {
|
|
951
925
|
return { bytes, release() {} };
|
|
952
926
|
}
|