@nimbus-sh/platform 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/dist/diag-counters.d.ts +210 -0
  4. package/dist/diag-counters.d.ts.map +1 -0
  5. package/dist/diag-counters.js +170 -0
  6. package/dist/heap-estimate.d.ts +225 -0
  7. package/dist/heap-estimate.d.ts.map +1 -0
  8. package/dist/heap-estimate.js +185 -0
  9. package/dist/heavy-alloc-coord.d.ts +93 -0
  10. package/dist/heavy-alloc-coord.d.ts.map +1 -0
  11. package/dist/heavy-alloc-coord.js +181 -0
  12. package/dist/index.d.ts +19 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +18 -0
  15. package/dist/install-phase.d.ts +47 -0
  16. package/dist/install-phase.d.ts.map +1 -0
  17. package/dist/install-phase.js +1 -0
  18. package/dist/limits.d.ts +83 -0
  19. package/dist/limits.d.ts.map +1 -0
  20. package/dist/limits.js +146 -0
  21. package/dist/oom-classify.d.ts +172 -0
  22. package/dist/oom-classify.d.ts.map +1 -0
  23. package/dist/oom-classify.js +336 -0
  24. package/dist/oom-discriminator.d.ts +176 -0
  25. package/dist/oom-discriminator.d.ts.map +1 -0
  26. package/dist/oom-discriminator.js +272 -0
  27. package/dist/rpc-dispose.d.ts +4 -0
  28. package/dist/rpc-dispose.d.ts.map +1 -0
  29. package/dist/rpc-dispose.js +30 -0
  30. package/dist/w7-frame.d.ts +107 -0
  31. package/dist/w7-frame.d.ts.map +1 -0
  32. package/dist/w7-frame.js +842 -0
  33. package/dist/weighted-credit-pool.d.ts +61 -0
  34. package/dist/weighted-credit-pool.d.ts.map +1 -0
  35. package/dist/weighted-credit-pool.js +196 -0
  36. package/package.json +57 -0
  37. package/src/diag-counters.ts +310 -0
  38. package/src/heap-estimate.ts +331 -0
  39. package/src/heavy-alloc-coord.ts +241 -0
  40. package/src/index.ts +19 -0
  41. package/src/install-phase.ts +56 -0
  42. package/src/limits.ts +165 -0
  43. package/src/oom-classify.ts +350 -0
  44. package/src/oom-discriminator.ts +405 -0
  45. package/src/rpc-dispose.ts +31 -0
  46. package/src/w7-frame.ts +1046 -0
  47. package/src/weighted-credit-pool.ts +244 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * oom-discriminator.ts — singleton ring buffer for OOM / failure
