TanStack

Content temporarily unavailable

Code weight: simplify the draft proxy and share its equality walker

Reviewed executable revision: 556cbd8ff (base 18abceee4, the fetched origin/main at review time). This record follows in a documentation-only commit.

  • a6445a7c2 adds the draft revert oracle and the clone and deepEquals laws on unchanged production code.
  • 639ed5a1e extends the revert oracle after a refactor mutant survived. The extension also passes on unchanged code.
  • 6b68b8dc3 is the refactor.
  • cccaf1b9e returns functions stored in a draft as stored, with a new native-differential law. This is a behavior change and a lost-write fix.
  • 1a07cd3ce applies review fixes: a stronger revert grammar and witness, a duplicate comment, and a redundant key in check in getChanges (about 14 minified bytes).
  • fcc353d5d through a49ae90e3 fix the bugs that a second review found. Each fix extends an oracle first. See Fixes from the second review.
  • 31b2a6bd4 removes draft code that only bought speed. See Code weight over speed.
  • b825e6483 fixes a CodeRabbit finding in the defineProperty fix: a non-configurable property with an object value threw. See Fixes from the second review.
  • 0ce101acf fixes two more lost-write classes from CodeRabbit review: accessor definitions and typed-array mutator methods.
  • cbd2aff06 and 556cbd8ff act on a loss audit of these oracles against docs/contributing/oracle-tests.md. They fix one bug, make the array and typed-array laws generated, and close the audit's grammar, checkpoint, and replay gaps.
  • 7e5dfee25 fixes three bugs that code review found in the earlier fixes: class write-backs, writes after Object.freeze, and subarray reads.

Change

packages/db/src/proxy.ts builds the drafts that collection.update and withChangeTracking pass to callbacks.

  • The write-only proxyCache, the symbol-key branches over assigned_, and the one-use createObjectProxy wrapper are deleted. Every writer of assigned_ stores a string key (String(prop), String(index), or ''), so the symbol branches could not run.

  • The custom array iterator is deleted. Arrays bind the native Array.prototype[Symbol.iterator] to the draft, so each element read takes the get trap and its parent edge. draft[Symbol.iterator]() now returns a native Array Iterator.

  • The set trap's revert path sets modified and calls checkParentStatus on its own tracker. That function already clears tracking and continues up the chain when every value is reverted.

  • draftValuesEqual becomes a draft mode of deepEqualsInternal in packages/db/src/utils.ts. Draft mode keeps Map and Set order, RegExp lastIndex, and array holes. The general mode does not change.

  • A function stored as data (a field, an array element, a nested object member) is now returned as stored. See Stored functions.

The audit prototype also rewrote deepClone as one Reflect.ownKeys loop. That loop made every draft 8% to 17% slower, so the refactor first kept the original two-loop form. 31b2a6bd4 adopts the shorter loop. See Code weight over speed.

Consumer bundle (esbuild, es2020)mingzipbrotli
full public API−1,445 (−0.41%)−343 (−0.33%)−108 (−0.12%)
collection with a filtered live query−1,445 (−0.54%)−363 (−0.46%)−279 (−0.41%)
local-only collection with an insert, an update, and a delete−1,441 (−1.18%)−350 (−1.00%)−223 (−0.72%)
local-only collection with one insert−1,441 (−1.18%)−350 (−1.00%)−227 (−0.73%)

Each row bundles the built dist, with private members renamed, for base and 31b2a6bd4 under the same package.json. The refactor alone (1a07cd3ce) saved 1,739 minified and 453 gzip bytes on the full public API. The fixes from the second review added 917 minified and 303 gzip bytes. 31b2a6bd4 then removed 623 minified and 193 gzip bytes of speed-only code.

Why the oracle work came first

Sixteen source mutants on unchanged code model plausible mistakes in this refactor. Before this work, eight of them passed the whole db suite (7,596 tests). Seven are real gaps:

  • A partial revert treated as a full revert, which drops the remaining change (P2).
  • A nested write under a symbol key treated as no change (E5).
  • deepClone copying non-enumerable string keys, or dropping enumerable or non-enumerable symbol keys (C1, C2, C3).
  • deepEquals comparing RegExp lastIndex, or treating array holes as differences (D2, D4).

