Repository navigation
Immer documents without immutable.js (sideEffects: false) - #21
Conversation
williamstein
left a comment
There was a problem hiding this comment.
Automated technical review of exact head 0d5f076d9a8553daa803d31ab72d21a71d22808b.
Finding to address before merge: sameElements is not actually equivalent to the old List(a).equals(List(b)). Immutable's list equality delegates element comparison to Immutable.is, not Object.is. In particular, the old code treats 0 and -0 as equal, compares Date values through valueOf(), and compares Immutable value objects through .equals(); the new code does none of those. Patch.patch is unknown and PatchGraph supports custom codecs, so a file patch can legally contain such top-level values. In that case two loads previously deduped can now be applied twice. Please preserve the old element predicate locally (without importing Immutable), or deliberately narrow/normalize the supported patch contract and cover the changed behavior with tests.
Non-blocking cleanup: mergeSet and nonnullCols gained export when moved into db-document-immutable.ts; because index.ts re-exports that module with export *, this unintentionally adds two internal helpers to the package's public API. They can remain file-local.
The requested focused checks otherwise look good:
- Native-Map iteration: all externally observable lists (
versions,history, heads/value heads) are sorted; the remaining map traversals only build sets/fixpoints. I found no order-sensitive consumer. - Consumers: CoCalc's sync package and Sagebrush TimeTravel use public methods only; neither accesses
PatchGraph's private maps. Generated declarations still expose those fields only asprivate patches/private children. sideEffects: false: there are no side-effect-only imports. The top-level initializers configure module-local objects or Immer support in modules retained when their exports are used. A Bun browser bundle ofSession + createImmerDbCodecwas 103,896 bytes; its metafile included Immer and excluded the Immutable module.
Independent validation on this head passed 157 tests (1 skipped), oxlint, and types/ESM/CJS builds. I also merged #21 locally onto exact #22 head: pnpm audit was clean and the same full check passed. #21's current CI failure is the Jest audit failure fixed by #22, so it still needs the planned rebase and a green post-rebase run.
A browser bundle that uses Session with the immer documents carried all of immutable.js: patch-graph.ts used immutable Maps and a List comparison, and db-util.ts imported immutable for one Map.isMap check and two helpers of the immutable documents. - patch-graph.ts: native Maps for patches and children (Map.set returns the map, so the assignments stay as they were); keys/values via the Map iterators; the file-load dedup compares patches element by element as List(a).equals(List(b)) did (Object.is; nested values by identity). - db-util.ts: recognizes an immutable Map by the marker Map.isMap checks, without importing immutable; mergeSet and nonnullCols move to db-document-immutable.ts, their only user. - package.json: "sideEffects": false, so bundlers drop the modules a program does not use (db-document-immutable.ts and immutable.js for immer users). Session + createImmerDbCodec + decodePatchId, minified by Bun for the browser: 167 kB before, 103 kB after. Tests: 157 pass (1 suite skipped, as before); tsc and oxlint clean.
…exported Review (lite4-review): sameElements used Object.is, but List(a).equals(List(b)) compared elements with immutable's is(): SameValueZero (+0 equals -0), valueOf() (equal Dates, a Date and its time, boxed values) and value objects (equals/hashCode). src/same-elements.ts now ports is() and List()'s view of array-likes (strings included), internal to the package; a test checks it against immutable's List.equals on those cases. mergeSet and nonnullCols are module-private again: moving them into db-document-immutable.ts had made them root exports.
0d5f076 to
dbca2ce
Compare
|
Addressed lite4-review's review in dbca2ce. Element equality (the finding).
Exports (non-blocking). Stacking.
Checks:
|
williamstein
left a comment
There was a problem hiding this comment.
Second automated review of exact head dbca2ce95df081de401743e1be13e94da2db2f50.
The element comparator now correctly ports Immutable.is for the cases raised previously, and the accidental root exports are fixed. One related semantic gap remains before this is equivalent to the old code:
elements() does not port the input conversion performed by List(value). The old List(patch.patch) accepts native and Immutable iterables, not only arrays/strings/objects with a numeric length. For example, separate Set([1, 2]), Immutable.List([1, 2]), Immutable.Set([1, 2]), and generator values compare equal under the old List(a).equals(List(b)), but sameElements returns false for all of them. This is observable through the public generic codec API: with a custom codec whose patch is Immutable.List([1]), two otherwise-identical nearby file patches produce value 1 on the old PatchGraph (deduped) and value 2 on this head (applied twice). Patch.patch remains unknown, so those patch containers are permitted.
Please handle the iterable protocol as List() did (while retaining the string behavior), and add at least an Immutable List or Set container regression test. The new tests currently exercise element equality thoroughly but pass arrays/strings as the outer containers, so they miss this conversion difference.
Everything else rechecked cleanly:
pnpm audit: no known vulnerabilities.pnpm check: 173 passed, 1 skipped; lint/types/ESM/CJS clean.- CI is green and the branch is correctly based on
fix-audit. - Built root exports no longer include
mergeSet/nonnullCols(or the internal equality helpers). - Bun browser bundle remains free of the Immutable module (104,468 bytes in this checkout).
Second review (lite4-review): List(value) also takes iterables and immutable collections (native Sets and Maps, immutable Lists, Sets, Maps, Seqs, records, generators), and List.equals compared their elements; the port only took arrays, strings and array-likes, so a codec whose patches are, e.g., immutable Lists stopped deduping identical file loads (applied twice). elements() now follows List() exactly (immutable 4.3): lists and indexed collections as they are; keyed collections and records by their entries; other collections by toIndexedSeq(); immutable's isArrayLike rule; any iterable (Symbol.iterator or @@iterator). Where List() throws (a value of none of these kinds) the patches are not equal. Tests: every container kind against immutable's List.equals, and a PatchGraph regression with a codec whose patches are immutable Lists: two identical file loads close in time apply once (the previous port applied them twice).
|
Addressed the second review in a34c1f7.
Elements are compared with the ported Tests:
Checks:
|
williamstein
left a comment
There was a problem hiding this comment.
Third automated review of exact head a34c1f7fd8644d21be96c6ab330e88e542feb1cb: no remaining findings.
I compared the conversion logic against Immutable 4.3's List / IndexedCollection implementation: Immutable list/indexed handling, keyed collection and Record entries, other collection values, the exact array-like predicate, and native/faux iterator paths are all represented. The new container matrix covers the meaningful branches. I also reran the previously failing custom-codec reproduction independently: two Immutable.List([1]) file patches now produce 1 on both the old and new PatchGraph.
Independent validation:
pnpm install --frozen-lockfilepnpm audit— no known vulnerabilitiespnpm check— 190 passed, 1 skipped; oxlint and types/ESM/CJS builds clean- CI green; branch remains mergeable on
fix-audit - Built root exports contain none of the internal helpers
- Bun
Session + createImmerDbCodecbrowser bundle is 105,254 bytes and its metafile excludes the Immutable module
The two earlier review findings are resolved. Technically ready subject to the planned stacked merge order and William's decision.
Why
Sagebrush's TimeTravel (sagebrush.space) records notebook history with
SessionandcreateImmerDbCodec. Its browser bundle carried all of immutable.js even though the immer documents never use it:patch-graph.tsused immutableMaps and aListcomparison;db-util.tsimported immutable for a singleMap.isMapcheck and two helpers used only by the immutable documents.What
patch-graph.ts:Maps forpatchesandchildren.Map.setreturns the map, so the existingthis.patches = this.patches.set(...)assignments are unchanged.keySeq()/toArray()becomeMapiterators.List(a).equals(List(b))did:Object.is, with nested arrays and objects compared by identity.db-util.ts:Mapby the markerMap.isMapchecks (@@__IMMUTABLE_MAP__@@), without importing immutable;mergeSetandnonnullColsmove todb-document-immutable.ts, their only user.package.json:"sideEffects": false, so bundlers drop the modules a program does not use. For immer users that meansdb-document-immutable.tsand immutable.js.Behavior notes:
toKeyof an immutableMapstill converts it withtoJS().Measured
import { Session, createImmerDbCodec, decodePatchId } from "patchflow", minified by Bun for the browser:Checks
pnpm test: 157 pass, 1 suite skipped (as onmain).tsc --noEmitandpnpm lint(oxlint): clean.pnpm build: works.No version bump; that is left to you.