footprintjs 9.29.0 → 9.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +4 -3
- package/dist/esm/lib/memory/StageContext.js +6 -2
- package/dist/esm/lib/memory/TransactionBuffer.d.ts +119 -86
- package/dist/esm/lib/memory/TransactionBuffer.js +256 -297
- package/dist/esm/lib/memory/admission.d.ts +81 -0
- package/dist/esm/lib/memory/admission.js +152 -0
- package/dist/esm/lib/memory/deltaEncoding.d.ts +66 -0
- package/dist/esm/lib/memory/deltaEncoding.js +122 -0
- package/dist/esm/lib/memory/pathOps.d.ts +8 -0
- package/dist/esm/lib/memory/pathOps.js +12 -2
- package/dist/esm/lib/memory/utils.d.ts +14 -4
- package/dist/esm/lib/memory/utils.js +41 -15
- package/dist/lib/memory/StageContext.js +6 -2
- package/dist/lib/memory/TransactionBuffer.js +265 -306
- package/dist/lib/memory/admission.js +158 -0
- package/dist/lib/memory/deltaEncoding.js +128 -0
- package/dist/lib/memory/pathOps.js +14 -2
- package/dist/lib/memory/utils.js +43 -16
- package/dist/types/lib/memory/TransactionBuffer.d.ts +119 -86
- package/dist/types/lib/memory/admission.d.ts +81 -0
- package/dist/types/lib/memory/deltaEncoding.d.ts +66 -0
- package/dist/types/lib/memory/pathOps.d.ts +8 -0
- package/dist/types/lib/memory/utils.d.ts +14 -4
- package/package.json +1 -1
|
@@ -45,6 +45,22 @@
|
|
|
45
45
|
* A read the working copy cannot answer is served from live state by the
|
|
46
46
|
* caller, as before; the buffer then replaces that path of its diff base
|
|
47
47
|
* with a private copy ({@link detachBase}), as the clone had it.
|
|
48
|
+
*
|
|
49
|
+
* THE ADMITTED RECORD (9.30.0 — docs/design/2026-10-admitted-record.md). A
|
|
50
|
+
* commit is admitted only if its bundle folds back to the stage's
|
|
51
|
+
* read-your-writes view at every path the stage touched (and at every
|
|
52
|
+
* container its writes created on the way there, below the stage's address).
|
|
53
|
+
* Both encoders commit the value the stage read: a compact row — a `merge`
|
|
54
|
+
* delta, an `append` tail — is kept where it provably folds back; a family of
|
|
55
|
+
* rows that does not is recorded as `set` rows of the values the stage read
|
|
56
|
+
* ({@link admit}). Before 9.30.0 the one accumulated merge delta per path was
|
|
57
|
+
* replayed at every `merge` row of that path, which lied across a hard write
|
|
58
|
+
* (`$update(k,{x}); k = {y}; $update(k,{z})` committed `{y, x, z}` for a
|
|
59
|
+
* read-back of `{y, z}`), an `[]` clear, a kind change or an array union
|
|
60
|
+
* deduplicated by reference. Verified when the stage staged a `merge` or a
|
|
61
|
+
* nested op; a stage that staged only `set` / `delete` of keys directly under
|
|
62
|
+
* its address is coherent by construction — both trees receive the same
|
|
63
|
+
* write — and is admitted without the fold.
|
|
48
64
|
*/
|
|
49
65
|
import type { CommitValuesMode, MemoryPatch, TraceEntry } from './types.js';
|
|
50
66
|
export declare class TransactionBuffer {
|
|
@@ -111,11 +127,24 @@ export declare class TransactionBuffer {
|
|
|
111
127
|
* at or below one is committed state, so a later detach inside it is a no-op.
|
|
112
128
|
*/
|
|
113
129
|
private detachedBase?;
|
|
114
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Where the stage writes (9.30.0): `['runs', runId]` for a run-namespaced
|
|
132
|
+
* stage, `[]` otherwise. Its containers are the stage's ADDRESS, not a value
|
|
133
|
+
* the stage reads (every read it makes is a key below it), so a shell a
|
|
134
|
+
* write leaves there is no part of the read-back the record must hold.
|
|
135
|
+
*/
|
|
136
|
+
private readonly address;
|
|
137
|
+
/** `merge` ops staged since the last commit — see {@link admit}. */
|
|
138
|
+
private stagedMerges;
|
|
139
|
+
/** Ops staged deeper than one key below the stage's address — see {@link admit}. */
|
|
140
|
+
private nestedOps;
|
|
141
|
+
constructor(base: any, commitValues?: CommitValuesMode, readKeysProvider?: () => string[], address?: readonly string[]);
|
|
115
142
|
/** Stamp the current read prefix onto a staged op — only when the
|
|
116
143
|
* provenance dial is on (provider present), so the default path allocates
|
|
117
144
|
* nothing and commit bundles stay byte-identical. */
|
|
118
145
|
private stampReadKeys;
|
|
146
|
+
/** Stage an op into `opTrace`, counting what decides whether {@link admit} must fold. */
|
|
147
|
+
private stage;
|
|
119
148
|
/**
|
|
120
149
|
* Hard overwrite at the specified path. Stores the REFERENCE in both trees
|
|
121
150
|
* (9.23.0) — the record's copy is taken once, at commit; see the header.
|
|
@@ -175,6 +204,14 @@ export declare class TransactionBuffer {
|
|
|
175
204
|
* (see {@link survivingRedactedPaths}).
|
|
176
205
|
*/
|
|
177
206
|
markRedactedFields(path: (string | number)[], fields: readonly string[]): void;
|
|
207
|
+
/**
|
|
208
|
+
* Does a redaction mark address an array ELEMENT right below `path`
|
|
209
|
+
* (`list.1.token`)? An `append` row re-bases the indices onto the tail, so
|
|
210
|
+
* such a path takes the whole-value `set` (`deltaEncoding · pushValueRow`).
|
|
211
|
+
* A mark by NAME below an array (`obj.y`) means the same thing on the tail
|
|
212
|
+
* as on the whole value, so the compact row stays (byte-identical).
|
|
213
|
+
*/
|
|
214
|
+
private markedBelow;
|
|
178
215
|
/**
|
|
179
216
|
* The redacted paths that survive the net-change filter: a path survives
|
|
180
217
|
* when it IS a surviving op path (a whole-key redaction) or sits UNDER one
|
|
@@ -228,9 +265,11 @@ export declare class TransactionBuffer {
|
|
|
228
265
|
* a read hands it out — so a read after the stage's first write behaves
|
|
229
266
|
* exactly as it did when the buffer began with a deep clone of the whole
|
|
230
267
|
* state. An in-place edit of what the read returned (out of contract —
|
|
231
|
-
* reads are borrowed) stays inside the buffer
|
|
232
|
-
*
|
|
233
|
-
*
|
|
268
|
+
* reads are borrowed) stays inside the buffer, and committed state is never
|
|
269
|
+
* touched. Whether the RECORD keeps it is the admitted record's law
|
|
270
|
+
* (9.30.0, owner ruling R1 — the record keeps what the stage read back): it
|
|
271
|
+
* is kept when the edited value lies at or below a path the stage wrote in
|
|
272
|
+
* this stage — set, merged or written back alike — and lost otherwise.
|
|
234
273
|
*
|
|
235
274
|
* Walking the path from the (owned) root:
|
|
236
275
|
* - a container still SHARED with committed state at the same position is
|
|
@@ -341,14 +380,14 @@ export declare class TransactionBuffer {
|
|
|
341
380
|
* Paths are compared at the exact granularity they were written (each trace
|
|
342
381
|
* entry's path), against `workingCopy` (final) vs `baseSnapshot` (start).
|
|
343
382
|
* Surviving `set` paths copy their final value from `overwritePatch`;
|
|
344
|
-
* surviving `merge` paths copy their accumulated delta from `updatePatch
|
|
345
|
-
*
|
|
346
|
-
*
|
|
383
|
+
* surviving `merge` paths copy their accumulated delta from `updatePatch`,
|
|
384
|
+
* keeping the set-vs-merge verb — and the result is ADMITTED ({@link admit}):
|
|
385
|
+
* a family of rows whose replay does not give back what the stage read is
|
|
386
|
+
* recorded as `set` rows of the read-back instead (9.30.0).
|
|
347
387
|
*
|
|
348
|
-
* This is the DEFAULT (`commitValues: 'full'`) payload
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* {@link toDeltaPayload}.
|
|
388
|
+
* This is the DEFAULT (`commitValues: 'full'`) payload, including the
|
|
389
|
+
* historical flattening of staged `delete` ops into `set`-of-`undefined`
|
|
390
|
+
* trace entries. The delta encoding lives in {@link toDeltaPayload}.
|
|
352
391
|
*
|
|
353
392
|
* Work, not bytes (9.22.1): the net-change verdict is paid once per PATH
|
|
354
393
|
* and the clone once per CONSECUTIVE run of ops on a path, not once per op
|
|
@@ -359,6 +398,8 @@ export declare class TransactionBuffer {
|
|
|
359
398
|
* place the record is detached from them.
|
|
360
399
|
*/
|
|
361
400
|
private toChangeOnlyPayload;
|
|
401
|
+
/** The 'full' rows; a family in `lossy` is written as its read-back, at its last touch ({@link emitReadBack}). */
|
|
402
|
+
private changeOnlyRows;
|
|
362
403
|
/**
|
|
363
404
|
* Delta-encoded payload (`commitValues: 'delta'`, #13c-B) — same net-change
|
|
364
405
|
* filter as {@link toChangeOnlyPayload}, three encoding differences:
|
|
@@ -377,40 +418,78 @@ export declare class TransactionBuffer {
|
|
|
377
418
|
* the `'full'` flattening (`_set` of `undefined`) COERCES that parent
|
|
378
419
|
* into an object; when they would disagree we keep the flattening;
|
|
379
420
|
* - ONLY `'merge'` ops → `merge` with the accumulated `updatePatch`
|
|
380
|
-
* delta
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
* replays the full-mode bundle ({@link replayPathVerbs}) — for
|
|
386
|
-
* pure-set paths that is simply the last set value; for mixed
|
|
387
|
-
* set+merge interleavings it reproduces the full mode's quirk of
|
|
388
|
-
* applying the ACCUMULATED merge delta at every merge position
|
|
389
|
-
* (which can differ from the buffer's read-your-writes view; parity
|
|
390
|
-
* with the `'full'` mode's committed state is the contract). If base
|
|
391
|
-
* and that value are arrays and base is a STRICT PREFIX → `append`
|
|
392
|
-
* storing only the tail; else `set` storing the full value.
|
|
421
|
+
* delta;
|
|
422
|
+
* - otherwise (`set`/mixed): the value the stage's 'full' rows give the
|
|
423
|
+
* path ({@link fullRowsValue}). If base and that value are arrays and
|
|
424
|
+
* base is a STRICT PREFIX → `append` storing only the tail; else `set`
|
|
425
|
+
* storing the full value.
|
|
393
426
|
* 3. **OVERLAP FAMILIES take the full-value fallback.** `overwrite` /
|
|
394
427
|
* `updates` are nested path TREES, so two surviving paths where one is
|
|
395
|
-
* an ancestor of the other (`a` and `a.p`) share storage.
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
* `
|
|
400
|
-
*
|
|
401
|
-
* `undefined`, so whichever entry is written second silently destroys or
|
|
402
|
-
* corrupts the other — the recorded write is LOST at replay. Therefore
|
|
403
|
-
* every path that has a surviving ancestor/descendant is committed as a
|
|
404
|
-
* plain `set` of the value it holds in the family's replayed value
|
|
405
|
-
* ({@link replayFamilyVerbs}), and the family's shallowest path (whose
|
|
406
|
-
* set covers the whole subtree) is emitted LAST. One coherent tree in,
|
|
407
|
-
* one coherent tree out: entries can no longer clobber each other and
|
|
408
|
-
* the replayed value is exact regardless of order.
|
|
428
|
+
* an ancestor of the other (`a` and `a.p`) share storage. Delta's
|
|
429
|
+
* per-path encodings would clobber each other there (an `append`
|
|
430
|
+
* ancestor stores only a TAIL, a `delete` ancestor `undefined`), so every
|
|
431
|
+
* path that has a surviving ancestor/descendant is committed as a plain
|
|
432
|
+
* `set` of the value the 'full' rows give it, and the family's
|
|
433
|
+
* shallowest path (whose set covers the whole subtree) is emitted LAST.
|
|
409
434
|
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
435
|
+
* The result is ADMITTED ({@link admit}) like the 'full' payload: a compact
|
|
436
|
+
* row is kept only where the bundle provably folds back, and a family that
|
|
437
|
+
* does not is recorded as what the stage read. So both encodings commit the
|
|
438
|
+
* value the stage read; before 9.30.0 they shared a replay that did not
|
|
439
|
+
* (`replayPathVerbs` / `replayFamilyVerbs`, deleted).
|
|
412
440
|
*/
|
|
413
441
|
private toDeltaPayload;
|
|
442
|
+
/** The delta rows; a family in `lossy` is written as its read-back, at its last touch ({@link emitReadBack}). */
|
|
443
|
+
private deltaRows;
|
|
444
|
+
/**
|
|
445
|
+
* THE ADMISSION — a commit is admitted only if its bundle folds back to the
|
|
446
|
+
* stage's read-your-writes view at every path the stage touched. `build`
|
|
447
|
+
* lays the rows out (one encoder: `'full'` or `'delta'`); the candidate is
|
|
448
|
+
* checked ({@link lossyFamilies}), and when some family's rows do not fold
|
|
449
|
+
* back the rows are laid out again with those families written as what the
|
|
450
|
+
* stage read ({@link emitReadBack}) — every other family's bytes exactly as
|
|
451
|
+
* the first layout made them.
|
|
452
|
+
*/
|
|
453
|
+
private admit;
|
|
454
|
+
/**
|
|
455
|
+
* The families whose rows do not fold back to what the stage read —
|
|
456
|
+
* `undefined` when there are none (`admission.ts · lossyFamilies`: the
|
|
457
|
+
* candidate replayed onto the diff base by `dryFold`, every family of
|
|
458
|
+
* touched paths compared with the working copy at its root and on the way
|
|
459
|
+
* down to it).
|
|
460
|
+
*
|
|
461
|
+
* Without a fold, when the stage staged no `merge` and no nested op: its
|
|
462
|
+
* rows are `set` / `delete` of keys directly under its address, each
|
|
463
|
+
* writing the SAME value into `workingCopy` and `overwritePatch` — the
|
|
464
|
+
* record is the read-back by construction (the property's ROOT-ONLY arm
|
|
465
|
+
* pins this). That keeps the commit of the typed scope's root-key writes
|
|
466
|
+
* exactly as cheap as it was.
|
|
467
|
+
*/
|
|
468
|
+
private lossyFamilies;
|
|
469
|
+
/**
|
|
470
|
+
* A family whose rows did not fold back, written as what the stage read:
|
|
471
|
+
* one `set` row per member that changed (descendants by last touch, each
|
|
472
|
+
* with its own read prefix), the ROOT last, and the root's read-back value
|
|
473
|
+
* in `overwrite` — one coherent tree, so no row can clobber another. The
|
|
474
|
+
* root's row is written even when the root itself did not change: it is the
|
|
475
|
+
* row that carries the family's read-back (and the containers L-1's dropped
|
|
476
|
+
* op left). A lone root under `'delta'` keeps the compact `append` when the
|
|
477
|
+
* value it read is its base plus a tail.
|
|
478
|
+
*/
|
|
479
|
+
private emitReadBack;
|
|
480
|
+
/**
|
|
481
|
+
* The value the stage's 'full' rows give a path — what the delta encoder
|
|
482
|
+
* commits for a family member or a path with a `set` — read from ONE
|
|
483
|
+
* {@link dryFold} of those rows over the patch trees themselves, taken the
|
|
484
|
+
* first time it is asked. By reference, as the stage held them: an element
|
|
485
|
+
* a `set` and a `merge` of one path share stays one element. A stage that
|
|
486
|
+
* staged no `merge` and no nested op needs no fold — a path's value is its
|
|
487
|
+
* last staged value. (Before 9.30.0: two replicas of the verb law,
|
|
488
|
+
* `replayPathVerbs` and `replayFamilyVerbs`.)
|
|
489
|
+
*/
|
|
490
|
+
private fullRowsValue;
|
|
491
|
+
/** The stage's 'full' rows: every op on a path the net-change filter keeps, a `delete` spelled `set`. */
|
|
492
|
+
private fullRows;
|
|
414
493
|
/**
|
|
415
494
|
* Did the stage change the value at `segments`? Base and final value are
|
|
416
495
|
* fixed at commit, so this is the ONE net-change verdict both payload
|
|
@@ -427,40 +506,6 @@ export declare class TransactionBuffer {
|
|
|
427
506
|
private opsByPath;
|
|
428
507
|
/** The net-change filter — identical to 'full' ({@link changedSinceBase}). Survivors keep last-touch order. */
|
|
429
508
|
private netChangeSurvivors;
|
|
430
|
-
/** Replay each overlapping family ONCE, lazily — most commits have none, and pay nothing. */
|
|
431
|
-
private memoisedFamilyValue;
|
|
432
|
-
/**
|
|
433
|
-
* Bucket the staged ops of every OVERLAPPING family by family root, in op
|
|
434
|
-
* order — the input {@link replayFamilyVerbs} folds. Ops on paths the
|
|
435
|
-
* net-change filter dropped are skipped (they are absent from `rootOf`),
|
|
436
|
-
* exactly as the `'full'` payload drops them from its trace. One pass over
|
|
437
|
-
* `opTrace`, and only when at least one family actually overlaps — commits
|
|
438
|
-
* that write no nested path pay nothing.
|
|
439
|
-
*/
|
|
440
|
-
private opsByFamily;
|
|
441
|
-
/**
|
|
442
|
-
* Replay ONE overlapping family's staged ops onto the family root's base
|
|
443
|
-
* value — the multi-path generalisation of {@link replayPathVerbs}, and
|
|
444
|
-
* byte-for-byte what `applySmartMerge` produces for the corresponding
|
|
445
|
-
* `'full'`-mode entries: each `set`/`delete` position writes that path's
|
|
446
|
-
* `overwritePatch` value, each `merge` position deep-merges that path's
|
|
447
|
-
* accumulated `updatePatch` delta into the value replayed so far.
|
|
448
|
-
*
|
|
449
|
-
* (Both patches are single coherent trees, so a full-mode bundle's stored
|
|
450
|
-
* value at any recorded path always reads back as its `overwritePatch` /
|
|
451
|
-
* `updatePatch` value — which is why sourcing from them here reproduces the
|
|
452
|
-
* full-mode replay exactly, including its intermediate-coercion quirks.)
|
|
453
|
-
*
|
|
454
|
-
* The value is held in a `{ v }` box so the root itself (relative path `[]`)
|
|
455
|
-
* can be REPLACED by `_set` the same way `applySmartMerge` replaces it
|
|
456
|
-
* inside the state tree — including `nativeSet`'s coercion of primitive
|
|
457
|
-
* intermediates.
|
|
458
|
-
*
|
|
459
|
-
* A `set` op the next op sets again is skipped on the same law as
|
|
460
|
-
* `applySmartMerge` ({@link supersededByNextSet}) — this is the delta
|
|
461
|
-
* encoder's own replay loop, and it clones per op just as the fold does.
|
|
462
|
-
*/
|
|
463
|
-
private replayFamilyVerbs;
|
|
464
509
|
/**
|
|
465
510
|
* May a staged `delete` at this path commit as the `'delete'` verb?
|
|
466
511
|
*
|
|
@@ -472,16 +517,4 @@ export declare class TransactionBuffer {
|
|
|
472
517
|
* so those (pathological) deletes keep the historical flattening.
|
|
473
518
|
*/
|
|
474
519
|
private deleteReplaysAsFlattening;
|
|
475
|
-
/**
|
|
476
|
-
* Replay ONE path's op-verb sequence against its base value, exactly the
|
|
477
|
-
* way `applySmartMerge` replays the corresponding full-mode bundle: every
|
|
478
|
-
* `set`/`delete` position applies the LAST staged overwrite value (the
|
|
479
|
-
* bag holds one value per path — last writer wins), every `merge`
|
|
480
|
-
* position applies the ACCUMULATED `updatePatch` delta. This reproduces
|
|
481
|
-
* the full mode's committed value for any interleaving — including the
|
|
482
|
-
* mixed set+merge quirk where the accumulated delta re-applies pre-set
|
|
483
|
-
* merge keys (full-mode replay semantics, kept for byte-parity across
|
|
484
|
-
* modes; property-tested in delta-replay-equivalence).
|
|
485
|
-
*/
|
|
486
|
-
private replayPathVerbs;
|
|
487
520
|
}
|