The eighth, a wrong parent edge for array iterator elements (P4), is equivalent under every public observation. See Mutant calibration.

A ninth (deepEquals comparing Maps in order) failed one unrelated test only.

The draft revert oracle

packages/db/tests/proxy-revert-oracle.property.test.ts generates write histories on a draft of a three-field row. An operation sets a field to a new value, reverts it to its original, deletes it, writes a nested property (a string or symbol key), writes an array index, or writes a row property through for...of. Values are primitives (including -0 and NaN), Dates, RegExps, Maps (with primitive or Set values), Sets, arrays with holes, objects with a symbol key, and arrays of rows.

The model applies the same operations to plain-data specs and compares values with its own encoding of draft equality. It never reads a draft. After each history, the check asserts:

  • getChanges() holds exactly the fields whose final value differs from the original, each with that value, and deleted fields as undefined.
  • The draft reads back the model's final value.
  • The original row is unchanged.

The generator also emits a revert of a field that is currently changed, so most reverts run the set trap's revert branch. A second property builds partial reverts directly: it changes two or three fields in any order, then reverts all of them but one.

Each property runs a fixed campaign (seed 2026101) and a random campaign: 400 general histories and 200 partial-revert histories. TANSTACK_DB_PROXY_REVERT_SEED and TANSTACK_DB_PROXY_REVERT_PATH select a replay. A positive witness requires the fixed general campaign to reach every operation, a change made only under a nested symbol key, and effective reverts: at least 10 histories where a change survives a revert, and at least 30 histories that end fully reverted. A revert counts only when the field differs from its original before the revert. Eleven pinned histories hold one rule each.

Review of the first version found that its witness also counted reverts of unchanged fields, which write an equal value and do nothing. With those excluded, the first grammar reached 10 partial and 11 full reverts in 400 histories. The changed-field revert and the partial-revert property raise that coverage. Mutant N2 now fails 6 of the oracle's 16 cases, including both partial-revert campaigns.

Limits:

  • Map, Set, and array mutator methods (set, add, push) mark a value changed without a revert check. The native-operation tests in proxy.test.ts own them.
  • Top-level symbol writes are outside the grammar. getChanges() does not report them, and the coverage map lists symbol writes as unsupported.

Clone key classes and deepEquals order rules

  • proxy-detachment-contract.test.ts pins that a draft copies a row's own enumerable string keys and every own symbol key, enumerable or not, and does not copy non-enumerable string keys. This is current behavior, not a promise.
  • utils.property.test.ts adds four generated laws: deepEquals ignores Map and Set insertion order, RegExp lastIndex, and array holes. These are the rules draft equality keeps, so a shared walker must not leak one mode into the other.

Stored functions

The get trap bound every function it returned to the private copy, so it could call built-in methods such as Map.prototype.get, which reject a Proxy receiver. That binding also applied to functions stored as data:

Read on a draft of { handler: f, fns: [f], obj: { g: f } }mainrefactor before the fixnow
draft.handler === ffalsefalsetrue
draft.fns[0] === ffalsefalsetrue
[...draft.fns][0] === f, for...oftruefalsetrue
draft.obj.g === ffalsefalsetrue

On main, array iteration was the only read path that kept identity, because the custom array iterator did not take the get trap. The native iterator does, so the refactor first lost identity there too.

The binding had a worse effect: a stored method saw the private copy as this. A method such as bump() { this.count++ } then changed the copy without tracking, and getChanges() reported no change. Inside collection.update, that write was lost.

cccaf1b9e returns an own data function as stored. A call then sees the draft as this, so its writes take the set trap. Inherited Array, Map, and Set methods keep their draft handling.

proxy.test.ts adds a native-differential law. Each probe runs on a native row and on a draft of an equal row, and the results must be equal. The probes are field access, array index, for...of, spread, includes and indexOf, an array callback, a nested field, Object.values, a Map value, a Set member, a call, a function assigned during the callback, and this inside a stored method. One more case checks that a write through this appears in getChanges(). The law fails 8 of 14 cases on main and 10 of 14 on the refactor before the fix.

Mutant on cccaf1b9eOwners
F1: every function is bound againassertion failure (10/455)
F2: inherited methods are returned unbound tooassertion failure (103/455)
F3: the own-property check reads the original rowassertion failure (1/455)

