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.
@@ -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
- constructor(base: any, commitValues?: CommitValuesMode, readKeysProvider?: () => string[]);
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: it is lost unless the stage
232
- * writes the value back, and then it is recorded; committed state is never
233
- * touched.
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
- * preserving the set-vs-merge verb so replay ({@link applySmartMerge}) is
346
- * byte-for-byte identical to recording only the real changes.
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 — byte-identical to
349
- * the historical behavior, including flattening staged `delete` ops into
350
- * `set`-of-`undefined` trace entries. The delta encoding lives in
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 (replaying the accumulated delta once ≡ the full mode's
381
- * k sequential replays — `deepSmartMerge` is reference-idempotent
382
- * within one replay pass);
383
- * - otherwise (`set`/mixed): the committed value is computed by
384
- * replaying the path's op sequence EXACTLY the way `applySmartMerge`
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. The `'full'`
396
- * payload survives that because every entry stores the FULL value at its
397
- * path, drawn from one coherent tree — nested entries agree with their
398
- * ancestor by construction. Delta's per-path encodings do NOT: an
399
- * `append` ancestor stores only a TAIL (whose indices are shifted
400
- * relative to the whole array) and a `delete` ancestor stores
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
- * Losslessness never depends on detection succeeding — every fallback is
411
- * today's full-value `set`.
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
  }