@milaboratories/pl-tree 1.13.7 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/accessors.d.ts +15 -17
  2. package/dist/accessors.d.ts.map +1 -1
  3. package/dist/dump.d.ts +14 -10
  4. package/dist/dump.d.ts.map +1 -1
  5. package/dist/index.cjs +7 -0
  6. package/dist/index.d.ts +2 -1
  7. package/dist/index.js +2 -1
  8. package/dist/persisted_tree.cjs +465 -0
  9. package/dist/persisted_tree.cjs.map +1 -0
  10. package/dist/persisted_tree.d.ts +99 -0
  11. package/dist/persisted_tree.d.ts.map +1 -0
  12. package/dist/persisted_tree.js +460 -0
  13. package/dist/persisted_tree.js.map +1 -0
  14. package/dist/snapshot.d.ts +12 -14
  15. package/dist/snapshot.d.ts.map +1 -1
  16. package/dist/state.cjs +7 -0
  17. package/dist/state.cjs.map +1 -1
  18. package/dist/state.d.ts +13 -11
  19. package/dist/state.d.ts.map +1 -1
  20. package/dist/state.js +7 -0
  21. package/dist/state.js.map +1 -1
  22. package/dist/sync.d.ts +26 -19
  23. package/dist/sync.d.ts.map +1 -1
  24. package/dist/synchronized_tree.cjs +62 -4
  25. package/dist/synchronized_tree.cjs.map +1 -1
  26. package/dist/synchronized_tree.d.ts +64 -17
  27. package/dist/synchronized_tree.d.ts.map +1 -1
  28. package/dist/synchronized_tree.js +62 -4
  29. package/dist/synchronized_tree.js.map +1 -1
  30. package/dist/traversal_ops.d.ts +12 -10
  31. package/dist/traversal_ops.d.ts.map +1 -1
  32. package/dist/value_and_error.d.ts +3 -4
  33. package/dist/value_and_error.d.ts.map +1 -1
  34. package/dist/value_or_error.d.ts +1 -2
  35. package/dist/value_or_error.d.ts.map +1 -1
  36. package/package.json +7 -7
  37. package/src/index.ts +1 -0
  38. package/src/persisted_tree.test.ts +291 -0
  39. package/src/persisted_tree.ts +708 -0
  40. package/src/state.ts +8 -0
  41. package/src/synchronized_tree.ts +102 -5