Fixes from the second review

A second review found one regression in the refactor and several bugs that main also has. Each fix extends an owner first, so the new law fails before the fix. Bytes are for the full public API, minified and gzip, against the commit before.

CommitBugOwner lawFails on mainBytes
fcc353d5dA nested add-then-delete, a fully reverted nested object beside a changed sibling, and a key added as undefined left wrong changes.Revert oracle: nested delete op and nested round-trip property.6 of 24 cases+138 / +49
36030ef8fThe refactor cloned a typed array with new Ctor(source), so a subclass that does not forward its argument cloned empty. A typed array of NaN never equaled itself.Revert oracle: Float64Array, Uint8Array, and a subclass, with index writes. utils.property: typed elements compare like numbers.NaN cases only+133 / +53
967a28c1dThe native iterator bound to the draft made for...of over 200 numbers 4 times slower than main.Iteration contract: arrays visit and publish what a native array does, with an edit while the iterator is open.none (performance)+293 / +93
5b4049cdfGeneral mode allocated a draft entry list for every Map.Existing Map mutants N6, N11, N14.none (performance)−5 / +5
b57d60022at, slice, concat, flat, toReversed, toSpliced, and with returned raw elements, so a write through them was lost. A search for a draft element failed, and constructor was a bound copy.proxy.test.ts: every non-mutating method of Array.prototype, against a native array.15 of 39 cases+38 / +16
38d8ba78fObject.defineProperty(draft, key, { value }) threw unless the descriptor set writable.proxy.test.ts: six definitions against a native row.3 of 6 cases+8 / +8
815432ebdTwo URLs, or two instances whose state is in private fields, were equal, so a draft dropped a write that replaced one.Revert oracle: URL values and a private-field class, with a write-twice property. utils.property: class and identity law.both campaigns+312 / +79
b825e648338d8ba78f defined a clone, so a non-configurable object value broke the Proxy invariants and threw. get would also have wrapped it.defineProperty law: a new key with only an object value, with identity where the invariants require it.throws on main too+9 / +5 (module only)
0ce101acfDefining a getter over a field reported no change. fill, set, sort, reverse, copyWithin, and writes through subarray on a typed-array draft changed the private copy without a change.proxy.test.ts: every method of TypedArray.prototype against a native typed array, and two getter definitions.6 of 31 methods, 2 of 2 getters+70 / +25 (module only)
7e5dfee25The class rule made writing back the row's own class instance a change, because draft snapshots hold class instances as plain objects. After Object.freeze(draft), a nested write was lost. A subarray read counted as a change.Revert oracle: a keyed class instance in original rows. proxy.test.ts: rows after freeze, seal, or a fixed key against native rows, and the typed-array law with and without a write.7 oracle cases and 3 pinned cases on ca0a2170cabout +160 / — (proxy and utils modules)
cbd2aff06sort, reverse, fill, and copyWithin on a draft returned the private copy, not the draft. main has the same bug.N: generated rows record whether a method returned the array itself.the four self-returning mutators, on arrays and typed arrayssmall (one comparison)
556cbd8ffNo product bug. R's grammar could not write back the row's own object, the frozen-key boundary had no nearby witness, and the two-step callback property had no fixed campaign or replay.R: same-object reverts. T: sealed and read-only configurable keys. proxy.test.ts: campaigns and replay.F4 survived before the read-only configurable case0
a49ae90e3None. A detached stored method must throw, as on a native row.proxy.test.ts stored-function law.passes0

