@earendil-works/chord 0.0.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 (98) hide show
  1. package/README.md +205 -0
  2. package/dist/api.d.ts +15 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +72 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/bundler.d.ts +6 -0
  7. package/dist/bundler.d.ts.map +1 -0
  8. package/dist/bundler.js +3 -0
  9. package/dist/bundler.js.map +1 -0
  10. package/dist/context/index.d.ts +24 -0
  11. package/dist/context/index.d.ts.map +1 -0
  12. package/dist/context/index.js +97 -0
  13. package/dist/context/index.js.map +1 -0
  14. package/dist/delta/index.d.ts +119 -0
  15. package/dist/delta/index.d.ts.map +1 -0
  16. package/dist/delta/index.js +1213 -0
  17. package/dist/delta/index.js.map +1 -0
  18. package/dist/facets/host.d.ts +12 -0
  19. package/dist/facets/host.d.ts.map +1 -0
  20. package/dist/facets/host.js +763 -0
  21. package/dist/facets/host.js.map +1 -0
  22. package/dist/facets/loader.d.ts +3 -0
  23. package/dist/facets/loader.d.ts.map +1 -0
  24. package/dist/facets/loader.js +5 -0
  25. package/dist/facets/loader.js.map +1 -0
  26. package/dist/index.d.ts +11 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +10 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/json.d.ts +4 -0
  31. package/dist/json.d.ts.map +1 -0
  32. package/dist/json.js +57 -0
  33. package/dist/json.js.map +1 -0
  34. package/dist/node/bundle-loader.d.ts +30 -0
  35. package/dist/node/bundle-loader.d.ts.map +1 -0
  36. package/dist/node/bundle-loader.js +377 -0
  37. package/dist/node/bundle-loader.js.map +1 -0
  38. package/dist/node/bundle.d.ts +26 -0
  39. package/dist/node/bundle.d.ts.map +1 -0
  40. package/dist/node/bundle.js +183 -0
  41. package/dist/node/bundle.js.map +1 -0
  42. package/dist/node/manifest.d.ts +36 -0
  43. package/dist/node/manifest.d.ts.map +1 -0
  44. package/dist/node/manifest.js +6 -0
  45. package/dist/node/manifest.js.map +1 -0
  46. package/dist/node/package.d.ts +15 -0
  47. package/dist/node/package.d.ts.map +1 -0
  48. package/dist/node/package.js +199 -0
  49. package/dist/node/package.js.map +1 -0
  50. package/dist/node.d.ts +5 -0
  51. package/dist/node.d.ts.map +1 -0
  52. package/dist/node.js +3 -0
  53. package/dist/node.js.map +1 -0
  54. package/dist/services/consumer.d.ts +11 -0
  55. package/dist/services/consumer.d.ts.map +1 -0
  56. package/dist/services/consumer.js +537 -0
  57. package/dist/services/consumer.js.map +1 -0
  58. package/dist/services/errors.d.ts +8 -0
  59. package/dist/services/errors.d.ts.map +1 -0
  60. package/dist/services/errors.js +22 -0
  61. package/dist/services/errors.js.map +1 -0
  62. package/dist/services/handle.d.ts +15 -0
  63. package/dist/services/handle.d.ts.map +1 -0
  64. package/dist/services/handle.js +99 -0
  65. package/dist/services/handle.js.map +1 -0
  66. package/dist/services/instances.d.ts +25 -0
  67. package/dist/services/instances.d.ts.map +1 -0
  68. package/dist/services/instances.js +137 -0
  69. package/dist/services/instances.js.map +1 -0
  70. package/dist/services/loopback.d.ts +5 -0
  71. package/dist/services/loopback.d.ts.map +1 -0
  72. package/dist/services/loopback.js +15 -0
  73. package/dist/services/loopback.js.map +1 -0
  74. package/dist/services/provider.d.ts +37 -0
  75. package/dist/services/provider.d.ts.map +1 -0
  76. package/dist/services/provider.js +472 -0
  77. package/dist/services/provider.js.map +1 -0
  78. package/dist/services/state-codec.d.ts +15 -0
  79. package/dist/services/state-codec.d.ts.map +1 -0
  80. package/dist/services/state-codec.js +115 -0
  81. package/dist/services/state-codec.js.map +1 -0
  82. package/dist/services/state-internals.d.ts +11 -0
  83. package/dist/services/state-internals.d.ts.map +1 -0
  84. package/dist/services/state-internals.js +10 -0
  85. package/dist/services/state-internals.js.map +1 -0
  86. package/dist/services/state.d.ts +23 -0
  87. package/dist/services/state.d.ts.map +1 -0
  88. package/dist/services/state.js +124 -0
  89. package/dist/services/state.js.map +1 -0
  90. package/dist/services/wire.d.ts +60 -0
  91. package/dist/services/wire.d.ts.map +1 -0
  92. package/dist/services/wire.js +173 -0
  93. package/dist/services/wire.js.map +1 -0
  94. package/dist/types.d.ts +203 -0
  95. package/dist/types.d.ts.map +1 -0
  96. package/dist/types.js +2 -0
  97. package/dist/types.js.map +1 -0
  98. package/package.json +72 -0