@@ -0,0 +1,460 @@
1
+ import { PlTreeState } from "./state.js";
2
+ import { NullSignedResourceId, createSignedResourceId, isNotNullSignedResourceId, parseSignedResourceId, toResourceSignature } from "@milaboratories/pl-client";
3
+ import { deflate, inflate } from "node:zlib";
4
+ import { promisify } from "node:util";
5
+ //#region src/persisted_tree.ts
6
+ const deflateAsync = promisify(deflate);
7
+ const inflateAsync = promisify(inflate);
8
+ /** "PLTS", little-endian. Distinguishes our file from anything else that lands in the
9
+ * snapshot directory, so a foreign file is rejected instead of parsed as garbage. */
10
+ const MAGIC = 1398033488;
11
+ /** Bumped whenever the byte layout below changes in a way an older decoder would
12
+ * misread. Only an exact match is accepted: the decoder's job here is to recognise a
13
+ * format it cannot read, not to migrate it. Invalidation on rule changes is the cache
14
+ * key's job (the middle layer's build stamp), not this number's. */
15
+ const PERSISTED_TREE_SCHEMA_VERSION = 1;
16
+ /** Payload is deflated. Absent means the payload is stored as-is, which is what a
17
+ * periodic write falls back to if compression CPU ever becomes a problem. */
18
+ const FLAG_COMPRESSED = 1;
19
+ const HEADER_FIXED_BYTES = 10;
20
+ const TRAILER_BYTES = 8;
21
+ /** Ceiling on the inflated payload. The checksum only says the bytes are the ones that were
22
+ * written, so a replaced file can pair a small, consistent payload with a ratio that
23
+ * inflates to gigabytes: unbounded, that costs the process its memory instead of costing
24
+ * one cold open. Far above anything real, the heaviest reference project being 10 MB
25
+ * compressed against a 256 MB cap on the whole directory. */
26
+ const DEFAULT_MAX_PAYLOAD_BYTES = 512 * 1024 * 1024;
27
+ /** Enum orderings are part of the on-disk format: append only, never reorder. An index
28
+ * the decoder does not know is malformed input, which is why decoding is bounds-checked
29
+ * rather than cast. */
30
+ const KINDS = ["Structural", "Value"];
31
+ const FIELD_TYPES = [
32
+ "Input",
33
+ "Output",
34
+ "Service",
35
+ "OTW",
36
+ "Dynamic",
37
+ "MTW"
38
+ ];
39
+ const FIELD_STATUSES = [
40
+ "Empty",
41
+ "Assigned",
42
+ "Resolved"
43
+ ];
44
+ const RES_HAS_DATA = 1;
45
+ const RES_INPUTS_LOCKED = 2;
46
+ const RES_OUTPUTS_LOCKED = 4;
47
+ const RES_READY = 8;
48
+ const RES_FINAL = 16;
49
+ /** Global id 0 stands for "no reference". A real resource can never have it:
50
+ * `createSignedResourceId` rejects the null id, so the sentinel is unambiguous. */
51
+ const NO_REFERENCE = 0n;
52
+ /**
53
+ * Captures a live tree's state for persistence. `witness` is the signature of the root as
54
+ * currently held, which is what a later open compares against to decide whether the
55
+ * signatures in this snapshot are still live.
56
+ *
57
+ * Throws on an invalidated tree. {@link PlTreeState.dumpState} would happily read through
58
+ * one, and a terminated or inconsistent tree is exactly what must not reach disk, so the
59
+ * caller has to capture before it tears the tree down.
60
+ */
61
+ function captureTreeState(state, witness) {
62
+ if (!state.isValid) throw new Error("refusing to capture an invalidated tree");
63
+ const resources = state.dumpState().map((r) => ({
64
+ ...r,
65
+ fields: r.fields.map((f) => ({
66
+ name: f.name,
67
+ type: f.type,
68
+ status: f.status,
69
+ value: f.value,
70
+ error: f.error,
71
+ valueIsFinal: f.valueIsFinal
72
+ }))
73
+ }));
74
+ return {
75
+ witness,
76
+ roots: [...state.roots],
77
+ resources
78
+ };
79
+ }
80
+ /**
81
+ * Rebuilds a tree from a snapshot, or returns undefined if the snapshot cannot be applied.
82
+ *
83
+ * The snapshot goes through {@link PlTreeState.updateFromResourceData}, the same call the
84
+ * live loading path uses, so every invariant that path enforces is enforced here and cannot
85
+ * drift from it. Reference counts are applied after the whole batch, so the order resources
86
+ * appear in the file does not matter, and finality is recomputed from `finalPredicate`
87
+ * rather than read from the file.
88
+ *
89
+ * The tree is built fresh and thrown away on failure: that call invalidates the tree it is
90
+ * given when it finds an inconsistency, so restoring into a live tree would destroy a
91
+ * working one instead of falling back to a cold open.
92
+ */
93
+ function restoreTreeState(snapshot, finalPredicate, ops = {}) {
94
+ const restored = new PlTreeState(ops.roots ?? new Set(snapshot.roots), finalPredicate);
95
+ try {
96
+ restored.updateFromResourceData([...snapshot.resources], { allowOrphanInputs: true });
97
+ return restored;
98
+ } catch (e) {
99
+ ops.logger?.warn(`tree snapshot could not be restored, opening cold: ${e instanceof Error ? e.message : String(e)}`);
100
+ return;
101
+ }
102
+ }
103
+ /** Serializes a tree mirror to the on-disk format. */
104
+ async function encodePersistedTree(tree, ops = {}) {
105
+ const compress = ops.compress ?? true;
106
+ const payload = writePayload(tree);
107
+ const stored = compress ? await deflateAsync(payload) : payload;
108
+ const header = new Writer(HEADER_FIXED_BYTES + tree.witness.length);
109
+ header.u32(MAGIC);
110
+ header.u16(1);
111
+ header.u16(compress ? FLAG_COMPRESSED : 0);
112
+ header.shortBytes(tree.witness);
113
+ const trailer = new Writer(TRAILER_BYTES);
114
+ trailer.u32(stored.length);
115
+ trailer.u32(crc32(stored));
116
+ return Buffer.concat([
117
+ header.result(),
118
+ stored,
119
+ trailer.result()
120
+ ]);
121
+ }
122
+ function writePayload(tree) {
123
+ const signatures = /* @__PURE__ */ new Map();
124
+ const collect = (id) => {
125
+ if (!isNotNullSignedResourceId(id)) return;
126
+ const { globalId, signature } = parseSignedResourceId(id);
127
+ const existing = signatures.get(globalId);
128
+ if (existing !== void 0 && !Buffer.from(existing).equals(Buffer.from(signature))) throw new Error(`cannot persist a tree holding global id ${globalId} under two different signatures`);
129
+ signatures.set(globalId, signature);
130
+ };
131
+ for (const root of tree.roots) collect(root);
132
+ for (const res of tree.resources) {
133
+ collect(res.id);
134
+ collect(res.originalResourceId);
135
+ collect(res.error);
136
+ for (const f of res.fields) {
137
+ collect(f.value);
138
+ collect(f.error);
139
+ }
140
+ }
141
+ const w = new Writer();
142
+ w.u32(tree.roots.length);
143
+ for (const root of tree.roots) w.u64(globalIdOf(root));
144
+ w.u32(signatures.size);
145
+ for (const [globalId, signature] of signatures) {
146
+ w.u64(globalId);
147
+ w.shortBytes(signature);
148
+ }
149
+ w.u32(tree.resources.length);
150
+ for (const res of tree.resources) writeResource(w, res);
151
+ return w.result();
152
+ }
153
+ function writeResource(w, res) {
154
+ w.u64(globalIdOf(res.id));
155
+ w.u64(optionalGlobalIdOf(res.originalResourceId));
156
+ w.u64(optionalGlobalIdOf(res.error));
157
+ w.u8(indexOfOrThrow(KINDS, res.kind, "resource kind"));
158
+ w.str(res.type.name);
159
+ w.str(res.type.version);
160
+ w.u8((res.data !== void 0 ? RES_HAS_DATA : 0) | (res.inputsLocked ? RES_INPUTS_LOCKED : 0) | (res.outputsLocked ? RES_OUTPUTS_LOCKED : 0) | (res.resourceReady ? RES_READY : 0) | (res.final ? RES_FINAL : 0));
161
+ if (res.data !== void 0) w.bytes(res.data);
162
+ w.u32(res.fields.length);
163
+ for (const f of res.fields) {
164
+ w.str(f.name);
165
+ w.u8(indexOfOrThrow(FIELD_TYPES, f.type, "field type"));
166
+ w.u8(indexOfOrThrow(FIELD_STATUSES, f.status, "field status"));
167
+ w.u64(optionalGlobalIdOf(f.value));
168
+ w.u64(optionalGlobalIdOf(f.error));
169
+ w.u8(f.valueIsFinal ? 1 : 0);
170
+ }
171
+ w.u32(res.kv.length);
172
+ for (const kv of res.kv) {
173
+ w.str(kv.key);
174
+ w.bytes(kv.value);
175
+ }
176
+ }
177
+ /** Reads magic, schema version and witness without touching the payload. Cheap enough to
178
+ * run on every open, which is what makes a rotated-session miss cheap. */
179
+ function readPersistedTreeHeader(bytes) {
180
+ try {
181
+ if (bytes.length < 18) return failure("not-a-snapshot");
182
+ const r = new Reader(bytes);
183
+ if (r.u32() !== MAGIC) return failure("not-a-snapshot");
184
+ const schemaVersion = r.u16();
185
+ r.u16();
186
+ const witness = toResourceSignature(r.shortBytes());
187
+ if (schemaVersion !== 1) return failure("unknown-schema");
188
+ return {
189
+ ok: true,
190
+ value: {
191
+ schemaVersion,
192
+ witness
193
+ }
194
+ };
195
+ } catch {
196
+ return failure("not-a-snapshot");
197
+ }
198
+ }
199
+ /** Reads a whole snapshot. Never throws: a torn, corrupt, foreign or unreadable file is
200
+ * reported as a failure reason, so the caller opens cold instead of replaying garbage. */
201
+ async function decodePersistedTree(bytes, ops = {}) {
202
+ const header = readPersistedTreeHeader(bytes);
203
+ if (!header.ok) return header;
204
+ let payload;
205
+ try {
206
+ const r = new Reader(bytes);
207
+ r.skip(6);
208
+ const flags = r.u16();
209
+ r.shortBytes();
210
+ const payloadStart = r.position;
211
+ const trailer = new Reader(bytes);
212
+ trailer.skip(bytes.length - TRAILER_BYTES);
213
+ const payloadLength = trailer.u32();
214
+ const checksum = trailer.u32();
215
+ if (payloadStart + payloadLength !== bytes.length - TRAILER_BYTES) return failure("truncated");
216
+ const stored = bytes.subarray(payloadStart, payloadStart + payloadLength);
217
+ if (crc32(stored) !== checksum) return failure("checksum");
218
+ payload = (flags & FLAG_COMPRESSED) !== 0 ? await inflateAsync(stored, { maxOutputLength: ops.maxPayloadBytes ?? DEFAULT_MAX_PAYLOAD_BYTES }) : stored;
219
+ } catch {
220
+ return failure("checksum");
221
+ }
222
+ try {
223
+ return {
224
+ ok: true,
225
+ value: readPayload(payload, header.value.witness)
226
+ };
227
+ } catch {
228
+ return failure("malformed");
229
+ }
230
+ }
231
+ function readPayload(payload, witness) {
232
+ const r = new Reader(payload);
233
+ const rootCount = r.u32();
234
+ const rootIds = [];
235
+ for (let i = 0; i < rootCount; i++) rootIds.push(r.u64());
236
+ const signatureCount = r.u32();
237
+ const signatures = /* @__PURE__ */ new Map();
238
+ for (let i = 0; i < signatureCount; i++) {
239
+ const globalId = r.u64();
240
+ signatures.set(globalId, toResourceSignature(r.shortBytes()));
241
+ }
242
+ /** Rejoins a stored global id with its signature. A reference with no table entry is
243
+ * malformed rather than recoverable: an unsigned id addresses nothing. */
244
+ const signed = (globalId) => {
245
+ const signature = signatures.get(globalId);
246
+ if (signature === void 0) throw new Error(`no signature stored for global id ${globalId}`);
247
+ return createSignedResourceId(globalId, signature);
248
+ };
249
+ const optionalSigned = (globalId) => globalId === NO_REFERENCE ? NullSignedResourceId : signed(globalId);
250
+ const roots = rootIds.map(signed);
251
+ const resourceCount = r.u32();
252
+ const resources = [];
253
+ for (let i = 0; i < resourceCount; i++) resources.push(readResource(r, signed, optionalSigned));
254
+ if (!r.atEnd) throw new Error("trailing bytes in snapshot payload");
255
+ return {
256
+ witness,
257
+ roots,
258
+ resources
259
+ };
260
+ }
261
+ function readResource(r, signed, optionalSigned) {
262
+ const id = signed(r.u64());
263
+ const originalResourceId = optionalSigned(r.u64());
264
+ const error = optionalSigned(r.u64());
265
+ const kind = atOrThrow(KINDS, r.u8(), "resource kind");
266
+ const type = {
267
+ name: r.str(),
268
+ version: r.str()
269
+ };
270
+ const flags = r.u8();
271
+ const data = (flags & RES_HAS_DATA) !== 0 ? r.bytes() : void 0;
272
+ const fieldCount = r.u32();
273
+ const fields = [];
274
+ for (let i = 0; i < fieldCount; i++) {
275
+ const name = r.str();
276
+ const fieldType = atOrThrow(FIELD_TYPES, r.u8(), "field type");
277
+ const status = atOrThrow(FIELD_STATUSES, r.u8(), "field status");
278
+ const value = optionalSigned(r.u64());
279
+ const fieldError = optionalSigned(r.u64());
280
+ const valueIsFinal = r.u8() !== 0;
281
+ fields.push({
282
+ name,
283
+ type: fieldType,
284
+ status,
285
+ value,
286
+ error: fieldError,
287
+ valueIsFinal
288
+ });
289
+ }
290
+ const kvCount = r.u32();
291
+ const kv = [];
292
+ for (let i = 0; i < kvCount; i++) kv.push({
293
+ key: r.str(),
294
+ value: r.bytes()
295
+ });
296
+ return {
297
+ id,
298
+ originalResourceId,
299
+ error,
300
+ kind,
301
+ type,
302
+ data,
303
+ inputsLocked: (flags & RES_INPUTS_LOCKED) !== 0,
304
+ outputsLocked: (flags & RES_OUTPUTS_LOCKED) !== 0,
305
+ resourceReady: (flags & RES_READY) !== 0,
306
+ final: (flags & RES_FINAL) !== 0,
307
+ fields,
308
+ kv
309
+ };
310
+ }
311
+ function failure(reason) {
312
+ return {
313
+ ok: false,
314
+ reason
315
+ };
316
+ }
317
+ function globalIdOf(id) {
318
+ return parseSignedResourceId(id).globalId;
319
+ }
320
+ function optionalGlobalIdOf(id) {
321
+ return isNotNullSignedResourceId(id) ? globalIdOf(id) : NO_REFERENCE;
322
+ }
323
+ function indexOfOrThrow(values, value, what) {
324
+ const idx = values.indexOf(value);
325
+ if (idx < 0) throw new Error(`unknown ${what}: ${String(value)}`);
326
+ return idx;
327
+ }
328
+ function atOrThrow(values, idx, what) {
329
+ if (idx < 0 || idx >= values.length) throw new Error(`unknown ${what} index: ${idx}`);
330
+ return values[idx];
331
+ }
332
+ /** Table-driven CRC-32 (IEEE). Node's `zlib.crc32` would do, but it landed in 22.2 and
333
+ * the repo's floor is 22, so this keeps the format readable on every supported runtime
334
+ * without adding a dependency. */
335
+ const CRC_TABLE = (() => {
336
+ const table = /* @__PURE__ */ new Int32Array(256);
337
+ for (let i = 0; i < 256; i++) {
338
+ let c = i;
339
+ for (let bit = 0; bit < 8; bit++) c = (c & 1) !== 0 ? 3988292384 ^ c >>> 1 : c >>> 1;
340
+ table[i] = c;
341
+ }
342
+ return table;
343
+ })();
344
+ function crc32(bytes) {
345
+ let c = 4294967295;
346
+ for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
347
+ return (c ^ 4294967295) >>> 0;
348
+ }
349
+ /** Growable little-endian writer. Doubling keeps a ten megabyte tree to a handful of
350
+ * reallocations. */
351
+ var Writer = class {
352
+ buf;
353
+ pos = 0;
354
+ constructor(initialBytes = 65536) {
355
+ this.buf = Buffer.allocUnsafe(Math.max(initialBytes, 16));
356
+ }
357
+ ensure(extra) {
358
+ if (this.pos + extra <= this.buf.length) return;
359
+ let size = this.buf.length;
360
+ while (size < this.pos + extra) size *= 2;
361
+ const next = Buffer.allocUnsafe(size);
362
+ this.buf.copy(next, 0, 0, this.pos);
363
+ this.buf = next;
364
+ }
365
+ u8(v) {
366
+ this.ensure(1);
367
+ this.pos = this.buf.writeUInt8(v, this.pos);
368
+ }
369
+ u16(v) {
370
+ this.ensure(2);
371
+ this.pos = this.buf.writeUInt16LE(v, this.pos);
372
+ }
373
+ u32(v) {
374
+ this.ensure(4);
375
+ this.pos = this.buf.writeUInt32LE(v, this.pos);
376
+ }
377
+ u64(v) {
378
+ this.ensure(8);
379
+ this.pos = this.buf.writeBigUInt64LE(v, this.pos);
380
+ }
381
+ /** u32-prefixed. For resource data and kv values, which have no small bound. */
382
+ bytes(b) {
383
+ this.u32(b.length);
384
+ this.ensure(b.length);
385
+ this.buf.set(b, this.pos);
386
+ this.pos += b.length;
387
+ }
388
+ /** u16-prefixed. For signatures, which are a hash and cannot approach 64 KB. */
389
+ shortBytes(b) {
390
+ if (b.length > 65535) throw new Error(`value too long for a short field: ${b.length}`);
391
+ this.u16(b.length);
392
+ this.ensure(b.length);
393
+ this.buf.set(b, this.pos);
394
+ this.pos += b.length;
395
+ }
396
+ str(s) {
397
+ this.bytes(Buffer.from(s, "utf8"));
398
+ }
399
+ result() {
400
+ return this.buf.subarray(0, this.pos);
401
+ }
402
+ };
403
+ /** Little-endian reader. Every accessor bounds-checks, so a truncated payload throws
404
+ * rather than reading past its end; callers turn that into a failure reason. */
405
+ var Reader = class {
406
+ src;
407
+ pos = 0;
408
+ view;
409
+ constructor(src) {
410
+ this.src = src;
411
+ this.view = new DataView(src.buffer, src.byteOffset, src.byteLength);
412
+ }
413
+ get position() {
414
+ return this.pos;
415
+ }
416
+ get atEnd() {
417
+ return this.pos === this.src.length;
418
+ }
419
+ take(n) {
420
+ if (n < 0 || this.pos + n > this.src.length) throw new Error(`read past end of snapshot: need ${n} at ${this.pos}`);
421
+ const at = this.pos;
422
+ this.pos += n;
423
+ return at;
424
+ }
425
+ skip(n) {
426
+ this.take(n);
427
+ }
428
+ u8() {
429
+ return this.view.getUint8(this.take(1));
430
+ }
431
+ u16() {
432
+ return this.view.getUint16(this.take(2), true);
433
+ }
434
+ u32() {
435
+ return this.view.getUint32(this.take(4), true);
436
+ }
437
+ u64() {
438
+ return this.view.getBigUint64(this.take(8), true);
439
+ }
440
+ /** Copies rather than returning a view. A view would keep the whole inflated payload alive
441
+ * for as long as the restored tree holds any one byte payload, including all the structure
442
+ * bytes it will never read again. The copy is transient; the retention would not be. */
443
+ bytes() {
444
+ const length = this.u32();
445
+ const at = this.take(length);
446
+ return Uint8Array.prototype.slice.call(this.src, at, at + length);
447
+ }
448
+ shortBytes() {
449
+ const length = this.u16();
450
+ const at = this.take(length);
451
+ return this.src.subarray(at, at + length);
452
+ }
453
+ str() {
454
+ return Buffer.from(this.bytes()).toString("utf8");
455
+ }
456
+ };
457
+ //#endregion
458
+ export { PERSISTED_TREE_SCHEMA_VERSION, captureTreeState, decodePersistedTree, encodePersistedTree, readPersistedTreeHeader, restoreTreeState };
459
+
460
+ //# sourceMappingURL=persisted_tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persisted_tree.js","names":[],"sources":["../src/persisted_tree.ts"],"sourcesContent":["/**\n * On-disk format for a tree mirror.\n *\n * Header and trailer are always plain bytes; only the payload is compressed. Every integer\n * is little-endian and fixed-width.\n *\n * ```text\n * +-- header (never compressed) -----------------------------+\n * | u32 magic 0x53544C50 (\"PLTS\", little-endian) |\n * | u16 schemaVersion |\n * | u16 flags bit0 = payload is deflated |\n * | u16 witnessLen + witness bytes (the root's signature) |\n * +-- payload (deflated, or raw if the flag is clear) -------+\n * | u32 rootCount, then u64 globalId per root |\n * | |\n * | u32 signatureCount, then per entry: |\n * | u64 globalId |\n * | u16 sigLen + signature bytes |\n * | |\n * | u32 resourceCount, then per resource: |\n * | u64 own globalId |\n * | u64 originalResourceId (0 = none) |\n * | u64 error (0 = none) |\n * | u8 kind index |\n * | str type.name |\n * | str type.version |\n * | u8 flags (hasData, inputsLocked, outputsLocked, |\n * | resourceReady, final) |\n * | [u32 len + data] only if hasData |\n * | u32 fieldCount, then per field: |\n * | str name |\n * | u8 field type index |\n * | u8 field status index |\n * | u64 value (0 = none) |\n * | u64 error (0 = none) |\n * | u8 valueIsFinal |\n * | u32 kvCount, then per entry: |\n * | str key |\n * | u32 len + value bytes |\n * +-- trailer (never compressed) ----------------------------+\n * | u32 payload length, as stored |\n * | u32 crc32 of the payload, as stored |\n * +----------------------------------------------------------+\n * ```\n *\n * Notes on the encoding:\n *\n * - `str` is a u32 length followed by UTF-8.\n * - A {@link SignedResourceId} is the string `\"<decimal globalId>|<signatureHex>\"`. Bodies\n * store only the global id; the signature comes from the side table. Global id 0 stands\n * for \"no reference\".\n * - `kind`, field type and field status are stored as indices into {@link KINDS},\n * {@link FIELD_TYPES} and {@link FIELD_STATUSES}. Those orderings are part of the format:\n * append only, never reorder.\n * - The payload length in the trailer, not the file size, delimits the payload.\n * - The witness is the root's signature at write time, and is outside the compressed section\n * so it can be read without inflating the payload ({@link readPersistedTreeHeader}).\n * - Reference counts, resource and data versions, change sources and the derived final state\n * are not stored. They are rebuilt on restore. The backend's `final` flag is stored, as\n * part of the body.\n */\n\nimport type {\n FieldData,\n FieldStatus,\n FieldType,\n FinalResourceDataPredicate,\n KeyValue,\n OptionalSignedResourceId,\n ResourceKind,\n ResourceSignature,\n SignedResourceId,\n} from \"@milaboratories/pl-client\";\nimport {\n createSignedResourceId,\n isNotNullSignedResourceId,\n NullSignedResourceId,\n parseSignedResourceId,\n toResourceSignature,\n} from \"@milaboratories/pl-client\";\nimport type { MiLogger } from \"@milaboratories/ts-helpers\";\nimport { deflate, inflate } from \"node:zlib\";\nimport { promisify } from \"node:util\";\nimport type { ExtendedResourceData } from \"./state\";\nimport { PlTreeState } from \"./state\";\n\nconst deflateAsync = promisify(deflate);\nconst inflateAsync = promisify(inflate);\n\n/** \"PLTS\", little-endian. Distinguishes our file from anything else that lands in the\n * snapshot directory, so a foreign file is rejected instead of parsed as garbage. */\nconst MAGIC = 0x53544c50;\n\n/** Bumped whenever the byte layout below changes in a way an older decoder would\n * misread. Only an exact match is accepted: the decoder's job here is to recognise a\n * format it cannot read, not to migrate it. Invalidation on rule changes is the cache\n * key's job (the middle layer's build stamp), not this number's. */\nexport const PERSISTED_TREE_SCHEMA_VERSION = 1;\n\n/** Payload is deflated. Absent means the payload is stored as-is, which is what a\n * periodic write falls back to if compression CPU ever becomes a problem. */\nconst FLAG_COMPRESSED = 1 << 0;\n\nconst HEADER_FIXED_BYTES = 4 /* magic */ + 2 /* schema */ + 2 /* flags */ + 2 /* witness len */;\nconst TRAILER_BYTES = 4 /* payload length */ + 4 /* checksum */;\n\n/** Ceiling on the inflated payload. The checksum only says the bytes are the ones that were\n * written, so a replaced file can pair a small, consistent payload with a ratio that\n * inflates to gigabytes: unbounded, that costs the process its memory instead of costing\n * one cold open. Far above anything real, the heaviest reference project being 10 MB\n * compressed against a 256 MB cap on the whole directory. */\nconst DEFAULT_MAX_PAYLOAD_BYTES = 512 * 1024 * 1024;\n\n/** Enum orderings are part of the on-disk format: append only, never reorder. An index\n * the decoder does not know is malformed input, which is why decoding is bounds-checked\n * rather than cast. */\nconst KINDS: readonly ResourceKind[] = [\"Structural\", \"Value\"];\nconst FIELD_TYPES: readonly FieldType[] = [\"Input\", \"Output\", \"Service\", \"OTW\", \"Dynamic\", \"MTW\"];\nconst FIELD_STATUSES: readonly FieldStatus[] = [\"Empty\", \"Assigned\", \"Resolved\"];\n\nconst RES_HAS_DATA = 1 << 0;\nconst RES_INPUTS_LOCKED = 1 << 1;\nconst RES_OUTPUTS_LOCKED = 1 << 2;\nconst RES_READY = 1 << 3;\nconst RES_FINAL = 1 << 4;\n\n/** Global id 0 stands for \"no reference\". A real resource can never have it:\n * `createSignedResourceId` rejects the null id, so the sentinel is unambiguous. */\nconst NO_REFERENCE = 0n;\n\n/**\n * A tree mirror as it sits on disk.\n *\n * Every reference inside {@link resources} is stored as a global id, with signatures held\n * apart in a side table that {@link decodePersistedTree} rejoins. That split is what lets a\n * snapshot outlive the signatures it was taken with: the bodies stay valid indefinitely, so\n * a future signature refresh can replace the table and reuse the same corpus.\n */\nexport type PersistedTree = {\n /** Session witness: the signature bytes of the tree's root at write time.\n * A resource signature is an HMAC over the id, the session and the colour, so byte\n * equality against a freshly resolved root signature means every other signature in the\n * table still addresses something. Inequality means they are all dead. */\n readonly witness: ResourceSignature;\n readonly roots: readonly SignedResourceId[];\n readonly resources: readonly ExtendedResourceData[];\n};\n\n/** The part of a snapshot readable without inflating the payload. Kept outside the\n * compressed section on purpose: a rotated session must be detectable without paying to\n * decompress ten megabytes that are about to be discarded. */\nexport type PersistedTreeHeader = {\n readonly schemaVersion: number;\n readonly witness: ResourceSignature;\n};\n\n/** Why a snapshot could not be read. Carried rather than thrown, because every one of\n * these means \"open cold\" and the caller wants to count which happened. */\nexport type PersistedTreeReadFailure =\n /** Not our file at all: wrong magic, or too short to hold a header. */\n | \"not-a-snapshot\"\n /** Written by a different schema version. */\n | \"unknown-schema\"\n /** File ends early: the length trailer disagrees with the actual size. */\n | \"truncated\"\n /** Checksum mismatch: the bytes are ours but damaged. */\n | \"checksum\"\n /** Structurally decodable but internally nonsensical (bad enum index, local id, ...). */\n | \"malformed\";\n\nexport type PersistedTreeReadResult<T> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly reason: PersistedTreeReadFailure };\n\nexport type DecodePersistedTreeOps = {\n /** Refuses a payload that inflates past this. Defaults to 512 MB. */\n readonly maxPayloadBytes?: number;\n};\n\nexport type EncodePersistedTreeOps = {\n /** Defaults to true. Turning it off trades roughly a factor of three in file size for\n * the compression CPU, which is the documented escape hatch for periodic writes. */\n readonly compress?: boolean;\n};\n\n//\n// Capture and restore\n//\n\n/**\n * Captures a live tree's state for persistence. `witness` is the signature of the root as\n * currently held, which is what a later open compares against to decide whether the\n * signatures in this snapshot are still live.\n *\n * Throws on an invalidated tree. {@link PlTreeState.dumpState} would happily read through\n * one, and a terminated or inconsistent tree is exactly what must not reach disk, so the\n * caller has to capture before it tears the tree down.\n */\nexport function captureTreeState(state: PlTreeState, witness: ResourceSignature): PersistedTree {\n if (!state.isValid) throw new Error(\"refusing to capture an invalidated tree\");\n\n // Field objects are copied, not referenced. `dumpState` hands back the tree's live\n // `PlTreeField` instances, which the next update mutates in place: a capture that merely\n // held them would drift under its holder between here and the encode, and a corpus whose\n // field points at a resource captured a moment earlier does not contain is unrestorable.\n // Today's caller happens to encode in the same synchronous turn, which is luck, not a\n // contract.\n const resources = state.dumpState().map((r) => ({\n ...r,\n fields: r.fields.map((f) => ({\n name: f.name,\n type: f.type,\n status: f.status,\n value: f.value,\n error: f.error,\n valueIsFinal: f.valueIsFinal,\n })),\n }));\n\n return { witness, roots: [...state.roots], resources };\n}\n\n/**\n * Rebuilds a tree from a snapshot, or returns undefined if the snapshot cannot be applied.\n *\n * The snapshot goes through {@link PlTreeState.updateFromResourceData}, the same call the\n * live loading path uses, so every invariant that path enforces is enforced here and cannot\n * drift from it. Reference counts are applied after the whole batch, so the order resources\n * appear in the file does not matter, and finality is recomputed from `finalPredicate`\n * rather than read from the file.\n *\n * The tree is built fresh and thrown away on failure: that call invalidates the tree it is\n * given when it finds an inconsistency, so restoring into a live tree would destroy a\n * working one instead of falling back to a cold open.\n */\nexport function restoreTreeState(\n snapshot: PersistedTree,\n finalPredicate: FinalResourceDataPredicate,\n ops: { roots?: Set<SignedResourceId>; logger?: MiLogger } = {},\n): PlTreeState | undefined {\n const roots = ops.roots ?? new Set(snapshot.roots);\n const restored = new PlTreeState(roots, finalPredicate);\n try {\n // allowOrphanInputs mirrors the live path. The check that matters for a snapshot is the\n // orphan-reference one, which runs either way and catches a corpus referencing an id it\n // does not carry.\n restored.updateFromResourceData([...snapshot.resources], { allowOrphanInputs: true });\n return restored;\n } catch (e: unknown) {\n ops.logger?.warn(\n `tree snapshot could not be restored, opening cold: ${e instanceof Error ? e.message : String(e)}`,\n );\n return undefined;\n }\n}\n\n//\n// Encoding\n//\n\n/** Serializes a tree mirror to the on-disk format. */\nexport async function encodePersistedTree(\n tree: PersistedTree,\n ops: EncodePersistedTreeOps = {},\n): Promise<Buffer> {\n const compress = ops.compress ?? true;\n\n const payload = writePayload(tree);\n const stored = compress ? await deflateAsync(payload) : payload;\n\n const header = new Writer(HEADER_FIXED_BYTES + tree.witness.length);\n header.u32(MAGIC);\n header.u16(PERSISTED_TREE_SCHEMA_VERSION);\n header.u16(compress ? FLAG_COMPRESSED : 0);\n header.shortBytes(tree.witness);\n\n const trailer = new Writer(TRAILER_BYTES);\n trailer.u32(stored.length);\n trailer.u32(crc32(stored));\n\n return Buffer.concat([header.result(), stored, trailer.result()]);\n}\n\nfunction writePayload(tree: PersistedTree): Buffer {\n // Signatures are collected from every id mentioned anywhere, not just from resource\n // bodies. originalResourceId is not refcounted by the tree, so a duplicate's original\n // can be referenced without being held, and its signature would otherwise be lost.\n const signatures = new Map<bigint, ResourceSignature>();\n const collect = (id: OptionalSignedResourceId) => {\n if (!isNotNullSignedResourceId(id)) return;\n const { globalId, signature } = parseSignedResourceId(id);\n\n // The tree keys its heap by the whole signed string, so one global id carrying two\n // different signatures is two distinct resources to the tree and one entry here. Refusing\n // is the only safe answer: last-write-wins would silently rewrite one resource's\n // references to point at the other. Unreachable for a single-root tree, where every\n // resource is signed under one colour, but a tree with several explicit seeds can legally\n // be served the same resource under two colours.\n const existing = signatures.get(globalId);\n if (existing !== undefined && !Buffer.from(existing).equals(Buffer.from(signature)))\n throw new Error(\n `cannot persist a tree holding global id ${globalId} under two different signatures`,\n );\n\n signatures.set(globalId, signature);\n };\n\n for (const root of tree.roots) collect(root);\n for (const res of tree.resources) {\n collect(res.id);\n collect(res.originalResourceId);\n collect(res.error);\n for (const f of res.fields) {\n collect(f.value);\n collect(f.error);\n }\n }\n\n const w = new Writer();\n\n w.u32(tree.roots.length);\n for (const root of tree.roots) w.u64(globalIdOf(root));\n\n w.u32(signatures.size);\n for (const [globalId, signature] of signatures) {\n w.u64(globalId);\n w.shortBytes(signature);\n }\n\n w.u32(tree.resources.length);\n for (const res of tree.resources) writeResource(w, res);\n\n return w.result();\n}\n\nfunction writeResource(w: Writer, res: ExtendedResourceData) {\n w.u64(globalIdOf(res.id));\n w.u64(optionalGlobalIdOf(res.originalResourceId));\n w.u64(optionalGlobalIdOf(res.error));\n\n w.u8(indexOfOrThrow(KINDS, res.kind, \"resource kind\"));\n w.str(res.type.name);\n w.str(res.type.version);\n\n w.u8(\n (res.data !== undefined ? RES_HAS_DATA : 0) |\n (res.inputsLocked ? RES_INPUTS_LOCKED : 0) |\n (res.outputsLocked ? RES_OUTPUTS_LOCKED : 0) |\n (res.resourceReady ? RES_READY : 0) |\n (res.final ? RES_FINAL : 0),\n );\n if (res.data !== undefined) w.bytes(res.data);\n\n w.u32(res.fields.length);\n for (const f of res.fields) {\n w.str(f.name);\n w.u8(indexOfOrThrow(FIELD_TYPES, f.type, \"field type\"));\n w.u8(indexOfOrThrow(FIELD_STATUSES, f.status, \"field status\"));\n w.u64(optionalGlobalIdOf(f.value));\n w.u64(optionalGlobalIdOf(f.error));\n w.u8(f.valueIsFinal ? 1 : 0);\n }\n\n w.u32(res.kv.length);\n for (const kv of res.kv) {\n w.str(kv.key);\n w.bytes(kv.value);\n }\n}\n\n//\n// Decoding\n//\n\n/** Reads magic, schema version and witness without touching the payload. Cheap enough to\n * run on every open, which is what makes a rotated-session miss cheap. */\nexport function readPersistedTreeHeader(\n bytes: Uint8Array,\n): PersistedTreeReadResult<PersistedTreeHeader> {\n try {\n if (bytes.length < HEADER_FIXED_BYTES + TRAILER_BYTES) return failure(\"not-a-snapshot\");\n\n const r = new Reader(bytes);\n if (r.u32() !== MAGIC) return failure(\"not-a-snapshot\");\n\n const schemaVersion = r.u16();\n r.u16(); // flags, only meaningful to the full decode\n const witness = toResourceSignature(r.shortBytes());\n\n if (schemaVersion !== PERSISTED_TREE_SCHEMA_VERSION) return failure(\"unknown-schema\");\n\n return { ok: true, value: { schemaVersion, witness } };\n } catch {\n // Any bounds violation while reading a fixed-size header means the file is not one.\n return failure(\"not-a-snapshot\");\n }\n}\n\n/** Reads a whole snapshot. Never throws: a torn, corrupt, foreign or unreadable file is\n * reported as a failure reason, so the caller opens cold instead of replaying garbage. */\nexport async function decodePersistedTree(\n bytes: Uint8Array,\n ops: DecodePersistedTreeOps = {},\n): Promise<PersistedTreeReadResult<PersistedTree>> {\n const header = readPersistedTreeHeader(bytes);\n if (!header.ok) return header;\n\n let payload: Uint8Array;\n try {\n const r = new Reader(bytes);\n r.skip(4 + 2); // magic, schema\n const flags = r.u16();\n r.shortBytes(); // witness, already read\n const payloadStart = r.position;\n\n // The trailer's length is what says where the payload ends. Deriving it from the file\n // size instead would accept a file with trailing garbage as intact.\n const trailer = new Reader(bytes);\n trailer.skip(bytes.length - TRAILER_BYTES);\n const payloadLength = trailer.u32();\n const checksum = trailer.u32();\n\n if (payloadStart + payloadLength !== bytes.length - TRAILER_BYTES) return failure(\"truncated\");\n\n const stored = bytes.subarray(payloadStart, payloadStart + payloadLength);\n if (crc32(stored) !== checksum) return failure(\"checksum\");\n\n payload =\n (flags & FLAG_COMPRESSED) !== 0\n ? await inflateAsync(stored, {\n maxOutputLength: ops.maxPayloadBytes ?? DEFAULT_MAX_PAYLOAD_BYTES,\n })\n : stored;\n } catch {\n // Includes inflate failures: a payload that passes its checksum but will not decompress,\n // or inflates past the ceiling, is damaged in a way we cannot distinguish from\n // corruption. Either way the answer is the same, open cold.\n return failure(\"checksum\");\n }\n\n try {\n return { ok: true, value: readPayload(payload, header.value.witness) };\n } catch {\n return failure(\"malformed\");\n }\n}\n\nfunction readPayload(payload: Uint8Array, witness: ResourceSignature): PersistedTree {\n const r = new Reader(payload);\n\n const rootCount = r.u32();\n const rootIds: bigint[] = [];\n for (let i = 0; i < rootCount; i++) rootIds.push(r.u64());\n\n const signatureCount = r.u32();\n const signatures = new Map<bigint, ResourceSignature>();\n for (let i = 0; i < signatureCount; i++) {\n const globalId = r.u64();\n signatures.set(globalId, toResourceSignature(r.shortBytes()));\n }\n\n /** Rejoins a stored global id with its signature. A reference with no table entry is\n * malformed rather than recoverable: an unsigned id addresses nothing. */\n const signed = (globalId: bigint): SignedResourceId => {\n const signature = signatures.get(globalId);\n if (signature === undefined) throw new Error(`no signature stored for global id ${globalId}`);\n return createSignedResourceId(globalId, signature);\n };\n const optionalSigned = (globalId: bigint): OptionalSignedResourceId =>\n globalId === NO_REFERENCE ? NullSignedResourceId : signed(globalId);\n\n const roots = rootIds.map(signed);\n\n const resourceCount = r.u32();\n const resources: ExtendedResourceData[] = [];\n for (let i = 0; i < resourceCount; i++) resources.push(readResource(r, signed, optionalSigned));\n\n if (!r.atEnd) throw new Error(\"trailing bytes in snapshot payload\");\n\n return { witness, roots, resources };\n}\n\nfunction readResource(\n r: Reader,\n signed: (globalId: bigint) => SignedResourceId,\n optionalSigned: (globalId: bigint) => OptionalSignedResourceId,\n): ExtendedResourceData {\n const id = signed(r.u64());\n const originalResourceId = optionalSigned(r.u64());\n const error = optionalSigned(r.u64());\n\n const kind = atOrThrow(KINDS, r.u8(), \"resource kind\");\n const type = { name: r.str(), version: r.str() };\n\n const flags = r.u8();\n const data = (flags & RES_HAS_DATA) !== 0 ? r.bytes() : undefined;\n\n const fieldCount = r.u32();\n const fields: FieldData[] = [];\n for (let i = 0; i < fieldCount; i++) {\n const name = r.str();\n const fieldType = atOrThrow(FIELD_TYPES, r.u8(), \"field type\");\n const status = atOrThrow(FIELD_STATUSES, r.u8(), \"field status\");\n const value = optionalSigned(r.u64());\n const fieldError = optionalSigned(r.u64());\n const valueIsFinal = r.u8() !== 0;\n fields.push({ name, type: fieldType, status, value, error: fieldError, valueIsFinal });\n }\n\n const kvCount = r.u32();\n const kv: KeyValue[] = [];\n for (let i = 0; i < kvCount; i++) kv.push({ key: r.str(), value: r.bytes() });\n\n return {\n id,\n originalResourceId,\n error,\n kind,\n type,\n data,\n inputsLocked: (flags & RES_INPUTS_LOCKED) !== 0,\n outputsLocked: (flags & RES_OUTPUTS_LOCKED) !== 0,\n resourceReady: (flags & RES_READY) !== 0,\n final: (flags & RES_FINAL) !== 0,\n fields,\n kv,\n };\n}\n\n//\n// Helpers\n//\n\nfunction failure(reason: PersistedTreeReadFailure): {\n ok: false;\n reason: PersistedTreeReadFailure;\n} {\n return { ok: false, reason };\n}\n\nfunction globalIdOf(id: SignedResourceId): bigint {\n return parseSignedResourceId(id).globalId;\n}\n\nfunction optionalGlobalIdOf(id: OptionalSignedResourceId): bigint {\n return isNotNullSignedResourceId(id) ? globalIdOf(id) : NO_REFERENCE;\n}\n\nfunction indexOfOrThrow<T>(values: readonly T[], value: T, what: string): number {\n const idx = values.indexOf(value);\n if (idx < 0) throw new Error(`unknown ${what}: ${String(value)}`);\n return idx;\n}\n\nfunction atOrThrow<T>(values: readonly T[], idx: number, what: string): T {\n if (idx < 0 || idx >= values.length) throw new Error(`unknown ${what} index: ${idx}`);\n return values[idx];\n}\n\n/** Table-driven CRC-32 (IEEE). Node's `zlib.crc32` would do, but it landed in 22.2 and\n * the repo's floor is 22, so this keeps the format readable on every supported runtime\n * without adding a dependency. */\nconst CRC_TABLE = (() => {\n const table = new Int32Array(256);\n for (let i = 0; i < 256; i++) {\n let c = i;\n for (let bit = 0; bit < 8; bit++) c = (c & 1) !== 0 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n table[i] = c;\n }\n return table;\n})();\n\nfunction crc32(bytes: Uint8Array): number {\n let c = 0xffffffff;\n for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]) & 0xff] ^ (c >>> 8);\n return (c ^ 0xffffffff) >>> 0;\n}\n\n/** Growable little-endian writer. Doubling keeps a ten megabyte tree to a handful of\n * reallocations. */\nclass Writer {\n private buf: Buffer;\n private pos = 0;\n\n constructor(initialBytes = 1 << 16) {\n this.buf = Buffer.allocUnsafe(Math.max(initialBytes, 16));\n }\n\n private ensure(extra: number) {\n if (this.pos + extra <= this.buf.length) return;\n let size = this.buf.length;\n while (size < this.pos + extra) size *= 2;\n const next = Buffer.allocUnsafe(size);\n this.buf.copy(next, 0, 0, this.pos);\n this.buf = next;\n }\n\n u8(v: number) {\n this.ensure(1);\n this.pos = this.buf.writeUInt8(v, this.pos);\n }\n\n u16(v: number) {\n this.ensure(2);\n this.pos = this.buf.writeUInt16LE(v, this.pos);\n }\n\n u32(v: number) {\n this.ensure(4);\n this.pos = this.buf.writeUInt32LE(v, this.pos);\n }\n\n u64(v: bigint) {\n this.ensure(8);\n this.pos = this.buf.writeBigUInt64LE(v, this.pos);\n }\n\n /** u32-prefixed. For resource data and kv values, which have no small bound. */\n bytes(b: Uint8Array) {\n this.u32(b.length);\n this.ensure(b.length);\n this.buf.set(b, this.pos);\n this.pos += b.length;\n }\n\n /** u16-prefixed. For signatures, which are a hash and cannot approach 64 KB. */\n shortBytes(b: Uint8Array) {\n if (b.length > 0xffff) throw new Error(`value too long for a short field: ${b.length}`);\n this.u16(b.length);\n this.ensure(b.length);\n this.buf.set(b, this.pos);\n this.pos += b.length;\n }\n\n str(s: string) {\n this.bytes(Buffer.from(s, \"utf8\"));\n }\n\n result(): Buffer {\n return this.buf.subarray(0, this.pos);\n }\n}\n\n/** Little-endian reader. Every accessor bounds-checks, so a truncated payload throws\n * rather than reading past its end; callers turn that into a failure reason. */\nclass Reader {\n private pos = 0;\n private readonly view: DataView;\n\n constructor(private readonly src: Uint8Array) {\n this.view = new DataView(src.buffer, src.byteOffset, src.byteLength);\n }\n\n get position(): number {\n return this.pos;\n }\n\n get atEnd(): boolean {\n return this.pos === this.src.length;\n }\n\n private take(n: number): number {\n if (n < 0 || this.pos + n > this.src.length)\n throw new Error(`read past end of snapshot: need ${n} at ${this.pos}`);\n const at = this.pos;\n this.pos += n;\n return at;\n }\n\n skip(n: number) {\n this.take(n);\n }\n\n u8(): number {\n return this.view.getUint8(this.take(1));\n }\n\n u16(): number {\n return this.view.getUint16(this.take(2), true);\n }\n\n u32(): number {\n return this.view.getUint32(this.take(4), true);\n }\n\n u64(): bigint {\n return this.view.getBigUint64(this.take(8), true);\n }\n\n /** Copies rather than returning a view. A view would keep the whole inflated payload alive\n * for as long as the restored tree holds any one byte payload, including all the structure\n * bytes it will never read again. The copy is transient; the retention would not be. */\n bytes(): Uint8Array {\n const length = this.u32();\n const at = this.take(length);\n return Uint8Array.prototype.slice.call(this.src, at, at + length);\n }\n\n shortBytes(): Uint8Array {\n const length = this.u16();\n const at = this.take(length);\n return this.src.subarray(at, at + length);\n }\n\n str(): string {\n return Buffer.from(this.bytes()).toString(\"utf8\");\n }\n}\n"],"mappings":"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"}
@@ -3,14 +3,13 @@ import { ResourceType, SignedResourceId } from "@milaboratories/pl-client";
3
3
  import { ComputableCtx } from "@milaboratories/computable";