Design decisions for these fixes:

  • Typed-array class. Draft equality treats a typed array of another class as a change. deepEquals still ignores the class, as utils.test.ts has pinned since #434.
  • Array read methods. Every non-mutating method reads through the draft. b57d60022 first ran searches, join, keys, and toString on the copy for speed. 31b2a6bd4 removed that list. Running the element methods on the copy and then replacing elements with drafts was faster but cost 167 more gzip bytes.
  • Classes and keyless objects. In both modes, instances of two different classes differ. A plain or null-prototype object from any realm compares by keys with any class, because draft snapshots and plain JSON hold class instances as plain objects. (815432ebd first made a plain object differ from a class instance; 7e5dfee25 reverted that after review found that writing back the row's own instance became a change.) A class instance without enumerable keys equals only itself. URLs compare by href, because a draft copies a URL by its href; by identity, an untouched URL would differ from its own snapshot.

The law that takes its methods from Array.prototype closes a whole class: a new built-in method fails the law until it has arguments. Its last case also caught the bound constructor.

Limits that remain:

  • A draft reads a class instance of the original row as a plain object, so a private-field getter reads undefined. The detachment contract owns that boundary. The revert oracle writes class instances but never starts with one.
  • A built-in method called through this on a nested Map or Set draft, such as Map.prototype.get.call(this.m, key), throws, because the receiver is a Proxy. This is a Proxy limit.
  • Reading an object under a frozen or fixed key of a draft counts that key as changed, because the Proxy invariants require the raw copy. The row may publish an equal value.
  • Writing back a keyless class instance of the original row counts as a change, because the snapshot holds it as an empty plain object.
  • DataView setters on a draft are not tracked. No owner generates a DataView.
  • A mutator method that changes nothing, such as sort on a sorted array or add of a present Set member, counts as a change, so the row publishes an equal value. Arrays, Maps, and Sets did this on main. Typed arrays do too since 0ce101acf. This is a deliberate limit: a revert check after mutators would cost bytes to avoid a publish of an equal value.
MutantOwners
T1: typed clone by constructor argumentassertion failure (7/470)
T2: typed elements compare with !==assertion failure (9/470)
T3: draft ignores typed-array classassertion failure (1/470)
I1: iterator yields raw elementsassertion failure (9/488)
I2: entries() yields no indexassertion failure (7/488)
I3: iterator records a wrong parent edgeassertion failure (19/488)
I4: iterator caches the lengthassertion failure (12/488)
I5: values() reads the raw copyassertion failure (2/488)
I6: entries() reads the raw copyassertion failure (2/488)
A1: element methods read the copyassertion failure (42/527)
A2: searches do not unwrap a draftassertion failure (5/527)
A3: slice runs on the copyassertion failure (2/527)
A4: constructor is boundassertion failure (1/527)
A5: searches read through the draftsurvives (equivalent)
K1: no class checkassertion failure (4/538)
K2: keyless instances compare by keysassertion failure (5/538)
K3: URLs compare by identityassertion failure (10/538)
K4: a null prototype is another classassertion failure (2/538)
K5: any two URLs are equalassertion failure (4/538)
K6: empty arrays compare by identityassertion failure (8/538)
K7: a URL equals a non-URL with the same hrefassertion failure (2/538)

I4 first survived every owner, because no test edited an array while an iterator was open. The iteration contract's array law closes that gap. K7 first survived too, and utils.property now compares a URL with an object that inherits the same href. A5 gives the same results and is only slower: searches through the draft compare memoized drafts.

Code weight over speed

Drafts run inside collection.update callbacks. These calls are rare and almost never touch large values, so draft speed does not matter, and bytes do. 31b2a6bd4 removes code that only bought speed:

RemovedSpeed it boughtLaw that still owns the behavior
The light array iterator from 967a28c1dfor...of over 200 numbers: 0.98 of main instead of 4.0Iteration contract, array law
The list of array methods that ran on the copyjoin() on 50 numbers: 1.03 instead of 2.8Array.prototype law in proxy.test.ts
The primitive fast path in the get trapslice on small arraysEvery proxy owner
The two-loop deepClone key walk8% to 17% for each draftDetachment contract key-class law
The typed-array element loop (TypedArray#set copies instead)noneRevert oracle typed values
The deferred class check in deepEqualsunequal rows exit before the class checkClass law in utils.property

Mutants of each new form fail the owners:

Mutant on 31b2a6bd4Owners
C1: clone copies non-enumerable stringsassertion failure (2/539)
C2: clone drops every symbolassertion failure (10/539)
C3: clone drops non-enumerable symbolsassertion failure (2/539)
T1: typed clone stays emptyassertion failure (13/539)
N3: the iterator reads the raw copyassertion failure (16/539)
A1: read methods run on the copyassertion failure (56/539)
A2: searches run on the copyassertion failure (4/539)
K1: no class checkassertion failure (4/539)
K2: keyless instances compare by keysassertion failure (5/539)
K3: URLs compare by identityassertion failure (10/539)
K4: a null prototype is another classassertion failure (2/539)
K5: any two URLs are equalassertion failure (5/539)
K6: empty arrays compare by identityassertion failure (8/539)
D1 (on b825e6483): get wraps a read-only non-configurable valuecrash, the reported TypeError (1/540)
D2 (on b825e6483): defineProperty stores a clonecrash, the reported TypeError (1/540)
G1 (on 0ce101acf): accessor definitions untrackedassertion failure (2/574)
G2 (on 0ce101acf): every definition tracked, sealing tooassertion failure (1/574, the existing Object.seal test)
Y1 (on 0ce101acf): typed-array mutators untrackedassertion failure (6/574)
Y2 (on 0ce101acf): subarray untrackedassertion failure (1/574)
Y3 (on 0ce101acf): typed-array set untrackedassertion failure (1/574)
R1 (on 7e5dfee25): a plain object differs from a classassertion failure (10/611)
R2 (on 7e5dfee25): no class checkassertion failure (2/611)
R3 (on 7e5dfee25): the keyless rule needs a on the class sideassertion failure (4/611)
F1 (on 7e5dfee25): a frozen-key object read is not a changeassertion failure (2/611)
F2 (on 7e5dfee25): a frozen-key primitive read is a changeassertion failure (1/611)
S1 (on 7e5dfee25): subarray returns a plain viewassertion failure (2/611)
S2 (on 7e5dfee25): subarray marks a change on callassertion failure (1/611)
M1 (on cbd2aff06): mutators return the raw copyassertion failure (12/587)
M2 (on cbd2aff06): mutators always return the draftassertion failure (10/587)
A1, A2, N3, Y1, Y3, S1, S2 (rerun on cbd2aff06 against N)assertion failure (55, 21, 23, 7, 3, 3, 3 of 587)
F3 (on 556cbd8ff): the frozen-key boundary checks configurability aloneassertion failure (1/589)
F4 (on 556cbd8ff): the frozen-key boundary checks writability aloneassertion failure (1/589); survived before the read-only configurable case

The I and A3 to A5 mutants in the previous section apply to code that 31b2a6bd4 removed.

Mutant calibration

"Owners" are the revert oracle and the four existing owners (proxy.test.ts, proxy-detachment-contract.test.ts, proxy-iteration-contract.test.ts, utils.property.test.ts). "Before" is the owners without this change's tests. The full-suite column shows whether any other db test caught the mutant.

On unchanged production (18abceee4)

MutantBeforeFull suite beforeOwners now
P1: a nested revert does not reach the parent20/417—assertion failure (21)
P2: a partial revert is treated as full0/4170/7,596assertion failure (3)
P3: the array iterator yields raw elements1/417—assertion failure (4)
P4: the array iterator records a wrong parent edge0/4170/7,596survives (equivalent)
C1: deepClone copies non-enumerable strings0/4170/7,596assertion failure (2)
C2: deepClone drops symbol keys0/4170/7,596assertion failure (4)
C3: deepClone drops non-enumerable symbols0/4170/7,596assertion failure (2)
E1: draft Set comparison ignores order2/417—assertion failure (3)
E2: draft Map comparison ignores order2/417—assertion failure (3)
E3: draft RegExp ignores lastIndex2/417—assertion failure (3)
E4: a draft array hole equals undefined1/417 (crash)—assertion failure (3)
E5: draft comparison ignores symbol keys0/4170/7,596assertion failure (2)
D1: deepEquals compares Maps in order0/4171/7,596assertion failure (2)
D2: deepEquals compares RegExp lastIndex0/4170/7,596assertion failure (2)
D3: deepEquals compares Sets in order3/417—assertion failure (5)
D4: deepEquals treats array holes as differences0/4170/7,596assertion failure (2)

P4 is equivalent under every public observation. An array element's parent edge feeds only the array's own revert check, and getChanges() works at the row level, where it compares each field with draft equality. A wrong edge can make the array mark itself reverted early, but the row then compares the field by value and still reports the change. The refactor's native iterator records the right edge through the get trap.

On the refactor (6b68b8dc3)

MutantOwners
N1: a nested revert does not reach the parentassertion failure (25/441)
N2: a partial revert in the set trap clears all trackingassertion failure (26/441)
N3: the array iterator binds the raw copyassertion failure (4/441)
N4: deepClone drops non-enumerable symbolsassertion failure (2/441)
N4b: deepClone drops every symbolassertion failure (4/441)
N5: deepClone copies non-enumerable stringsassertion failure (2/441)
N6: draft Map comparison ignores orderassertion failure (3/441)
N7: draft Set comparison ignores orderassertion failure (3/441)
N8: draft RegExp ignores lastIndexassertion failure (3/441)
N9: draft arrays use the element walkassertion failure (2/441)
N10: general mode compares lastIndexassertion failure (2/441)
N11: general mode compares Maps in orderassertion failure (2/441)
N12: general arrays use the key walkassertion failure (4/441)
N13: draft mode is not passed into object valuesassertion failure (1/441)
N14: draft mode is not passed into Map valuesassertion failure (1/441)

N14 first survived, because generated Map values were only primitives. 639ed5a1e lets Map values be nested Sets and adds a pinned case for a reordered Set inside a Map value.

Performance

Each timing process loads one implementation and runs each workload five times after two warm-up passes. Processes alternate, and each ratio is the median of 11 processes of each. An A/A control of two copies of the base stayed within ±1%.

WorkloadA/APrototype loopKept loop
draft: two writes, then getChanges (20,000 rows)1.0101.0120.827
draft: a write, then a revert (20,000 rows)1.0061.1630.900
draft: Map, Set, and array writes (10,000 rows)1.0050.9100.772
deepEquals: equal rows (20,000)0.9930.9530.965
deepEquals: equal rows with Map, Set, RegExp (10,000)1.0101.0191.023
deepEquals: unequal rows (20,000)0.9991.0241.009
draft: 20 Array method calls (10,000 rows)1.021—0.961

Each ratio is new time over base time. "Prototype loop" is the audit prototype. "Kept loop" is the final code. The array-method row was measured with the stored-function fix, which adds an own-property check before each inherited method is handled. A second run with the fix gave 0.830, 0.876, and 0.766 for the three draft rows above. Bisection showed that its deepClone loop caused the regression: the equality fold alone was faster (0.92, 1.00, 0.81 on the draft rows), and the other proxy changes with the original loop were faster too (0.92, 0.93, 1.03). deepClone runs twice for each draft, and the Reflect.ownKeys loop calls propertyIsEnumerable for each key. Keeping the two-loop form costs about 18 minified bytes. The harness is not checked in.

Timings for the fixes

These timings describe a49ae90e3. 31b2a6bd4 removed the iterator and the copy-method list for code weight, so the iteration and join rows no longer hold. These runs used 11 processes of each variant. The machine was loaded, and an A/A control stayed within ±5%.

WorkloadRatioCompared with
for...of and spread over 200 numbers0.98 (4.02 before 967a28c1d)main
for...of over 200 objects0.99main
slice(0, 1) on a 2-element draft array1.45the commit before b57d60022
slice() on a 50-number draft array5.6the commit before b57d60022
join() on a 50-number draft array1.03the commit before b57d60022
deepEquals: equal rows1.06 to 1.08the commit before 815432ebd
deepEquals: unequal rows1.00the commit before 815432ebd

The iterator first used a generator, which made object elements 1.4 times slower. It also named the get trap as a function expression, which made every draft write about 7% slower. The kept iterator is a module function. It calls the trap through the handler object and skips primitives. The class check in deepEquals runs after the keys match, so an unequal row exits before it.

ORC-001 through ORC-014

The owners are:

  • R, the revert oracle proxy-revert-oracle.property.test.ts: four generated properties over one grammar (general histories, partial reverts, nested round trips, and URL and keyless writes).
  • N, the native-methods oracle proxy-native-methods.property.test.ts: generated array and typed-array calls compared with native values.
  • T, the native-differential tables in proxy.test.ts: stored functions, defineProperty, and frozen and sealed drafts. These are bounded enumerations, not important generated properties.
  • U, the laws this change adds to utils.property.test.ts.
RequirementResult
ORC-001R states the getChanges() contract, nine draft-equality rules, the three laws, their authority (rules 1 to 6 from src/proxy.ts and earlier tests, rules 7 to 9 from this record), and the limits. N and each T table open with the contract they check, against a native value.
ORC-002R's model is an encoding of plain-data specs. It does not import draftValuesEqual, deepEquals, or deepClone, and it never reads a draft. N and T take the expected result from the same call on a native value. U builds each pair with a known expected relation.
ORC-003R keeps contract, model (canon, step, expectedChanges), grammar (the arbitraries), driver (drive, realize), and check (expectHistory) apart in one file. N's opening comment names the same five parts.
ORC-004See ORC-004 controls below.
ORC-005R and N drive the draft that createChangeProxy or withChangeTracking returns. The checkpoint is the end of the callback, before publication: R compares getChanges() and the draft, and N the call result and the published row. Positive witnesses: R's fixed general campaign reaches every operation, both revert outcomes, 20 or more same-object reverts, and a class-instance write-back. R's URL and keyless witness requires 10 or more of each write. N's witnesses reach every method, empty rows, holes, nested objects, NaN, and -0. The partial-revert and nested round-trip properties reach their histories by construction.
ORC-006Every mutant in this record has an outcome class. P4 and A5 are equivalent within the tested domain, with reasons.
ORC-007R, N, and the two-step callback property each run a fixed campaign (seeds 2026101, 2026102, 2026103) and a random one with the same property, grammar, check, and budget. Replay: TANSTACK_DB_PROXY_REVERT_*, TANSTACK_DB_PROXY_NATIVE_*, and TANSTACK_DB_PROXY_CALLBACK_* accept a seed and path and run only the replay. Under mutant M1, a captured random failure ([[], "sort", 0]) replayed directly to the same counterexample. U uses the replay interface utils.property.test.ts already has.
ORC-008R's model is a stateless recomputation over specs, and N and T use native values, so this requirement does not apply.
ORC-009Terms match proxy.ts and utils.ts: draft, revert, changes, draft equality. Model-only spec kinds such as rows and point are named in R.
ORC-010fast-check reports seed, path, and shrunk input. R's messages give the field and the history. N's give the row, method, and argument.
ORC-011N and T are native-differential, a second formulation independent of R's model. No shared semantic fault was found that would call for another one.
ORC-012This record ties the revisions, outcome classes, limits, and performance evidence to the coverage map. See Bug-class closure below.
ORC-013See Reusable boundary laws below.
ORC-014No controlled provider or host supplies a premise. Drafts run in-process on the JavaScript engine, so this requirement does not apply.

ORC-004 controls

R (all four properties share one grammar).

  • Reconstruction: the pinned histories rebuild each rule and each reported bug in R's own grammar and check. These include a nested add-then-delete, a stale parent edge, a key added as undefined, a typed-array subclass, typed NaN, a URL write, a keyless write, and a class-instance write-back. The general grammar reaches each of their shapes. 556cbd8ff added the same-object revert so that a write-back of the row's own object is generated, not only pinned.
  • Ablation: each mutant in Mutant calibration and Fixes from the second review removes one rule, edge, or clause. Every one fails an owner, except P4, which is equivalent.
  • Range: three fields. Values are primitives 0, -0, 1, NaN, "a", "b", null, and undefined; Dates (including invalid); RegExps with lastIndex 0 or 1; Maps of up to two entries, with values that may be Sets; Sets of up to two members; arrays of up to three slots with holes; objects with up to two string keys and a symbol key; arrays of rows; typed arrays of up to three elements (three classes); URLs; a keyed class instance; and, as written values only, a keyless class instance. The marginal cases are empty containers, -0 and NaN, holes, and an absent key against a key holding undefined. Typed-array subclasses, URLs, keyless instances, and the class write-back each exposed a missing rule when first added.
  • Exclusion: applicable rejects a nested write on a value that is not an object, an index write beyond the length or into a Uint8Array, and a revert of a field that is absent both originally and now. Model and driver skip the same operations. Mutator methods and top-level symbol writes are outside the grammar.

N (arrays and typed arrays).

  • Reconstruction: the rows of the earlier pinned tables run for every method as fixed cases.
  • Ablation: mutants A1, A2, N3, M1, M2, Y1, Y3, S1, and S2 each fail an owner.
  • Range: array rows of up to four slots from 0 to 3, undefined, holes, objects, and nested arrays of up to two objects. Typed rows of up to four values from 0, -0, 1, 2, NaN, and 3.5. An index-shaped argument from 0 to 4, so negative and out-of-range indexes occur. Every method of the built-in prototypes. The marginal cases are empty rows, holes, and duplicate elements.
  • Exclusion: every generated call is legal JavaScript, and a native throw is an observation that the draft must reproduce. A callback that writes is outside the grammar.

U. The laws use small fixed value sets with the pinned pairs inside the same property. Mutants K1 to K7 and R1 to R4 are the ablations. Every pair in the domain is legal, so no exclusion applies.

The two-step callback property was already on main. This change only adds its campaigns and replay, and it makes no new grammar claim for it.

Reusable boundary laws (ORC-013)

LawPremise witnessNearby witnessWrong boundary it rejects
A key that is both read-only and non-configurable returns the raw value and counts as changedfreeze, then a nested writea sealed key, and a read-only configurable key, read without a changeF3 (configurability alone), F4 (writability alone)
Two different classes differ, and a plain object compares with any class by keysPoint against OtherPoint against a plain object, and a draft write-backR1 (a plain object differs from a class), R2 (no class check)
A keyless class instance equals only itself; a URL compares by hrefSecret against SecretSecret against {}, and a URL against an object that inherits the same hrefK2, R3, K7
Typed-array class counts only in draft modethe draft Float64Array to Uint8Array writedeepEquals of Uint8Array and Int8ArrayT3, and the #434 test against a class check in general mode
Every non-mutating array method reads through the draftgenerated rows for every methodsearches with draft elements and duplicatesA1 (all methods on the copy), A2 (searches on the copy)

Bug-class closure (ORC-012)

Each class below is bounded by contract, histories, production path, and observation. Open cells name their owner.

ClassBoundaryOpen in-scope cells
Stored functions read back boundAny draft read path (field, index, iterator, spread, Map value, Set member, Object.values) on the get trap; observed by identity and getChanges()A built-in method called through this on a nested Map or Set draft rejects the Proxy (T, a Proxy limit)
Array and typed-array methods lose writes or identityEvery built-in method on generated rows (N); observed by result, identity, returned self, and the published rowA callback that writes (N, outside the grammar); a no-op mutator publishes an equal value (N and R, declared limit)
defineProperty throws or loses changesValue, accessor, read-only, and fixed definitions (T); observed by result, descriptor, read, and changesNone known
Nested reverts leave stale changesR's nested, delete, and round-trip histories; observed by getChanges()Mutator methods have no revert check (R, declared)
Equality hides writes of URLs, keyless objects, and classesR's URL, keyed, and keyless class values and U's pairs; observed by getChanges() and deepEqualsWriting back a keyless instance of the original row is a change; a draft reads a class instance of the original row as a plain object (detachment owner)
Frozen drafts lose nested writesFreeze, seal, and fixed keys (T); observed by the published rowAn object read under a frozen key publishes an equal value (T, declared)

None of these classes has a known reachable counterexample to its claimed law. The open cells are listed in the coverage map's Drafts row.

Guide prompts that do not apply

The guide's reusable boundary-law checklist targets adapter and lifecycle oracles. Drafts have no provider, classifier, transport, await boundary, or acquired resource, so real-provider conformance, minimal ambiguity, name invariance, await-boundary transitions, local/transport refinement, and partial-construction cleanup do not apply. Representation symmetry appears as the class rule above. Value-and-work refinement does not apply, because no work bound is promised.

Verification

On 556cbd8ff, which includes origin/main at 3463cf8ad, with the built dist:

  • packages/db Vitest, typecheck off: 199 files, 7,801 tests.
  • packages/db tsc --noEmit: no errors.
  • pnpm check:mangle: 368 names.
  • pnpm test:minified-db: error names, index metadata, query rows, and live updates.
  • pnpm --filter @tanstack/db test:dist: 5 files, 290 tests.

The environment was Node v24.19.0 and Vitest 3.2.4 on Darwin arm64.