@@ -0,0 +1,1213 @@
1
+ // ─── Classification ──────────────────────────────────────────────────────────
2
+ export const isReplace = (op) => op[0] === "r";
3
+ /**
4
+ * A batch begins with a replacement. Flush guarantees `r` is at index 0 or absent,
5
+ * so this is exact rather than a heuristic.
6
+ */
7
+ export const isBase = (ops) => ops.length > 0 && ops[0][0] === "r";
8
+ // ─── Overlap ─────────────────────────────────────────────────────────────────
9
+ /**
10
+ * Longest suffix of `a` that is a prefix of `b`. Probes with indexOf and verifies
11
+ * exact substring equality, so the hot loops are native. A hand-written KMP is
12
+ * asymptotically equivalent and much slower in practice.
13
+ *
14
+ * Always correct: the returned n satisfies a.slice(a.length - n) === b.slice(0, n).
15
+ */
16
+ export function overlap(a, b, scan, probe = 64, maxCandidates = 8) {
17
+ if (a.length === 0 || b.length === 0 || scan === 0)
18
+ return 0;
19
+ const tail = a.length > scan ? a.slice(a.length - scan) : a;
20
+ // A probe of length h can only find overlaps of at least h — the head must
21
+ // actually occur in `a`. So try a long head first (few candidates, and it
22
+ // catches the large overlaps a rolling window produces), then fall back to one
23
+ // character, which finds any overlap at the cost of more candidates.
24
+ //
25
+ // Candidates are bounded because repetitive output — a build log, or any run of
26
+ // one character — makes a long head match at thousands of positions. Giving up
27
+ // returns 0, which emits a set: larger, never wrong.
28
+ for (const h of [Math.min(probe, b.length), 1]) {
29
+ const head = b.slice(0, h);
30
+ let tried = 0;
31
+ for (let k = tail.indexOf(head); k !== -1; k = tail.indexOf(head, k + 1)) {
32
+ if (++tried > maxCandidates)
33
+ break;
34
+ const n = tail.length - k;
35
+ if (n <= b.length && tail.slice(k) === b.slice(0, n))
36
+ return n;
37
+ }
38
+ if (h === 1)
39
+ break;
40
+ }
41
+ return 0;
42
+ }
43
+ const isObj = (value) => value !== null && typeof value === "object";
44
+ const cloneJson = (value) => {
45
+ if (!isObj(value))
46
+ return value;
47
+ if (Array.isArray(value))
48
+ return value.map((item) => cloneJson(item));
49
+ const result = Object.create(Object.getPrototypeOf(value) === null ? null : Object.prototype);
50
+ for (const [key, child] of Object.entries(value)) {
51
+ Object.defineProperty(result, key, {
52
+ value: cloneJson(child),
53
+ writable: true,
54
+ enumerable: true,
55
+ configurable: true,
56
+ });
57
+ }
58
+ return result;
59
+ };
60
+ const INDEX = /^(?:0|[1-9]\d*)$/;
61
+ const norm = (target, key) => typeof key === "symbol" ? key : Array.isArray(target) && INDEX.test(key) ? Number(key) : key;
62
+ const MUTATORS = new Set(["push", "pop", "shift", "unshift", "splice", "sort", "reverse", "fill", "copyWithin"]);
63
+ const MISSING = Symbol("missing");
64
+ const dirtyNode = () => ({ children: new Map() });
65
+ const spliceItems = (target, index, remove, items) => {
66
+ const removed = Reflect.apply(Array.prototype.splice, target, [index, remove]);
67
+ const chunkSize = 10_000;
68
+ for (let offset = 0; offset < items.length; offset += chunkSize) {
69
+ Reflect.apply(Array.prototype.splice, target, [index + offset, 0, ...items.slice(offset, offset + chunkSize)]);
70
+ }
71
+ return removed;
72
+ };
73
+ const jsonEqual = (left, right) => {
74
+ if (left === right)
75
+ return true;
76
+ if (!isObj(left) || !isObj(right) || Array.isArray(left) !== Array.isArray(right))
77
+ return false;
78
+ if (Array.isArray(left) && Array.isArray(right)) {
79
+ if (left.length !== right.length)
80
+ return false;
81
+ for (let index = 0; index < left.length; index++) {
82
+ if (!jsonEqual(left[index], right[index]))
83
+ return false;
84
+ }
85
+ return true;
86
+ }
87
+ const leftObject = left;
88
+ const rightObject = right;
89
+ const leftKeys = Object.keys(leftObject);
90
+ const rightKeys = Object.keys(rightObject);
91
+ if (leftKeys.length !== rightKeys.length)
92
+ return false;
93
+ for (const key of leftKeys) {
94
+ if (!Object.hasOwn(rightObject, key) || !jsonEqual(leftObject[key], rightObject[key]))
95
+ return false;
96
+ }
97
+ return true;
98
+ };
99
+ const ownValue = (value, segment) => {
100
+ if (!isObj(value) || !Object.hasOwn(value, segment))
101
+ return MISSING;
102
+ return value[segment];
103
+ };
104
+ const emitSet = (path, value, out) => {
105
+ const snapshot = cloneJson(value);
106
+ if (path.length === 0)
107
+ out.push(["r", snapshot]);
108
+ else
109
+ out.push(["s", [...path], snapshot]);
110
+ };
111
+ const emitDelete = (path, out) => {
112
+ if (path.length === 0)
113
+ throw new TypeError("the tracked root cannot be deleted");
114
+ out.push(["d", [...path]]);
115
+ };
116
+ const diffString = (before, after, path, scan, out) => {
117
+ if (before === after)
118
+ return;
119
+ if (path.length === 0) {
120
+ emitSet(path, after, out);
121
+ return;
122
+ }
123
+ const at = [...path];
124
+ // NOT `after.startsWith(before)`. `after` is usually a cons string — the
125
+ // producer just did `s += chunk` — and V8's startsWith walks a cons char by
126
+ // char. `slice(...) === before` flattens once and compares with memcmp.
127
+ // Measured on a 200 KB string growing by 8 bytes per flush: 845 us -> 42 us.
128
+ if (after.length > before.length && after.slice(0, before.length) === before) {
129
+ out.push(["a", at, after.slice(before.length)]);
130
+ return;
131
+ }
132
+ const shared = overlap(before, after, scan);
133
+ if (shared === 0) {
134
+ out.push(["s", at, after]);
135
+ return;
136
+ }
137
+ out.push(["t", at, before.length - shared]);
138
+ if (after.length > shared)
139
+ out.push(["a", at, after.slice(shared)]);
140
+ };
141
+ const diffValue = (before, after, path, scan, out) => {
142
+ if (before === MISSING) {
143
+ if (after !== MISSING)
144
+ emitSet(path, after, out);
145
+ return;
146
+ }
147
+ if (after === MISSING) {
148
+ emitDelete(path, out);
149
+ return;
150
+ }
151
+ if (before === after)
152
+ return;
153
+ if (typeof before === "string" && typeof after === "string") {
154
+ diffString(before, after, path, scan, out);
155
+ return;
156
+ }
157
+ if (Array.isArray(before) && Array.isArray(after)) {
158
+ diffArray(before, after, path, scan, out);
159
+ return;
160
+ }
161
+ if (isObj(before) && isObj(after) && !Array.isArray(before) && !Array.isArray(after)) {
162
+ diffObject(before, after, path, scan, out);
163
+ return;
164
+ }
165
+ emitSet(path, after, out);
166
+ };
167
+ function diffObject(before, after, path, scan, out) {
168
+ if ([...Object.keys(before), ...Object.keys(after)].some((key) => RESERVED_SEGMENTS.has(key))) {
169
+ emitSet(path, after, out);
170
+ return;
171
+ }
172
+ for (const key of Object.keys(after)) {
173
+ diffValue(Object.hasOwn(before, key) ? before[key] : MISSING, after[key], [...path, key], scan, out);
174
+ }
175
+ for (const key of Object.keys(before)) {
176
+ if (!Object.hasOwn(after, key))
177
+ emitDelete([...path, key], out);
178
+ }
179
+ }
180
+ function diffArray(before, after, path, scan, out) {
181
+ if (before.length === after.length) {
182
+ for (let index = 0; index < after.length; index++) {
183
+ diffValue(before[index], after[index], [...path, index], scan, out);
184
+ }
185
+ return;
186
+ }
187
+ let prefix = 0;
188
+ while (prefix < before.length && prefix < after.length && jsonEqual(before[prefix], after[prefix]))
189
+ prefix++;
190
+ let suffix = 0;
191
+ while (suffix < before.length - prefix &&
192
+ suffix < after.length - prefix &&
193
+ jsonEqual(before[before.length - 1 - suffix], after[after.length - 1 - suffix])) {
194
+ suffix++;
195
+ }
196
+ const shorter = Math.min(before.length, after.length);
197
+ if (prefix + suffix === shorter) {
198
+ const remove = before.length - prefix - suffix;
199
+ const items = after.slice(prefix, after.length - suffix);
200
+ if (prefix === 0 && remove === before.length)
201
+ emitSet(path, after, out);
202
+ else
203
+ out.push(["p", [...path], prefix, remove, cloneJson(items)]);
204
+ return;
205
+ }
206
+ // Structural movement combined with retained-index edits has no unique
207
+ // alignment. Preserve the retained index deltas and express only the tail
208
+ // length change structurally. It may be broader than the producer's intent,
209
+ // but never degrades those edits to a whole-array replacement.
210
+ for (let index = 0; index < shorter; index++) {
211
+ diffValue(before[index], after[index], [...path, index], scan, out);
212
+ }
213
+ if (after.length > before.length) {
214
+ out.push(["p", [...path], before.length, 0, cloneJson(after.slice(before.length))]);
215
+ }
216
+ else if (before.length > after.length) {
217
+ if (after.length === 0)
218
+ emitSet(path, after, out);
219
+ else
220
+ out.push(["p", [...path], after.length, before.length - after.length, []]);
221
+ }
222
+ }
223
+ const walkDirty = (before, after, node, path, scan, out) => {
224
+ if (node.valueDirty) {
225
+ diffValue(before, after, path, scan, out);
226
+ return;
227
+ }
228
+ if (node.array !== undefined) {
229
+ if (node.array.kind === "replace") {
230
+ if (!jsonEqual(before, after))
231
+ emitSet(path, after, out);
232
+ return;
233
+ }
234
+ if (!Array.isArray(before) || !Array.isArray(after) || node.array.kind === "diff") {
235
+ diffValue(before, after, path, scan, out);
236
+ return;
237
+ }
238
+ const start = node.array.start;
239
+ if (before.length !== start || after.length < start) {
240
+ diffValue(before, after, path, scan, out);
241
+ return;
242
+ }
243
+ for (const [segment, child] of node.children) {
244
+ if (typeof segment !== "number" || segment >= start)
245
+ continue;
246
+ const previous = ownValue(before, segment);
247
+ const current = ownValue(after, segment);
248
+ if (previous === MISSING || current === MISSING || child.valueDirty) {
249
+ diffValue(previous, current, [...path, segment], scan, out);
250
+ }
251
+ else if (isObj(previous) && isObj(current)) {
252
+ walkDirty(previous, current, child, [...path, segment], scan, out);
253
+ }
254
+ else {
255
+ diffValue(previous, current, [...path, segment], scan, out);
256
+ }
257
+ }
258
+ const items = after.slice(start);
259
+ if (items.length > 0)
260
+ out.push(["p", [...path], start, 0, cloneJson(items)]);
261
+ return;
262
+ }
263
+ for (const [segment, child] of node.children) {
264
+ const previous = ownValue(before, segment);
265
+ const current = ownValue(after, segment);
266
+ if (previous === MISSING || current === MISSING || child.valueDirty) {
267
+ diffValue(previous, current, [...path, segment], scan, out);
268
+ continue;
269
+ }
270
+ if (!isObj(previous) || !isObj(current)) {
271
+ diffValue(previous, current, [...path, segment], scan, out);
272
+ continue;
273
+ }
274
+ walkDirty(previous, current, child, [...path, segment], scan, out);
275
+ }
276
+ };
277
+ /**
278
+ * Bring `baseline` up to `root` along the dirty paths by sharing references.
279
+ * Returns false — having changed nothing — if any dirty node is an array
280
+ * change other than a pure append, so the caller can replay ops instead.
281
+ * Cloning a whole array there is O(n) per flush; replay is O(changes).
282
+ *
283
+ * Strings are immutable, so root's `after` is shared outright, and sharing it
284
+ * also means the next flush compares against a flat string rather than a cons.
285
+ * Objects are cloned because root keeps mutating them.
286
+ */
287
+ const syncBaseline = (baseline, root, node) => {
288
+ if (!canSync(node))
289
+ return false;
290
+ syncInto(baseline, root, node);
291
+ return true;
292
+ };
293
+ const canSync = (node) => {
294
+ if (node.array !== undefined && node.array.kind !== "append")
295
+ return false;
296
+ for (const child of node.children.values())
297
+ if (!canSync(child))
298
+ return false;
299
+ return true;
300
+ };
301
+ const syncInto = (baseline, root, node) => {
302
+ const parent = baseline;
303
+ if (node.array?.kind === "append" && Array.isArray(baseline) && Array.isArray(root)) {
304
+ const start = node.array.start;
305
+ for (const [index, child] of node.children) {
306
+ if (typeof index === "number" && index < start)
307
+ syncChild(parent, root, index, child);
308
+ }
309
+ for (let i = start; i < root.length; i++) {
310
+ baseline.push(isObj(root[i]) ? cloneJson(root[i]) : root[i]);
311
+ }
312
+ return;
313
+ }
314
+ for (const [segment, child] of node.children)
315
+ syncChild(parent, root, segment, child);
316
+ };
317
+ const syncChild = (parent, root, segment, child) => {
318
+ const current = ownValue(root, segment);
319
+ const previous = ownValue(parent, segment);
320
+ if (current === MISSING) {
321
+ if (Array.isArray(parent))
322
+ parent.splice(segment, 1);
323
+ else
324
+ delete parent[segment];
325
+ return;
326
+ }
327
+ if (child.valueDirty || !isObj(current) || !isObj(previous) || Array.isArray(current) !== Array.isArray(previous)) {
328
+ parent[segment] = isObj(current) ? cloneJson(current) : current;
329
+ return;
330
+ }
331
+ syncInto(previous, current, child);
332
+ };
333
+ const cloneOp = (op) => {
334
+ switch (op[0]) {
335
+ case "r":
336
+ return ["r", cloneJson(op[1])];
337
+ case "s":
338
+ return ["s", op[1], cloneJson(op[2])];
339
+ case "p":
340
+ return ["p", op[1], op[2], op[3], cloneJson(op[4])];
341
+ default:
342
+ return op;
343
+ }
344
+ };
345
+ export function track(root, options = {}) {
346
+ const scan = options.maxOverlapScan ?? 65_536;
347
+ let pending = dirtyNode();
348
+ let hasPending = false;
349
+ let baseline;
350
+ let forceBase = true;
351
+ const clearPending = () => {
352
+ pending = dirtyNode();
353
+ hasPending = false;
354
+ };
355
+ const ensureNode = (path) => {
356
+ hasPending = true;
357
+ let node = pending;
358
+ for (const segment of path) {
359
+ if (node.valueDirty || node.array?.kind === "diff" || node.array?.kind === "replace")
360
+ return undefined;
361
+ let child = node.children.get(segment);
362
+ if (child === undefined)
363
+ child = dirtyNode();
364
+ else
365
+ node.children.delete(segment);
366
+ node.children.set(segment, child);
367
+ node = child;
368
+ }
369
+ return node;
370
+ };
371
+ const findNode = (path) => {
372
+ let node = pending;
373
+ for (const segment of path) {
374
+ const child = node.children.get(segment);
375
+ if (child === undefined)
376
+ return undefined;
377
+ node = child;
378
+ }
379
+ return node;
380
+ };
381
+ const markValue = (path) => {
382
+ const node = ensureNode(path);
383
+ if (node === undefined)
384
+ return;
385
+ node.valueDirty = true;
386
+ node.array = undefined;
387
+ node.children.clear();
388
+ };
389
+ const markArrayAppend = (path, start) => {
390
+ const node = ensureNode(path);
391
+ if (node === undefined || node.valueDirty || node.array?.kind === "diff" || node.array?.kind === "replace") {
392
+ return;
393
+ }
394
+ if (node.array === undefined)
395
+ node.array = { kind: "append", start };
396
+ };
397
+ const markArrayDiff = (path) => {
398
+ const node = ensureNode(path);
399
+ if (node === undefined || node.valueDirty || node.array?.kind === "replace")
400
+ return;
401
+ node.array = { kind: "diff" };
402
+ node.children.clear();
403
+ };
404
+ const markArrayReplace = (path) => {
405
+ const node = ensureNode(path);
406
+ if (node === undefined || node.valueDirty)
407
+ return;
408
+ node.array = { kind: "replace" };
409
+ node.children.clear();
410
+ };
411
+ const appendStart = (path) => {
412
+ const array = findNode(path)?.array;
413
+ return array?.kind === "append" ? array.start : undefined;
414
+ };
415
+ const guard = (segment) => {
416
+ if (typeof segment === "symbol")
417
+ throw new UnsafePathError(String(segment));
418
+ if (typeof segment === "string" && RESERVED_SEGMENTS.has(segment))
419
+ throw new UnsafePathError(segment);
420
+ return segment;
421
+ };
422
+ const adoptItems = (values) => values;
423
+ const integer = (value) => {
424
+ const number = Number(value);
425
+ if (Number.isNaN(number) || number === 0)
426
+ return 0;
427
+ return Number.isFinite(number) ? Math.trunc(number) : number;
428
+ };
429
+ const spliceRange = (length, args) => {
430
+ const rawStart = args.length === 0 ? 0 : integer(args[0]);
431
+ const index = rawStart < 0 ? Math.max(0, length + rawStart) : Math.min(rawStart, length);
432
+ const remove = args.length === 0
433
+ ? 0
434
+ : args.length === 1
435
+ ? length - index
436
+ : Math.max(0, Math.min(integer(args[1]), length - index));
437
+ return { index, remove };
438
+ };
439
+ const wrap = (object, path, blockedSegment) => {
440
+ const childProxies = new Map();
441
+ const proxy = new Proxy(object, {
442
+ get(target, key, receiver) {
443
+ if (Array.isArray(target) && typeof key === "string" && MUTATORS.has(key)) {
444
+ return (...args) => {
445
+ if (blockedSegment !== undefined)
446
+ throw new UnsafePathError(blockedSegment);
447
+ const before = target.length;
448
+ let result;
449
+ switch (key) {
450
+ case "push": {
451
+ const items = adoptItems(args);
452
+ if (items.length > 0)
453
+ markArrayAppend(path, before);
454
+ spliceItems(target, before, 0, items);
455
+ result = target.length;
456
+ break;
457
+ }
458
+ case "unshift": {
459
+ const items = adoptItems(args);
460
+ if (items.length > 0)
461
+ markArrayDiff(path);
462
+ spliceItems(target, 0, 0, items);
463
+ result = target.length;
464
+ break;
465
+ }
466
+ case "pop":
467
+ if (before > 0) {
468
+ const start = appendStart(path);
469
+ if (start === undefined || before - 1 < start)
470
+ markArrayDiff(path);
471
+ }
472
+ result = Reflect.apply(Array.prototype.pop, target, args);
473
+ break;
474
+ case "shift":
475
+ if (before > 0)
476
+ markArrayDiff(path);
477
+ result = Reflect.apply(Array.prototype.shift, target, args);
478
+ break;
479
+ case "splice": {
480
+ const items = adoptItems(args.slice(2));
481
+ const { index, remove } = spliceRange(before, args);
482
+ if (remove > 0 || items.length > 0) {
483
+ const start = appendStart(path);
484
+ if (index === 0 && remove === before)
485
+ markArrayReplace(path);
486
+ else if (start !== undefined && index >= start) {
487
+ // The final append payload includes all tail edits.
488
+ }
489
+ else if (index === before && remove === 0)
490
+ markArrayAppend(path, before);
491
+ else
492
+ markArrayDiff(path);
493
+ }
494
+ result = spliceItems(target, index, remove, items);
495
+ break;
496
+ }
497
+ default:
498
+ markArrayDiff(path);
499
+ result = Reflect.apply(Array.prototype[key], target, args);
500
+ }
501
+ if (key === "pop")
502
+ childProxies.delete(String(before - 1));
503
+ else if (key !== "push")
504
+ childProxies.clear();
505
+ return key === "sort" || key === "reverse" || key === "fill" || key === "copyWithin" ? proxy : result;
506
+ };
507
+ }
508
+ const value = Reflect.get(target, key, receiver);
509
+ if (!isObj(value))
510
+ return value;
511
+ const cached = childProxies.get(key);
512
+ if (cached?.target === value)
513
+ return cached.proxy;
514
+ const rawSegment = norm(target, key);
515
+ let segment;
516
+ let childBlocked = blockedSegment;
517
+ if (blockedSegment !== undefined) {
518
+ if (typeof rawSegment === "symbol")
519
+ throw new UnsafePathError(String(rawSegment));
520
+ segment = rawSegment;
521
+ }
522
+ else if (typeof rawSegment === "string" &&
523
+ RESERVED_SEGMENTS.has(rawSegment) &&
524
+ Object.hasOwn(target, key)) {
525
+ segment = rawSegment;
526
+ childBlocked = rawSegment;
527
+ }
528
+ else
529
+ segment = guard(rawSegment);
530
+ const child = wrap(value, [...path, segment], childBlocked);
531
+ childProxies.set(key, { target: value, proxy: child });
532
+ return child;
533
+ },
534
+ set(target, key, value) {
535
+ if (blockedSegment !== undefined)
536
+ throw new UnsafePathError(blockedSegment);
537
+ if (Array.isArray(target) && key === "length") {
538
+ const before = target.length;
539
+ const next = Number(value);
540
+ if (!Number.isSafeInteger(next) || next < 0 || next > 4_294_967_295) {
541
+ return Reflect.set(target, key, value);
542
+ }
543
+ if (next < before) {
544
+ const start = appendStart(path);
545
+ if (next === 0)
546
+ markArrayReplace(path);
547
+ else if (start === undefined || next < start)
548
+ markArrayDiff(path);
549
+ Reflect.set(target, key, next);
550
+ childProxies.clear();
551
+ }
552
+ else if (next > before) {
553
+ markArrayAppend(path, before);
554
+ target.length = next;
555
+ target.fill(null, before);
556
+ }
557
+ return true;
558
+ }
559
+ const segment = guard(norm(target, key));
560
+ if (Array.isArray(target)) {
561
+ if (typeof segment !== "number")
562
+ throw new UnsafePathError(segment);
563
+ if (segment > target.length)
564
+ throw new UnsafePathError(segment);
565
+ }
566
+ const at = [...path, segment];
567
+ if (value === undefined) {
568
+ if (Array.isArray(target)) {
569
+ throw new TypeError("undefined would create a sparse array; use splice instead");
570
+ }
571
+ markValue(at);
572
+ childProxies.delete(key);
573
+ return Reflect.deleteProperty(target, key);
574
+ }
575
+ const previous = target[key];
576
+ if (previous === value)
577
+ return true;
578
+ const cached = childProxies.get(key);
579
+ if (cached !== undefined && cached.target === previous && cached.proxy === value)
580
+ return true;
581
+ if (Array.isArray(target)) {
582
+ const index = segment;
583
+ if (index === target.length)
584
+ markArrayAppend(path, target.length);
585
+ else {
586
+ const start = appendStart(path);
587
+ if (start === undefined || index < start)
588
+ markValue(at);
589
+ }
590
+ }
591
+ else
592
+ markValue(at);
593
+ childProxies.delete(key);
594
+ return Reflect.set(target, key, value);
595
+ },
596
+ deleteProperty(target, key) {
597
+ if (blockedSegment !== undefined)
598
+ throw new UnsafePathError(blockedSegment);
599
+ const segment = guard(norm(target, key));
600
+ if (Array.isArray(target)) {
601
+ if (typeof segment !== "number")
602
+ throw new UnsafePathError(segment);
603
+ throw new TypeError("delete would create a sparse array; use splice instead");
604
+ }
605
+ markValue([...path, segment]);
606
+ childProxies.delete(key);
607
+ return Reflect.deleteProperty(target, key);
608
+ },
609
+ defineProperty() {
610
+ throw new TypeError("defineProperty is not supported on tracked state; use assignment");
611
+ },
612
+ setPrototypeOf() {
613
+ throw new TypeError("setPrototypeOf is not supported on tracked state");
614
+ },
615
+ preventExtensions() {
616
+ throw new TypeError("preventExtensions is not supported on tracked state");
617
+ },
618
+ });
619
+ return proxy;
620
+ };
621
+ let state = wrap(root, []);
622
+ return {
623
+ get state() {
624
+ return state;
625
+ },
626
+ get target() {
627
+ return root;
628
+ },
629
+ set state(next) {
630
+ if (next === state) {
631
+ clearPending();
632
+ forceBase = true;
633
+ return;
634
+ }
635
+ clearPending();
636
+ root = next;
637
+ state = wrap(root, []);
638
+ baseline = undefined;
639
+ forceBase = true;
640
+ },
641
+ rebase() {
642
+ clearPending();
643
+ forceBase = true;
644
+ },
645
+ get dirty() {
646
+ return forceBase || hasPending;
647
+ },
648
+ discard() {
649
+ baseline = cloneJson(root);
650
+ clearPending();
651
+ },
652
+ flush() {
653
+ if (forceBase) {
654
+ const value = cloneJson(root);
655
+ baseline = cloneJson(root);
656
+ forceBase = false;
657
+ clearPending();
658
+ return [["r", value]];
659
+ }
660
+ if (!hasPending || baseline === undefined)
661
+ return [];
662
+ const out = [];
663
+ walkDirty(baseline, root, pending, [], scan, out);
664
+ // Advance the baseline by SHARING references from root where that is
665
+ // cheap and exact — scalars, strings, and array appends. Replaying the
666
+ // ops rebuilds every touched string via slice + concat: two window-sized
667
+ // allocations per flush and a cons the next flush must flatten. For
668
+ // anything the sync cannot express cheaply (a non-append array change),
669
+ // it declines and the original replay runs unchanged.
670
+ if (!syncBaseline(baseline, root, pending)) {
671
+ if (out.length > 0)
672
+ baseline = apply(baseline, out.map(cloneOp));
673
+ }
674
+ clearPending();
675
+ return out;
676
+ },
677
+ };
678
+ }
679
+ // ─── Path safety ─────────────────────────────────────────────────────────────
680
+ /**
681
+ * Segments that reach the prototype chain.
682
+ *
683
+ * `JSON.parse` is safe on its own — it makes `__proto__` an own property. What is
684
+ * not safe is `parent[key] = value`, which is exactly what an applier does, and
685
+ * paths are data: `["s", ["__proto__", "isAdmin"], true]` pollutes
686
+ * `Object.prototype` for the whole process.
687
+ *
688
+ * Ops arrive from a facet, a plugin compartment, or a tool whose details may echo
689
+ * model output, so none of it is trusted input.
690
+ */
691
+ export const RESERVED_SEGMENTS = new Set(["__proto__", "constructor", "prototype"]);
692
+ export class UnsafePathError extends Error {
693
+ // Not a parameter property: Node's --experimental-strip-types rejects those,
694
+ // and these files are meant to run under it directly.
695
+ segment;
696
+ constructor(segment) {
697
+ super(`unsafe path segment: ${String(segment)}`);
698
+ this.segment = segment;
699
+ this.name = "UnsafePathError";
700
+ }
701
+ }
702
+ /**
703
+ * Verb, arity and payload shape for a **decoded** op: paths inline, no `#`, no
704
+ * short forms. `apply` uses this.
705
+ *
706
+ * Validating `Op` against the wire grammar would be laxer than the type: a
707
+ * two-element `["s", value]` would pass, and `apply` would then read the value as
708
+ * a path. Each vocabulary gets the validator that matches it.
709
+ */
710
+ export function assertValidOp(op) {
711
+ if (!Array.isArray(op) || op.length === 0)
712
+ throw new TypeError("op is not a tuple");
713
+ switch (op[0]) {
714
+ case "r":
715
+ if (op.length !== 2)
716
+ throw new TypeError("r arity");
717
+ return;
718
+ case "s":
719
+ if (op.length !== 3)
720
+ throw new TypeError("s arity");
721
+ assertPathArg(op[1], true);
722
+ return;
723
+ case "d":
724
+ if (op.length !== 2)
725
+ throw new TypeError("d arity");
726
+ assertPathArg(op[1], true);
727
+ return;
728
+ case "a":
729
+ if (op.length !== 3 || typeof op[2] !== "string")
730
+ throw new TypeError("a shape");
731
+ assertPathArg(op[1], true);
732
+ return;
733
+ case "t":
734
+ if (op.length !== 3 || !Number.isInteger(op[2]) || op[2] < 0)
735
+ throw new TypeError("t shape");
736
+ assertPathArg(op[1], true);
737
+ return;
738
+ case "p": {
739
+ if (op.length !== 5)
740
+ throw new TypeError("p arity");
741
+ assertPathArg(op[1]);
742
+ if (!Number.isInteger(op[2]) || op[2] < 0)
743
+ throw new TypeError("p index");
744
+ if (!Number.isInteger(op[3]) || op[3] < 0)
745
+ throw new TypeError("p remove");
746
+ if (!Array.isArray(op[4]))
747
+ throw new TypeError("p items");
748
+ return;
749
+ }
750
+ // Silently skipping an unknown verb is how a newer producer's op vanishes.
751
+ default:
752
+ throw new TypeError(`unknown op verb: ${String(op[0])}`);
753
+ }
754
+ }
755
+ function assertPathArg(p, nonEmpty = false) {
756
+ if (!Array.isArray(p))
757
+ throw new TypeError("path is not an array");
758
+ if (nonEmpty && p.length === 0)
759
+ throw new TypeError("path is empty");
760
+ assertSafePath(p);
761
+ }
762
+ /** The same, for the wire grammar: ids and short forms are legal here. */
763
+ export function assertValidWireOp(op) {
764
+ if (!Array.isArray(op) || op.length === 0)
765
+ throw new TypeError("op is not a tuple");
766
+ const [verb] = op;
767
+ const okRef = (r) => {
768
+ if (typeof r === "number") {
769
+ if (!Number.isInteger(r) || r < 0)
770
+ throw new TypeError("bad path id");
771
+ return;
772
+ }
773
+ // A string is not a path. Unchecked, `"a".slice(0, -1)` is `""`, so it
774
+ // resolves to the ROOT and writes there — a path that is not a path, accepted.
775
+ if (!Array.isArray(r))
776
+ throw new TypeError("path is not an array");
777
+ assertSafePath(r);
778
+ };
779
+ switch (verb) {
780
+ case "r":
781
+ if (op.length !== 2)
782
+ throw new TypeError("r arity");
783
+ return;
784
+ case "s":
785
+ if (op.length === 3)
786
+ okRef(op[1]);
787
+ else if (op.length !== 2)
788
+ throw new TypeError("s arity");
789
+ return;
790
+ case "d":
791
+ if (op.length === 2)
792
+ okRef(op[1]);
793
+ else if (op.length !== 1)
794
+ throw new TypeError("d arity");
795
+ return;
796
+ case "a":
797
+ if (op.length === 3) {
798
+ okRef(op[1]);
799
+ if (typeof op[2] !== "string")
800
+ throw new TypeError("a value");
801
+ }
802
+ else if (op.length === 2) {
803
+ if (typeof op[1] !== "string")
804
+ throw new TypeError("a value");
805
+ }
806
+ else
807
+ throw new TypeError("a arity");
808
+ return;
809
+ case "t":
810
+ if (op.length === 3) {
811
+ okRef(op[1]);
812
+ if (!Number.isInteger(op[2]) || op[2] < 0)
813
+ throw new TypeError("t count");
814
+ }
815
+ else if (op.length === 2) {
816
+ if (!Number.isInteger(op[1]) || op[1] < 0)
817
+ throw new TypeError("t count");
818
+ }
819
+ else
820
+ throw new TypeError("t arity");
821
+ return;
822
+ case "p": {
823
+ const [i, r, items] = op.length === 5 ? [op[2], op[3], op[4]] : op.length === 4 ? [op[1], op[2], op[3]] : [];
824
+ if (items === undefined)
825
+ throw new TypeError("p arity");
826
+ if (op.length === 5)
827
+ okRef(op[1]);
828
+ if (!Number.isInteger(i) || i < 0)
829
+ throw new TypeError("p index");
830
+ if (!Number.isInteger(r) || r < 0)
831
+ throw new TypeError("p remove");
832
+ if (!Array.isArray(items))
833
+ throw new TypeError("p items");
834
+ return;
835
+ }
836
+ case "#": {
837
+ if (op.length !== 3 || !Number.isInteger(op[1]) || op[1] < 0 || !Array.isArray(op[2])) {
838
+ throw new TypeError("# shape");
839
+ }
840
+ assertSafePath(op[2]);
841
+ return;
842
+ }
843
+ // Silently skipping an unknown verb is how a newer producer's op vanishes.
844
+ default:
845
+ throw new TypeError(`unknown op verb: ${String(verb)}`);
846
+ }
847
+ }
848
+ export function assertSafePath(path) {
849
+ for (const seg of path) {
850
+ if (typeof seg === "string") {
851
+ if (RESERVED_SEGMENTS.has(seg))
852
+ throw new UnsafePathError(seg);
853
+ }
854
+ else if (!Number.isInteger(seg) || seg < 0) {
855
+ throw new UnsafePathError(seg);
856
+ }
857
+ }
858
+ }
859
+ /**
860
+ * An index may address an existing element or append exactly one past the end.
861
+ *
862
+ * This is not an arbitrary cap — it is what keeps the value a `JsonValue`. A
863
+ * sparse array does not survive a JSON round trip: holes serialise to `null` and
864
+ * return as real properties, so `arr[7] = x` on a length-3 array already produces
865
+ * state a replica cannot match. Rejecting the write is more honest than silently
866
+ * diverging.
867
+ *
868
+ * It also removes the denial of service it would otherwise permit:
869
+ * `["s", ["xs", 4294967290], 1]` allocates a 4.29-billion-entry array from one op.
870
+ * Growth stays possible and stays proportional — the tracker already emits
871
+ * `arr.length = n` as a splice of explicit nulls, whose op size grows with the
872
+ * gap, so a large growth costs a large op rather than a small one.
873
+ */
874
+ function assertIndexInRange(parent, index) {
875
+ if (index > parent.length)
876
+ throw new UnsafePathError(index);
877
+ }
878
+ // ─── Applier ─────────────────────────────────────────────────────────────────
879
+ export class PathError extends Error {
880
+ path;
881
+ constructor(path) {
882
+ super(`unresolvable path: ${JSON.stringify(path)}`);
883
+ this.path = path;
884
+ this.name = "PathError";
885
+ }
886
+ }
887
+ /**
888
+ * Apply ops to a plain mutable value. Returns the value, because `r` replaces it
889
+ * outright and cannot be done in place.
890
+ *
891
+ * Takes decoded ops. Path ids and omitted paths are a wire concern — run
892
+ * `decode` first if the ops came from a boundary.
893
+ */
894
+ export function apply(target, ops) {
895
+ return applyOps(target, ops);
896
+ }
897
+ function applyOps(target, ops) {
898
+ let root = target;
899
+ for (const op of ops) {
900
+ assertValidOp(op);
901
+ if (op[0] === "r") {
902
+ // Adopted, not copied. The consumer owns the batch it was handed.
903
+ //
904
+ // Fanning one batch out to several consumers in-process therefore makes
905
+ // their replicas alias each other. That is an ownership rule, not a
906
+ // defect: copy the batch at the fan-out point, or let each consumer
907
+ // decode its own. A batch that crosses a real boundary is already
908
+ // distinct, because serialisation produces fresh objects.
909
+ root = op[1];
910
+ continue;
911
+ }
912
+ const path = op[1];
913
+ assertSafePath(path);
914
+ if (op[0] === "p") {
915
+ const target_ = path.length === 0 ? root : resolve(root, path);
916
+ if (!Array.isArray(target_))
917
+ throw new PathError(path);
918
+ target_.splice(op[2], op[3]);
919
+ const chunkSize = 10_000;
920
+ for (let offset = 0; offset < op[4].length; offset += chunkSize) {
921
+ target_.splice(op[2] + offset, 0, ...op[4].slice(offset, offset + chunkSize));
922
+ }
923
+ continue;
924
+ }
925
+ // s/d/a/t can never target the root — the type forbids it.
926
+ const parent = resolve(root, path.slice(0, -1));
927
+ const key = path[path.length - 1];
928
+ if (Array.isArray(parent)) {
929
+ if (typeof key !== "number")
930
+ throw new UnsafePathError(key);
931
+ assertIndexInRange(parent, key);
932
+ }
933
+ // defineProperty rather than assignment: a setter inherited from the prototype
934
+ // chain would otherwise run on write.
935
+ const write = (value) => {
936
+ Object.defineProperty(parent, key, { value, writable: true, enumerable: true, configurable: true });
937
+ };
938
+ const read = () => (Object.hasOwn(parent, key) ? parent[key] : undefined);
939
+ switch (op[0]) {
940
+ case "s":
941
+ write(op[2]);
942
+ break;
943
+ case "d":
944
+ if (Array.isArray(parent)) {
945
+ if (typeof key !== "number" || key >= parent.length)
946
+ throw new PathError(path);
947
+ parent.splice(key, 1);
948
+ }
949
+ else
950
+ delete parent[key];
951
+ break;
952
+ case "a": {
953
+ const current = read();
954
+ if (typeof current !== "string")
955
+ throw new PathError(path);
956
+ write(`${current}${op[2]}`);
957
+ break;
958
+ }
959
+ case "t": {
960
+ const current = read();
961
+ if (typeof current !== "string")
962
+ throw new PathError(path);
963
+ write(current.slice(op[2]));
964
+ break;
965
+ }
966
+ }
967
+ }
968
+ return root;
969
+ }
970
+ /** Apply decoded operations without mutating the previous immutable value. */
971
+ export function applyImmutable(target, ops) {
972
+ let root = target;
973
+ for (const op of ops) {
974
+ if (op[0] === "r") {
975
+ assertValidOp(op);
976
+ root = op[1];
977
+ continue;
978
+ }
979
+ root = copyContainers(root, op[0] === "p" ? op[1] : op[1].slice(0, -1));
980
+ root = applyOps(root, [op]);
981
+ }
982
+ return root;
983
+ }
984
+ function copyContainers(root, path) {
985
+ const copy = (value) => {
986
+ if (Array.isArray(value))
987
+ return value.slice();
988
+ if (!isObj(value))
989
+ throw new PathError(path);
990
+ const result = Object.create(Object.getPrototypeOf(value) === null ? null : Object.prototype);
991
+ for (const key of Object.keys(value)) {
992
+ Object.defineProperty(result, key, {
993
+ value: value[key],
994
+ writable: true,
995
+ enumerable: true,
996
+ configurable: true,
997
+ });
998
+ }
999
+ return result;
1000
+ };
1001
+ const copiedRoot = copy(root);
1002
+ let source = root;
1003
+ let destination = copiedRoot;
1004
+ for (const segment of path) {
1005
+ if (!isObj(source) || !Object.hasOwn(source, segment))
1006
+ throw new PathError(path);
1007
+ if (Array.isArray(source) && typeof segment !== "number")
1008
+ throw new UnsafePathError(segment);
1009
+ const child = source[segment];
1010
+ const copiedChild = copy(child);
1011
+ Object.defineProperty(destination, segment, {
1012
+ value: copiedChild,
1013
+ writable: true,
1014
+ enumerable: true,
1015
+ configurable: true,
1016
+ });
1017
+ source = child;
1018
+ destination = copiedChild;
1019
+ }
1020
+ return copiedRoot;
1021
+ }
1022
+ function resolveValue(root, path) {
1023
+ let node = root;
1024
+ for (const seg of path) {
1025
+ if (!isObj(node))
1026
+ throw new PathError(path);
1027
+ if (Array.isArray(node) && typeof seg !== "number")
1028
+ throw new UnsafePathError(seg);
1029
+ // Own properties only: an inherited getter must not run, and a walk must not
1030
+ // escape the value into the prototype chain.
1031
+ if (!Object.hasOwn(node, seg))
1032
+ throw new PathError(path);
1033
+ node = node[seg];
1034
+ }
1035
+ return node;
1036
+ }
1037
+ function resolve(root, path) {
1038
+ const node = resolveValue(root, path);
1039
+ if (!isObj(node))
1040
+ throw new PathError(path);
1041
+ return node;
1042
+ }
1043
+ // ─── Codec ───────────────────────────────────────────────────────────────────
1044
+ //
1045
+ // Path interning and arity omission live between the tracker and a boundary;
1046
+ // `Op` and `apply` know nothing about them.
1047
+ //
1048
+ // ONE PAIR PER INDEPENDENT STATE STREAM. Every decoder must observe exactly the
1049
+ // batches encoded by its matching encoder, beginning with that state's base.
1050
+ // Sharing a transport connection does not make separately hydrated states one
1051
+ // stream.
1052
+ const pathKey = (path) => JSON.stringify(path);
1053
+ /**
1054
+ * Intern on SECOND use. A definition costs more than the path it replaces, so
1055
+ * interning on first use loses on the many paths written exactly once.
1056
+ */
1057
+ export function encoder() {
1058
+ const seen = new Set();
1059
+ const ids = new Map();
1060
+ let nextId = 0;
1061
+ let previous; // last path in THIS batch
1062
+ return {
1063
+ encode(ops) {
1064
+ // Arity omission is scoped to a batch. Letting it span batches would make
1065
+ // a batch's first op depend on the previous batch's last one, so a reader
1066
+ // that skips or reorders a batch decodes into the wrong path. Ids are the
1067
+ // only cross-batch state, and the dictionary makes those explicit.
1068
+ previous = undefined;
1069
+ const out = [];
1070
+ for (const op of ops) {
1071
+ if (op[0] === "r") {
1072
+ out.push(op);
1073
+ // A base batch is a RECOVERY POINT: a reader replays from the last one
1074
+ // with a fresh decoder. So everything after it must be self-contained.
1075
+ // Keeping ids across a replacement emits references to definitions the
1076
+ // reader never saw — recovery fails with an unresolvable path id.
1077
+ seen.clear();
1078
+ ids.clear();
1079
+ nextId = 0;
1080
+ previous = undefined;
1081
+ continue;
1082
+ }
1083
+ const path = op[1];
1084
+ const key = pathKey(path);
1085
+ // Same path as the previous op: drop the ref entirely.
1086
+ if (key === previous) {
1087
+ switch (op[0]) {
1088
+ case "s":
1089
+ out.push(["s", op[2]]);
1090
+ break;
1091
+ case "d":
1092
+ out.push(["d"]);
1093
+ break;
1094
+ case "a":
1095
+ out.push(["a", op[2]]);
1096
+ break;
1097
+ case "t":
1098
+ out.push(["t", op[2]]);
1099
+ break;
1100
+ case "p":
1101
+ out.push(["p", op[2], op[3], op[4]]);
1102
+ break;
1103
+ }
1104
+ continue;
1105
+ }
1106
+ let ref = path;
1107
+ const existing = ids.get(key);
1108
+ if (existing !== undefined) {
1109
+ ref = existing;
1110
+ }
1111
+ else if (seen.has(key)) {
1112
+ const id = nextId++;
1113
+ ids.set(key, id);
1114
+ out.push(["#", id, path]); // second use: define, then reference
1115
+ ref = id;
1116
+ }
1117
+ else {
1118
+ seen.add(key); // first use: inline
1119
+ }
1120
+ switch (op[0]) {
1121
+ case "s":
1122
+ out.push(["s", ref, op[2]]);
1123
+ break;
1124
+ case "d":
1125
+ out.push(["d", ref]);
1126
+ break;
1127
+ case "a":
1128
+ out.push(["a", ref, op[2]]);
1129
+ break;
1130
+ case "t":
1131
+ out.push(["t", ref, op[2]]);
1132
+ break;
1133
+ case "p":
1134
+ out.push(["p", ref, op[2], op[3], op[4]]);
1135
+ break;
1136
+ }
1137
+ previous = key;
1138
+ }
1139
+ return out;
1140
+ },
1141
+ };
1142
+ }
1143
+ export function decoder() {
1144
+ const paths = new Map();
1145
+ return {
1146
+ decode(wire) {
1147
+ let previous; // scoped to the batch, as in encode
1148
+ const out = [];
1149
+ for (const op of wire) {
1150
+ assertValidWireOp(op);
1151
+ if (op[0] === "#") {
1152
+ assertSafePath(op[2]);
1153
+ paths.set(op[1], op[2]);
1154
+ continue;
1155
+ }
1156
+ if (op[0] === "r") {
1157
+ out.push(op);
1158
+ paths.clear();
1159
+ previous = undefined;
1160
+ continue;
1161
+ }
1162
+ // Arity tells us whether a ref is present: the short forms omit it.
1163
+ const short = (op[0] === "d" && op.length === 1) ||
1164
+ (op[0] !== "d" && op[0] !== "p" && op.length === 2) ||
1165
+ (op[0] === "p" && op.length === 4);
1166
+ let path;
1167
+ if (short) {
1168
+ if (previous === undefined)
1169
+ throw new PathError([]);
1170
+ path = previous;
1171
+ }
1172
+ else {
1173
+ const ref = op[1];
1174
+ if (typeof ref === "number") {
1175
+ const resolved = paths.get(ref);
1176
+ if (resolved === undefined)
1177
+ throw new PathError(ref);
1178
+ path = resolved;
1179
+ }
1180
+ else {
1181
+ path = ref;
1182
+ }
1183
+ previous = path;
1184
+ }
1185
+ if (op[0] !== "p" && path.length === 0)
1186
+ throw new PathError(path);
1187
+ switch (op[0]) {
1188
+ case "s":
1189
+ out.push(["s", path, (short ? op[1] : op[2])]);
1190
+ break;
1191
+ case "d":
1192
+ out.push(["d", path]);
1193
+ break;
1194
+ case "a":
1195
+ out.push(["a", path, (short ? op[1] : op[2])]);
1196
+ break;
1197
+ case "t":
1198
+ out.push(["t", path, (short ? op[1] : op[2])]);
1199
+ break;
1200
+ case "p": {
1201
+ const [i, r, items] = short
1202
+ ? [op[1], op[2], op[3]]
1203
+ : [op[2], op[3], op[4]];
1204
+ out.push(["p", path, i, r, items]);
1205
+ break;
1206
+ }
1207
+ }
1208
+ }
1209
+ return out;
1210
+ },
1211
+ };
1212
+ }
1213
+ //# sourceMappingURL=index.js.map