4
4
  import { Optional } from "utility-types";
5
5
  import { ZodType, z } from "zod";
6
-
7
6
  //#region src/snapshot.d.ts
8
7
  /**
9
8
  * A DTO that can be generated from a tree node to make a snapshot of specific parts of it's state.
10
9
  * Such snapshots can then be used in core that requires this information without the need of
11
10
  * retrieving state from the tree.
12
11
  */
13
- type ResourceSnapshot<Data = undefined, Fields extends Record<string, SignedResourceId | undefined> | undefined = undefined, KV extends Record<string, unknown> | undefined = undefined> = {
12
+ export type ResourceSnapshot<Data = undefined, Fields extends Record<string, SignedResourceId | undefined> | undefined = undefined, KV extends Record<string, unknown> | undefined = undefined> = {
14
13
  readonly id: SignedResourceId;
15
14
  readonly type: ResourceType;
16
15
  readonly data: Data;
@@ -18,13 +17,13 @@ type ResourceSnapshot<Data = undefined, Fields extends Record<string, SignedReso
18
17
  readonly kv: KV;
19
18
  };
20
19
  /** Request that we'll pass to getResourceSnapshot function. We infer the type of ResourceSnapshot from this. */
21
- type ResourceSnapshotSchema<Data extends ZodType | "raw" | undefined = undefined, Fields extends Record<string, boolean> | undefined = undefined, KV extends Record<string, ZodType | "raw"> | undefined = undefined> = {
20
+ export type ResourceSnapshotSchema<Data extends ZodType | "raw" | undefined = undefined, Fields extends Record<string, boolean> | undefined = undefined, KV extends Record<string, ZodType | "raw"> | undefined = undefined> = {
22
21
  readonly data: Data;
23
22
  readonly fields: Fields;
24
23
  readonly kv: KV;
25
24
  };
26
25
  /** Creates ResourceSnapshotSchema. It converts an optional schema type to schema type. */
27
- declare function rsSchema<const Data extends ZodType | "raw" | undefined = undefined, const Fields extends Record<string, boolean> | undefined = undefined, const KV extends Record<string, ZodType | "raw"> | undefined = undefined>(schema: Optional<ResourceSnapshotSchema<Data, Fields, KV>>): ResourceSnapshotSchema<Data, Fields, KV>;
26
+ export declare function rsSchema<const Data extends ZodType | "raw" | undefined = undefined, const Fields extends Record<string, boolean> | undefined = undefined, const KV extends Record<string, ZodType | "raw"> | undefined = undefined>(schema: Optional<ResourceSnapshotSchema<Data, Fields, KV>>): ResourceSnapshotSchema<Data, Fields, KV>;
28
27
  /** The most generic type of ResourceSnapshotSchema. */
29
28
  type ResourceSnapshotSchemaGeneric = ResourceSnapshotSchema<ZodType | "raw" | undefined, Record<string, boolean> | undefined, Record<string, ZodType | "raw"> | undefined>;
30
29
  /**
@@ -39,35 +38,34 @@ type InferDataType<Data extends ZodType | "raw" | undefined> = Data extends "raw
39
38
  * If it's false and doesn't exist, we'll return undefined.
40
39
  * If Fields type is undefined, we won't set fields at all.
41
40
  */
42
- type InferFieldsType<Fields extends Record<string, boolean> | undefined> = Fields extends undefined ? undefined : { [FieldName in keyof Fields]: Fields[FieldName] extends true ? SignedResourceId : SignedResourceId | undefined };
41
+ type InferFieldsType<Fields extends Record<string, boolean> | undefined> = Fields extends undefined ? undefined : { [FieldName in keyof Fields]: Fields[FieldName] extends true ? SignedResourceId : SignedResourceId | undefined; };
43
42
  /**
44
43
  * If KV is undefined, won't set it.
45
44
  * If one of values is Zod, we'll get KV and converts it to Zod schema.
46
45
  * If the value is 'raw', just returns bytes.
47
46
  */
48
- type InferKVType<KV extends Record<string, ZodType | "raw"> | undefined> = KV extends undefined ? undefined : { [FieldName in keyof KV]: KV[FieldName] extends ZodType ? z.infer<KV[FieldName]> : Uint8Array };
47
+ type InferKVType<KV extends Record<string, ZodType | "raw"> | undefined> = KV extends undefined ? undefined : { [FieldName in keyof KV]: KV[FieldName] extends ZodType ? z.infer<KV[FieldName]> : Uint8Array; };
49
48
  /** Infer ResourceSnapshot from ResourceShapshotSchema, S can be any ResourceSnapshotSchema. */
50
- type InferSnapshot<S extends ResourceSnapshotSchemaGeneric> = ResourceSnapshot<InferDataType<S["data"]>, InferFieldsType<S["fields"]>, InferKVType<S["kv"]>>;
49
+ export type InferSnapshot<S extends ResourceSnapshotSchemaGeneric> = ResourceSnapshot<InferDataType<S["data"]>, InferFieldsType<S["fields"]>, InferKVType<S["kv"]>>;
51
50
  /** Gets a ResourceSnapshot from PlTreeEntry. */
52
- declare function makeResourceSnapshot<Schema extends ResourceSnapshotSchemaGeneric>(res: PlTreeEntry, schema: Schema, ctx: ComputableCtx): InferSnapshot<Schema>;
53
- declare function makeResourceSnapshot<Schema extends ResourceSnapshotSchemaGeneric>(res: PlTreeEntryAccessor | PlTreeNodeAccessor, schema: Schema): InferSnapshot<Schema>;
51
+ export declare function makeResourceSnapshot<Schema extends ResourceSnapshotSchemaGeneric>(res: PlTreeEntry, schema: Schema, ctx: ComputableCtx): InferSnapshot<Schema>;
52
+ export declare function makeResourceSnapshot<Schema extends ResourceSnapshotSchemaGeneric>(res: PlTreeEntryAccessor | PlTreeNodeAccessor, schema: Schema): InferSnapshot<Schema>;
54
53
  /** @deprecated */
55
- type ResourceWithData = {
54
+ export type ResourceWithData = {
56
55
  readonly id: SignedResourceId;
57
56
  readonly type: ResourceType;
58
57
  readonly fields: Map<string, SignedResourceId | undefined>;
59
58
  readonly data?: Uint8Array;
60
59
  };
61
60
  /** @deprecated */
62
- declare function treeEntryToResourceWithData(res: PlTreeEntry | ResourceWithData, fields: string[], ctx: ComputableCtx): ResourceWithData;
61
+ export declare function treeEntryToResourceWithData(res: PlTreeEntry | ResourceWithData, fields: string[], ctx: ComputableCtx): ResourceWithData;
63
62
  /** @deprecated */
64
- type ResourceWithMetadata = {
63
+ export type ResourceWithMetadata = {
65
64
  readonly id: SignedResourceId;
66
65
  readonly type: ResourceType;
67
66
  readonly metadata: Record<string, any>;
68
67
  };
69
68
  /** @deprecated */
70
- declare function treeEntryToResourceWithMetadata(res: PlTreeEntry | ResourceWithMetadata, mdKeys: string[], ctx: ComputableCtx): ResourceWithMetadata;
69
+ export declare function treeEntryToResourceWithMetadata(res: PlTreeEntry | ResourceWithMetadata, mdKeys: string[], ctx: ComputableCtx): ResourceWithMetadata;
71
70
  //#endregion
72
- export { InferSnapshot, ResourceSnapshot, ResourceSnapshotSchema, ResourceWithData, ResourceWithMetadata, makeResourceSnapshot, rsSchema, treeEntryToResourceWithData, treeEntryToResourceWithMetadata };
73
71
  //# sourceMappingURL=snapshot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.d.ts","names":[],"sources":["../src/snapshot.ts"],"mappings":";;;;;;;;AAaA;;;;KAAY,gBAAA,kCAEK,MAAA,SAAe,gBAAA,kDACnB,MAAA;EAAA,SAEF,EAAA,EAAI,gBAAA;EAAA,SACJ,IAAA,EAAM,YAAA;EAAA,SACN,IAAA,EAAM,IAAA;EAAA,SACN,MAAA,EAAQ,MAAA;EAAA,SACR,EAAA,EAAI,EAAA;AAAA;;KAWH,sBAAA,cACG,OAAA,iDACE,MAAA,sDACJ,MAAA,SAAe,OAAA;EAAA,SAEjB,IAAA,EAAM,IAAA;EAAA,SACN,MAAA,EAAQ,MAAA;EAAA,SACR,EAAA,EAAI,EAAA;AAAA;;iBAIC,QAAA,oBACK,OAAA,uDACE,MAAA,4DACJ,MAAA,SAAe,OAAA,mCAEhC,MAAA,EAAQ,QAAA,CAAS,sBAAA,CAAuB,IAAA,EAAM,MAAA,EAAQ,EAAA,KACrD,sBAAA,CAAuB,IAAA,EAAM,MAAA,EAAQ,EAAA;;KAKnC,6BAAA,GAAgC,sBAAA,CACnC,OAAA,sBACA,MAAA,+BACA,MAAA,SAAe,OAAA;;;;;;KAQZ,aAAA,cAA2B,OAAA,wBAA+B,IAAA,iBAC3D,UAAA,GACA,IAAA,SAAa,OAAA,GACX,CAAA,CAAE,KAAA,CAAM,IAAA;;;;AA/CG;AAWjB;;KA6CK,eAAA,gBAA+B,MAAA,iCAAuC,MAAA,uDAGjD,MAAA,GAAS,MAAA,CAAO,SAAA,iBAChC,gBAAA,GACA,gBAAA;;;;;;KAQL,WAAA,YAAuB,MAAA,SAAe,OAAA,yBAAgC,EAAA,uDAGjD,EAAA,GAAK,EAAA,CAAG,SAAA,UAAmB,OAAA,GAAU,CAAA,CAAE,KAAA,CAAM,EAAA,CAAG,SAAA,KAAc,UAAA;;KAI5E,aAAA,WAAwB,6BAAA,IAAiC,gBAAA,CACnE,aAAA,CAAc,CAAA,WACd,eAAA,CAAgB,CAAA,aAChB,WAAA,CAAY,CAAA;;iBAIE,oBAAA,gBAAoC,6BAAA,EAClD,GAAA,EAAK,WAAA,EACL,MAAA,EAAQ,MAAA,EACR,GAAA,EAAK,aAAA,GACJ,aAAA,CAAc,MAAA;AAAA,iBACD,oBAAA,gBAAoC,6BAAA,EAClD,GAAA,EAAK,mBAAA,GAAsB,kBAAA,EAC3B,MAAA,EAAQ,MAAA,GACP,aAAA,CAAc,MAAA;;KAqDL,gBAAA;EAAA,SACD,EAAA,EAAI,gBAAA;EAAA,SACJ,IAAA,EAAM,YAAA;EAAA,SACN,MAAA,EAAQ,GAAA,SAAY,gBAAA;EAAA,SACpB,IAAA,GAAO,UAAA;AAAA;;iBAIF,2BAAA,CACd,GAAA,EAAK,WAAA,GAAc,gBAAA,EACnB,MAAA,YACA,GAAA,EAAK,aAAA,GACJ,gBAAA;AAtIH;AAAA,KA2JY,oBAAA;EAAA,SACD,EAAA,EAAI,gBAAA;EAAA,SACJ,IAAA,EAAM,YAAA;EAAA,SACN,QAAA,EAAU,MAAA;AAAA;;iBAIL,+BAAA,CACd,GAAA,EAAK,WAAA,GAAc,oBAAA,EACnB,MAAA,YACA,GAAA,EAAK,aAAA,GACJ,oBAAA"}
1
+ {"version":3,"file":"snapshot.d.ts","names":[],"sources":["../src/snapshot.ts"],"mappings":";;;;;;;;;;;YAaY,iBACV,kBACA,eAAe,eAAe,uDAC9B,WAAW;WAEF,IAAI;WACJ,MAAM;WACN,MAAM;WACN,QAAQ;WACR,IAAI;;;YAWH,uBACV,aAAa,yCACb,eAAe,iDACf,WAAW,eAAe;WAEjB,MAAM;WACN,QAAQ;WACR,IAAI;;;wBAIC,eACR,aAAa,+CACb,eAAe,uDACf,WAAW,eAAe,0CAEhC,QAAQ,SAAS,uBAAuB,MAAM,QAAQ,OACrD,uBAAuB,MAAM,QAAQ;;KAKnC,gCAAgC,uBACnC,6BACA,qCACA,eAAe;;;;;;KAQZ,cAAc,aAAa,+BAA+B,qBAC3D,aACA,aAAa,UACX,EAAE,MAAM;;;;;;;KAST,gBAAgB,eAAe,uCAAuC,0CAGpE,mBAAmB,SAAS,OAAO,0BAChC,mBACA;;;;;;KAQL,YAAY,WAAW,eAAe,gCAAgC,sCAGpE,mBAAmB,KAAK,GAAG,mBAAmB,UAAU,EAAE,MAAM,GAAG,cAAc;;YAI5E,cAAc,UAAU,iCAAiC,iBACnE,cAAc,YACd,gBAAgB,cAChB,YAAY;;wBAIE,qBAAqB,eAAe,+BAClD,KAAK,aACL,QAAQ,QACR,KAAK,gBACJ,cAAc;wBACD,qBAAqB,eAAe,+BAClD,KAAK,sBAAsB,oBAC3B,QAAQ,SACP,cAAc;;YAqDL;WACD,IAAI;WACJ,MAAM;WACN,QAAQ,YAAY;WACpB,OAAO;;;wBAIF,4BACd,KAAK,cAAc,kBACnB,kBACA,KAAK,gBACJ;;YAqBS;WACD,IAAI;WACJ,MAAM;WACN,UAAU;;;wBAIL,gCACd,KAAK,cAAc,sBACnB,kBACA,KAAK,gBACJ"}
package/dist/state.cjs CHANGED
@@ -279,6 +279,13 @@ var PlTreeState = class {
279
279
  forEachResource(cb) {
280
280
  this.resources.forEach((v) => cb(v));
281
281
  }
282
+ /** False once the tree has been invalidated (an inconsistent update, or termination of
283
+ * its synchronization loop). {@link dumpState} deliberately reads through an invalid
284
+ * tree, so anything persisting that dump must check this first: the contents of an
285
+ * invalidated tree are not something to write to disk. */
286
+ get isValid() {
287
+ return this._isValid;
288
+ }
282
289
  checkValid() {
283
290
  if (!this._isValid) throw new Error(this.invalidationMessage ?? "tree is in invalid state");
284
291
  }