3
+ * forensics + last-known-RPC-frame + last-known-facet-id +
4
+ * session-recovery-event ring, plus snapshot/rehydrate hooks for DO
5
+ * storage persistence.
6
+ *
7
+ * W5 (Lever 5 / J.1.1) — original module. Pairs with oom-classify.ts.
8
+ * C'.2 — added the recovery_event ring.
9
+ *
10
+ * Why singleton-per-isolate
11
+ * ─────────────────────────
12
+ * Same pattern as src/observability/diag-counters.ts. The supervisor bundle
13
+ * is the consumer; all writers (sqlite-vfs, facet-pool, facet-manager,
14
+ * supervisor-rpc, npm-installer, nimbus-session) live in the same
15
+ * isolate. Module scope provides one process-local diagnostic without
16
+ * threading a handle through each writer.
17
+ *
18
+ * Two distinct rings
19
+ * ──────────────────
20
+ * - failures: things that failed (the original W5 ring).
21
+ * - recoveryEvents: lifecycle transitions of the session. Cold isolate
22
+ * boot → 'cold' → 'hydrated' → 'active' → 'drained'.
23
+ *
24
+ * Bounded-size guarantees
25
+ * ───────────────────────
26
+ * - failures ring: 50 entries (RING_SIZE).
27
+ * - recoveryEvents ring: 50 entries (RECOVERY_RING_SIZE).
28
+ * - per-message cap: 200 chars (truncated at insert).
29
+ * - per-RPC-frame: single slot, one object.
30
+ * - per-facet-id: single slot, one object.
31
+ *
32
+ * Snapshot size stays bounded even with both rings full.
33
+ */
34
+ import { type OomCause } from './oom-classify.js';
35
+ export interface RpcFrame {
36
+ method: string;
37
+ payloadBytes: number;
38
+ atMs: number;
39
+ }
40
+ export interface FacetId {
41
+ codeId: string;
42
+ slotIndex: number;
43
+ atMs: number;
44
+ }
45
+ export interface DiagFailure {
46
+ /** ms epoch */
47
+ at: number;
48
+ /** Lifecycle stage (e.g. 'install', 'resolve', 'rpc', 'facet', 'ws'). */
49
+ phase: string;
50
+ /** Discriminated cause. See oom-classify.ts. */
51
+ cause: OomCause;
52
+ /** Best-effort RSS estimate in bytes (peak observed). */
53
+ rssEstimateBytes: number;
54
+ /** process.memoryUsage().heapUsed if available; 0 in DO contexts. */
55
+ heapUsedBytes: number;
56
+ /** SqliteVFS LRU hot bytes at time of failure. */
57
+ lruBytes: number;
58
+ /** Sum of in-flight RPC payload bytes (best-effort). */
59
+ inFlightBytes: number;
60
+ /** Snapshot of the most recent RPC frame, if any. */
61
+ lastRpcFrame: RpcFrame | null;
62
+ /** Snapshot of the most recent facet dispatch, if any. */
63
+ lastFacetId: FacetId | null;
64
+ /** Optional facet exit code (when failure represents a facet termination). */
65
+ exitCode?: number;
66
+ /** Optional facet pid. */
67
+ pid?: number;
68
+ /** Free-form message; truncated to MESSAGE_CAP characters. */
69
+ message?: string;
70
+ }
71
+ /**
72
+ * Session lifecycle states for the C'.2 recovery_event ring.
73
+ *
74
+ * This module records state-machine transitions; it does not enforce them.
75
+ *
76
+ * State semantics:
77
+ * - 'cold' : fresh DO instance, no in-memory session state yet.
78
+ * - 'hydrated' : Phase R completed — SQL state read into in-memory cache.
79
+ * - 'active' : Phase B+W+O completed — kernel/shell/terminal wired.
80
+ * - 'drained' : webSocketError/Close fired; in-memory caches nulled,
81
+ * SQL state freshly persisted; awaiting next reconnect.
82
+ *
83
+ * Trigger labels: human-readable event that caused the transition.
84
+ * Examples: 'first-fetch', 'ws-upgrade', 'ws-close', 'ws-error',
85
+ * 'isolate-evicted', 'manual-test'.
86
+ */
87
+ /** Session state union.
88
+ *
89
+ * The original four are the high-level lifecycle markers:
90
+ * cold — never inited
91
+ * hydrated — re-init complete (warm path; Phase O skipped)
92
+ * active — live shell processing input
93
+ * drained — webSocketError/Close fired; SQL freshly persisted
94
+ *
95
+ * The four [B'.4] additions are the fine-grained phases of
96
+ * initSession itself, recorded as the function progresses:
97
+ * rehydrate — Phase R: loading state from DO SQLite
98
+ * build — Phase B: constructing Kernel/Shell/registry
99
+ * wire — Phase W: attaching terminal, replaying scrollback
100
+ * online — Phase O: cold-start MOTD + starter hint (cold only;
101
+ * on warm re-init this phase is skipped and the
102
+ * initSession ends with 'hydrated' instead)
103
+ *
104
+ * The phase transitions are SUPPLEMENTAL to the high-level markers,
105
+ * not a replacement. Probes that look for the legacy `'hydrated'`
106
+ * marker continue to work; probes that want fine-grained debugging
107
+ * can grep for the phase states.
108
+ */
109
+ export type SessionState = 'cold' | 'hydrated' | 'active' | 'drained' | 'rehydrate' | 'build' | 'wire' | 'online';
110
+ export interface DiagRecoveryEvent {
111
+ /** ms epoch */
112
+ at: number;
113
+ /** Source state. */
114
+ fromState: SessionState;
115
+ /** Destination state. */
116
+ toState: SessionState;
117
+ /** Human-readable trigger ('ws-close', 'isolate-evicted', etc.). */
118
+ trigger: string;
119
+ /** The isolate generation at the time of the event. Lets a probe detect the
120
+ * difference between an in-isolate transition and a cross-isolate one
121
+ * (the latter implies workerd recycled the DO). */
122
+ isolateGen: number;
123
+ /** True when the transition could not preserve state that should
124
+ * have survived (e.g. SQL persist threw or a snapshot was missing). */
125
+ dataLoss: boolean;
126
+ /** Number of SQL keys/rows rehydrated on a hydrated transition.
127
+ * Zero when the transition is one that doesn't read SQL. */
128
+ snapshotKeysRehydrated: number;
129
+ /** Optional free-form notes (200 char cap). */
130
+ notes?: string;
131
+ }
132
+ /** Append a failure to the ring. Newest first. Capped at RING_SIZE. */
133
+ export declare function recordFailure(f: DiagFailure): void;
134
+ /** Read a snapshot of the ring. Newest first. Caller-side mutations
135
+ * do not affect the singleton. */
136
+ export declare function getFailures(): DiagFailure[];
137
+ /** Record the current RPC frame (called at every RPC entry). Bounded
138
+ * to a single slot — the LATEST frame wins. */
139
+ export declare function setLastRpcFrame(method: string, payloadBytes: number): void;
140
+ export declare function getLastRpcFrame(): RpcFrame | null;
141
+ /** Record the most recent facet dispatch. */
142
+ export declare function setLastFacetId(codeId: string, slotIndex: number): void;
143
+ export declare function getLastFacetId(): FacetId | null;
144
+ /** Append a recovery event to the ring. Newest first. Capped at
145
+ * RECOVERY_RING_SIZE. */
146
+ export declare function recordRecoveryEvent(e: DiagRecoveryEvent): void;
147
+ /** Read a snapshot of the recovery ring. Newest first. */
148
+ export declare function getRecoveryEvents(): DiagRecoveryEvent[];
149
+ /** Reset the recovery ring. Tests + manual operator use only. */
150
+ export declare function resetRecoveryEvents(): void;
151
+ /**
152
+ * Build a JSON-serializable snapshot suitable for ctx.storage.put.
153
+ * Bounded ≤ 40 KB even with both rings full of MESSAGE_CAP-truncated
154
+ * messages. Schema version embedded so a future shape change can
155
+ * cleanly reject old snapshots.
156
+ *
157
+ * Schema version 2 includes both failure and recovery-event rings.
158
+ */
159
+ export interface DiagSnapshot {
160
+ /** Schema version. Bump when shape changes. */
161
+ v: 2;
162
+ failures: DiagFailure[];
163
+ recoveryEvents: DiagRecoveryEvent[];
164
+ lastRpcFrame: RpcFrame | null;
165
+ lastFacetId: FacetId | null;
166
+ }
167
+ export declare function snapshotForStorage(): DiagSnapshot;
168
+ /**
169
+ * Restore from a snapshot. Garbage / null / wrong-shape input is
170
+ * silently ignored. Does NOT throw — constructor-time rehydration must
171
+ * never block DO startup.
172
+ *
173
+ * Only the current v2 schema is accepted.
174
+ */
175
+ export declare function rehydrateFromStorage(blob: unknown): void;
176
+ //# sourceMappingURL=oom-discriminator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oom-discriminator.d.ts","sourceRoot":"","sources":["../src/oom-discriminator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,272 @@
1
+ /**
2
+ * oom-discriminator.ts — singleton ring buffer for OOM / failure
3
+ * forensics + last-known-RPC-frame + last-known-facet-id +
4
+ * session-recovery-event ring, plus snapshot/rehydrate hooks for DO
5
+ * storage persistence.
6
+ *
7
+ * W5 (Lever 5 / J.1.1) — original module. Pairs with oom-classify.ts.
8
+ * C'.2 — added the recovery_event ring.
9
+ *
10
+ * Why singleton-per-isolate
11
+ * ─────────────────────────
12
+ * Same pattern as src/observability/diag-counters.ts. The supervisor bundle
13
+ * is the consumer; all writers (sqlite-vfs, facet-pool, facet-manager,
14
+ * supervisor-rpc, npm-installer, nimbus-session) live in the same
15
+ * isolate. Module scope provides one process-local diagnostic without
16
+ * threading a handle through each writer.
17
+ *
18
+ * Two distinct rings
19
+ * ──────────────────
20
+ * - failures: things that failed (the original W5 ring).
21
+ * - recoveryEvents: lifecycle transitions of the session. Cold isolate
22
+ * boot → 'cold' → 'hydrated' → 'active' → 'drained'.
23
+ *
24
+ * Bounded-size guarantees
25
+ * ───────────────────────
26
+ * - failures ring: 50 entries (RING_SIZE).
27
+ * - recoveryEvents ring: 50 entries (RECOVERY_RING_SIZE).
28
+ * - per-message cap: 200 chars (truncated at insert).
29
+ * - per-RPC-frame: single slot, one object.
30
+ * - per-facet-id: single slot, one object.
31
+ *
32
+ * Snapshot size stays bounded even with both rings full.
33
+ */
34
+ import { isOomCause } from './oom-classify.js';
35
+ const RING_SIZE = 50;
36
+ const RECOVERY_RING_SIZE = 50;
37
+ const MESSAGE_CAP = 200;
38
+ const state = {
39
+ failures: [],
40
+ recoveryEvents: [],
41
+ lastRpcFrame: null,
42
+ lastFacetId: null,
43
+ };
44
+ function getState() {
45
+ return state;
46
+ }
47
+ /** Append a failure to the ring. Newest first. Capped at RING_SIZE. */
48
+ export function recordFailure(f) {
49
+ const s = getState();
50
+ // Defensive copy + message cap.
51
+ const entry = {
52
+ at: Number(f.at) || Date.now(),
53
+ phase: String(f.phase ?? 'unknown'),
54
+ cause: f.cause,
55
+ rssEstimateBytes: Number(f.rssEstimateBytes) || 0,
56
+ heapUsedBytes: Number(f.heapUsedBytes) || 0,
57
+ lruBytes: Number(f.lruBytes) || 0,
58
+ inFlightBytes: Number(f.inFlightBytes) || 0,
59
+ lastRpcFrame: f.lastRpcFrame ?? s.lastRpcFrame,
60
+ lastFacetId: f.lastFacetId ?? s.lastFacetId,
61
+ };
62
+ if (typeof f.exitCode === 'number')
63
+ entry.exitCode = f.exitCode;
64
+ if (typeof f.pid === 'number')
65
+ entry.pid = f.pid;
66
+ if (f.message) {
67
+ const m = String(f.message);
68
+ entry.message = m.length > MESSAGE_CAP ? m.slice(0, MESSAGE_CAP) : m;
69
+ }
70
+ s.failures.unshift(entry);
71
+ if (s.failures.length > RING_SIZE)
72
+ s.failures.length = RING_SIZE;
73
+ }
74
+ /** Read a snapshot of the ring. Newest first. Caller-side mutations
75
+ * do not affect the singleton. */
76
+ export function getFailures() {
77
+ return getState().failures.slice(0);
78
+ }
79
+ /** Record the current RPC frame (called at every RPC entry). Bounded
80
+ * to a single slot — the LATEST frame wins. */
81
+ export function setLastRpcFrame(method, payloadBytes) {
82
+ const s = getState();
83
+ s.lastRpcFrame = {
84
+ method: String(method),
85
+ payloadBytes: Number(payloadBytes) || 0,
86
+ atMs: Date.now(),
87
+ };
88
+ }
89
+ export function getLastRpcFrame() {
90
+ return getState().lastRpcFrame;
91
+ }
92
+ /** Record the most recent facet dispatch. */
93
+ export function setLastFacetId(codeId, slotIndex) {
94
+ const s = getState();
95
+ s.lastFacetId = {
96
+ codeId: String(codeId),
97
+ slotIndex: Number(slotIndex) || 0,
98
+ atMs: Date.now(),
99
+ };
100
+ }
101
+ export function getLastFacetId() {
102
+ return getState().lastFacetId;
103
+ }
104
+ // ── C'.2 recovery_event ring ────────────────────────────────────────────
105
+ //
106
+ // The ring is bounded at RECOVERY_RING_SIZE; the diag endpoint reads via
107
+ // getRecoveryEvents() (newest first).
108
+ /** Append a recovery event to the ring. Newest first. Capped at
109
+ * RECOVERY_RING_SIZE. */
110
+ export function recordRecoveryEvent(e) {
111
+ const s = getState();
112
+ // Defensive copy + notes cap. We intentionally do NOT validate the
113
+ // state-machine direction here (e.g. that 'drained' only follows
114
+ // 'active') — the state machine lives elsewhere; this ring is a
115
+ // recorder, not an enforcer. If a probe sees an impossible
116
+ // transition, that's a real bug in the state machine and the
117
+ // probe must fail.
118
+ const entry = {
119
+ at: Number(e.at) || Date.now(),
120
+ fromState: e.fromState,
121
+ toState: e.toState,
122
+ trigger: String(e.trigger ?? 'unknown'),
123
+ isolateGen: Number(e.isolateGen) || 0,
124
+ dataLoss: !!e.dataLoss,
125
+ snapshotKeysRehydrated: Number(e.snapshotKeysRehydrated) || 0,
126
+ };
127
+ if (e.notes) {
128
+ const n = String(e.notes);
129
+ entry.notes = n.length > MESSAGE_CAP ? n.slice(0, MESSAGE_CAP) : n;
130
+ }
131
+ s.recoveryEvents.unshift(entry);
132
+ if (s.recoveryEvents.length > RECOVERY_RING_SIZE) {
133
+ s.recoveryEvents.length = RECOVERY_RING_SIZE;
134
+ }
135
+ }
136
+ /** Read a snapshot of the recovery ring. Newest first. */
137
+ export function getRecoveryEvents() {
138
+ return getState().recoveryEvents.slice(0);
139
+ }
140
+ /** Reset the recovery ring. Tests + manual operator use only. */
141
+ export function resetRecoveryEvents() {
142
+ getState().recoveryEvents.length = 0;
143
+ }
144
+ export function snapshotForStorage() {
145
+ const s = getState();
146
+ return {
147
+ v: 2,
148
+ failures: s.failures.slice(0),
149
+ recoveryEvents: s.recoveryEvents.slice(0),
150
+ lastRpcFrame: s.lastRpcFrame,
151
+ lastFacetId: s.lastFacetId,
152
+ };
153
+ }
154
+ /**
155
+ * Restore from a snapshot. Garbage / null / wrong-shape input is
156
+ * silently ignored. Does NOT throw — constructor-time rehydration must
157
+ * never block DO startup.
158
+ *
159
+ * Only the current v2 schema is accepted.
160
+ */
161
+ export function rehydrateFromStorage(blob) {
162
+ if (!isRecord(blob) || blob.v !== 2)
163
+ return;
164
+ const b = blob;
165
+ if (!Array.isArray(b.failures))
166
+ return;
167
+ const s = getState();
168
+ s.failures.length = 0;
169
+ for (const f of b.failures) {
170
+ const entry = parseFailure(f);
171
+ if (!entry)
172
+ continue;
173
+ s.failures.push(entry);
174
+ if (s.failures.length >= RING_SIZE)
175
+ break;
176
+ }
177
+ s.recoveryEvents.length = 0;
178
+ if (Array.isArray(b.recoveryEvents)) {
179
+ for (const e of b.recoveryEvents) {
180
+ const entry = parseRecoveryEvent(e);
181
+ if (!entry)
182
+ continue;
183
+ s.recoveryEvents.push(entry);
184
+ if (s.recoveryEvents.length >= RECOVERY_RING_SIZE)
185
+ break;
186
+ }
187
+ }
188
+ const lastRpcFrame = parseRpcFrame(b.lastRpcFrame);
189
+ if (lastRpcFrame)
190
+ s.lastRpcFrame = lastRpcFrame;
191
+ const lastFacetId = parseFacetId(b.lastFacetId);
192
+ if (lastFacetId)
193
+ s.lastFacetId = lastFacetId;
194
+ }
195
+ function isRecord(value) {
196
+ return typeof value === 'object' && value !== null;
197
+ }
198
+ function parseRpcFrame(value) {
199
+ if (!isRecord(value)
200
+ || typeof value.method !== 'string'
201
+ || typeof value.payloadBytes !== 'number'
202
+ || typeof value.atMs !== 'number')
203
+ return null;
204
+ return { method: value.method, payloadBytes: value.payloadBytes, atMs: value.atMs };
205
+ }
206
+ function parseFacetId(value) {
207
+ if (!isRecord(value)
208
+ || typeof value.codeId !== 'string'
209
+ || typeof value.slotIndex !== 'number'
210
+ || typeof value.atMs !== 'number')
211
+ return null;
212
+ return { codeId: value.codeId, slotIndex: value.slotIndex, atMs: value.atMs };
213
+ }
214
+ function parseFailure(value) {
215
+ if (!isRecord(value)
216
+ || typeof value.at !== 'number'
217
+ || typeof value.phase !== 'string'
218
+ || !isOomCause(value.cause)
219
+ || typeof value.rssEstimateBytes !== 'number'
220
+ || typeof value.heapUsedBytes !== 'number'
221
+ || typeof value.lruBytes !== 'number'
222
+ || typeof value.inFlightBytes !== 'number')
223
+ return null;
224
+ const entry = {
225
+ at: value.at,
226
+ phase: value.phase,
227
+ cause: value.cause,
228
+ rssEstimateBytes: value.rssEstimateBytes,
229
+ heapUsedBytes: value.heapUsedBytes,
230
+ lruBytes: value.lruBytes,
231
+ inFlightBytes: value.inFlightBytes,
232
+ lastRpcFrame: parseRpcFrame(value.lastRpcFrame),
233
+ lastFacetId: parseFacetId(value.lastFacetId),
234
+ };
235
+ if (typeof value.exitCode === 'number')
236
+ entry.exitCode = value.exitCode;
237
+ if (typeof value.pid === 'number')
238
+ entry.pid = value.pid;
239
+ if (typeof value.message === 'string')
240
+ entry.message = value.message.slice(0, MESSAGE_CAP);
241
+ return entry;
242
+ }
243
+ const SESSION_STATES = [
244
+ 'cold', 'hydrated', 'active', 'drained',
245
+ 'rehydrate', 'build', 'wire', 'online',
246
+ ];
247
+ function isSessionState(value) {
248
+ return SESSION_STATES.some((state) => state === value);
249
+ }
250
+ function parseRecoveryEvent(value) {
251
+ if (!isRecord(value)
252
+ || typeof value.at !== 'number'
253
+ || !isSessionState(value.fromState)
254
+ || !isSessionState(value.toState)
255
+ || typeof value.trigger !== 'string'
256
+ || typeof value.isolateGen !== 'number'
257
+ || typeof value.dataLoss !== 'boolean'
258
+ || typeof value.snapshotKeysRehydrated !== 'number')
259
+ return null;
260
+ const entry = {
261
+ at: value.at,
262
+ fromState: value.fromState,
263
+ toState: value.toState,
264
+ trigger: value.trigger,
265
+ isolateGen: value.isolateGen,
266
+ dataLoss: value.dataLoss,
267
+ snapshotKeysRehydrated: value.snapshotKeysRehydrated,
268
+ };
269
+ if (typeof value.notes === 'string')
270
+ entry.notes = value.notes.slice(0, MESSAGE_CAP);
271
+ return entry;
272
+ }
@@ -0,0 +1,4 @@
1
+ export declare function disposeRpcResource(value: unknown): boolean;
2
+ export declare function disposeRpcResources(values: Iterable<unknown>): void;
3
+ export declare function useRpcResource<T, R>(promise: Promise<T>, use: (value: T) => R | Promise<R>): Promise<R>;
4
+ //# sourceMappingURL=rpc-dispose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-dispose.d.ts","sourceRoot":"","sources":["../src/rpc-dispose.ts"],"names":[],"mappings":"AAEA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAY1D;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,IAAI,CAEnE;AAED,wBAAsB,cAAc,CAAC,CAAC,EAAE,CAAC,EACvC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAChC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
@@ -0,0 +1,30 @@
1
+ export function disposeRpcResource(value) {
2
+ if ((typeof value !== 'object' && typeof value !== 'function') || value === null)
3
+ return false;
4
+ const disposerKey = Symbol.dispose;
5
+ if (!disposerKey)
6
+ return false;
7
+ const disposer = Reflect.get(value, disposerKey);
8
+ if (typeof disposer !== 'function')
9
+ return false;
10
+ try {
11
+ Reflect.apply(disposer, value, []);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ export function disposeRpcResources(values) {
19
+ for (const value of values)
20
+ disposeRpcResource(value);
21
+ }
22
+ export async function useRpcResource(promise, use) {
23
+ const value = await promise;
24
+ try {
25
+ return await use(value);
26
+ }
27
+ finally {
28
+ disposeRpcResource(value);
29
+ }
30
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * W7 v3 — incremental typed records for streamed bulk filesystem writes.
3
+ * The format is internal: every producer and consumer deploys together.
4
+ */
5
+ export type VfsInodeKind = 'file' | 'directory' | 'symlink';
6
+ /** Entry for bulk inode creation via writeBatch(). */
7
+ export interface BatchInodeEntry {
8
+ path: string;
9
+ parentPath: string;
10
+ /** Defaults to isDir ? directory : file for legacy/non-symlink producers. */
11
+ kind?: VfsInodeKind;
12
+ isDir: boolean;
13
+ size: number;
14
+ atime?: number;
15
+ mtime: number;
16
+ mode: number;
17
+ uid?: number;
18
+ gid?: number;
19
+ chunkCount: number;
20
+ }
21
+ /** Entry for bulk chunk creation via writeBatch(). */
22
+ export interface BatchChunkEntry {
23
+ path: string;
24
+ chunkId: number;
25
+ data: Uint8Array;
26
+ }
27
+ /** Payload for writeBatch() — all inodes + chunks written in ONE transactionSync(). */
28
+ export interface BatchWritePayload {
29
+ inodes: BatchInodeEntry[];
30
+ chunks: BatchChunkEntry[];
31
+ /** Paths to delete before writing (for clean reinstall). */
32
+ deletePaths?: string[];
33
+ }
34
+ export declare const W7_MAGIC: Uint8Array<ArrayBuffer>;
35
+ export declare const W7_MAX_PATHS_PER_BATCH = 128;
36
+ export declare const W7_MAX_OWNED_PATH_BYTES: number;
37
+ export declare const W7_MAX_RECORD_BYTES: number;
38
+ declare const MODE: "path-atomic-committed-prefix";
39
+ export interface W7BatchSummary {
40
+ recordCount: number;
41
+ pathCount: number;
42
+ deleteCount: number;
43
+ directoryCount: number;
44
+ fileCount: number;
45
+ chunkCount: number;
46
+ byteCount: number;
47
+ check: number;
48
+ }
49
+ export interface W7ChunkRetention {
50
+ readonly bytes: number;
51
+ release(): void;
52
+ }
53
+ export interface W7DecodeOptions {
54
+ signal?: AbortSignal;
55
+ retainChunk?: (byteLength: number, signal?: AbortSignal) => Promise<W7ChunkRetention>;
56
+ }
57
+ type W7DirectoryInode = BatchInodeEntry & {
58
+ kind: 'directory';
59
+ isDir: true;
60
+ };
61
+ type W7ContentInode = BatchInodeEntry & {
62
+ kind: 'file' | 'symlink';
63
+ isDir: false;
64
+ };
65
+ export type W7DecodedRecord = {
66
+ type: 'delete';
67
+ path: string;
68
+ } | {
69
+ type: 'directory';
70
+ inode: W7DirectoryInode;
71
+ } | {
72
+ type: 'file-begin';
73
+ streamContentId: string;
74
+ inode: W7ContentInode;
75
+ } | {
76
+ type: 'file-chunk';
77
+ streamContentId: string;
78
+ path: string;
79
+ chunkId: number;
80
+ data: Uint8Array;
81
+ retention: W7ChunkRetention;
82
+ } | {
83
+ type: 'file-end';
84
+ streamContentId: string;
85
+ path: string;
86
+ size: number;
87
+ chunkCount: number;
88
+ check: number;
89
+ } | {
90
+ type: 'batch-end';
91
+ summary: W7BatchSummary;
92
+ };
93
+ export interface W7DecodedStream {
94
+ readonly batchId: string;
95
+ readonly mode: typeof MODE;
96
+ readonly records: AsyncIterable<W7DecodedRecord>;
97
+ }
98
+ /** Encode one bounded record per pull; no batch-sized metadata header exists. */
99
+ export declare function encodeWriteBatchStream(payload: BatchWritePayload): ReadableStream<Uint8Array>;
100
+ /**
101
+ * Parse the v3 preamble eagerly, then expose validated operation records
102
+ * incrementally. Chunk credit is acquired after its bounded header validates
103
+ * and before its payload bytes are read or copied.
104
+ */
105
+ export declare function decodeWriteBatchStream(stream: ReadableStream<Uint8Array>, options?: W7DecodeOptions): Promise<W7DecodedStream>;
106
+ export {};
107
+ //# sourceMappingURL=w7-frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"w7-frame.d.ts","sourceRoot":"","sources":["../src/w7-frame.ts"],"names":[],"mappings":"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"}