Yjs
Changelog
Release notes for @homeostate/crdt-yjs: what changed in each version, newest first.
0.3.0
Minor Changes
-
de1c8a7:
createYjsBackendreturns aYjsBackend, aCrdtBackendwith two more members:map, the syncedY.Map, andorigin, which tags every transaction itswriteruns. Track that origin withY.UndoManagerto undo and redo the store's own edits:const backend = createYjsBackend(doc, "shared"); createSyncEngine(backend, adapter).connect(); const undoManager = new Y.UndoManager(backend.map, { trackedOrigins: new Set([backend.origin]), });createYjsBackend(map, options)syncs aY.Mapyou already have, such as one nested in another map or in a subdocument; a map that is not part of a document throws aTypeError. The neworiginoption sets the origin, which must be unique to the backend; it defaults to a private Symbol, as before. -
6f2f2e1: Report a synced map that a peer replaced. Once a peer set a new map in place of a backend's nested map, or of a map holding it, the backend lost every write, its store never saw the peer's state, and nothing told the app. It now calls the new
onRemovedoption once, which logs the error withconsole.errorby default, so the app can connect a new engine over the map that replaced it.createYjsBackendalso throws aTypeErrorfor a map already removed from its document. -
aa7cbc1: Implement
CrdtBackend.revision: it counts the document's transactions that changed something, and isundefinedwhile one is open. The sync engine then applies a remote change another listener heard of first before writing a local change, instead of the write throwingLength exceeded!on items the peer had deleted.writenow diffs against thepreviousit is given even inside another transaction, where it used to read the document; the engine checks the revision before passingprevious. Use it with the@homeostate/corerelease that addsCrdtBackend.revision, since an older engine passespreviouswithout that check.
Patch Changes
- a80be3b: Build the backend with
createCrdtBackendfrom@homeostate/core, so the default text policy, what a write diffs against and leaving its own writes out ofsubscribeare written once, in core. Options, extras such as the Yjs backend'smapandorigin, and behaviour are unchanged. Use it with the@homeostate/corerelease that addscreateCrdtBackend. - ceca144: Leave copying objects that a peer's
__proto__entry gave a prototype to the sync engine, which now does it for every backend.read()may return such an object as the document holds it; the store still never receives one. The Yjs backend still leaves__proto__entries of aY.Mapout of what it reads. Use it with the@homeostate/corerelease whose engine copies such objects. - 55812ce: Implement the
CrdtBackendwhosereadreturns aJsonObjectand whosewritetakes aJsonObjectInput, with no casts at the boundary. Use it with the@homeostate/corerelease that narrowsCrdtBackend.readandwrite. - Updated dependencies [39809d7]
- Updated dependencies [a80be3b]
- Updated dependencies [60331b4]
- Updated dependencies [567932b]
- Updated dependencies [55812ce]
- Updated dependencies [d2a66d8]
- Updated dependencies [8ed9541]
- Updated dependencies [ceca144]
- Updated dependencies [3bdd635]
- Updated dependencies [9a6e0c6]
- Updated dependencies [aa7cbc1]
- Updated dependencies [3b9d6dc]
- Updated dependencies [9ce5ff0]
- Updated dependencies [cde0b51]
- Updated dependencies [074eadc]
- Updated dependencies [7e7171a]
- @homeostate/core@0.4.0
0.2.0
Minor Changes
-
f77d8a6: Add document persistence, so a synced document survives reloads, offline starts, and every peer and the sync server losing it.
createPersistencein@homeostate/corekeeps a CRDT document in aPersistenceAdapter;createYjsPersistable,createLoroPersistableandcreateAutomergePersistableexpose each backend's document to it; and the new@homeostate/persist-indexeddband@homeostate/persist-local-storagepackages store it in IndexedDB or Web Storage.createMemoryPersistenceAdapterin@homeostate/core/testingis an in-memory adapter for tests. -
d8e9906: Store strings as plain values unless a
textoption marks them as collaborative text. Every backend stored every string as its text type and merged concurrent replacements character by character, so two peers setting a filter to"active"and"completed"at once converged to"compctiveeted"; ids and timestamps merged the same way. Concurrent writes of a string now keep one of the written values.Mark prose that should keep merging with the backend's new
textoption, which receives each string's path from the synced root:createYjsBackend(doc, "shared", { text: (path) => path[0] === "todos" && path[2] === "title", });Yjs and Loro store other strings as plain map and list values, and Automerge as
ImmutableString.read()returns plain strings for both kinds. Documents written by earlier releases hold every string as text: they read as before, and each string is stored as the configured kind the next time it changes. Give every backend writing to a document the same policy.Core adds
getChanges(a, b, { text }), which replaces strings outside the policy with oneUPDATE; without the option every string is still diffed character by character. It also exports theTextPolicyandDiffOptionstypes, and exportsapplyStringChangesagain for backends that find text stored as a plain value.
Patch Changes
-
732c2c8: Replace a plain array or object that other code stored in the synced map on the next write that changes it. Writing over a plain array threw, leaving earlier changes of the write applied on Yjs, and changes to a plain object were dropped, so the store and the document disagreed.
-
456fbad: Write
undefinedand functions the wayJSON.stringifydoes in every backend: object entries holding them are left out, and such array items, holes included, becomenull. Writing the same state again produces no update.- Yjs threw halfway through a write on an
undefinedarray item or a nested function, and the part already applied replicated to peers, sometimes deleting an existing item.undefinedobject values were stored as present keys. - Loro threw halfway through a write on a nested function or a sparse array, leaving a partial
subtree that replicated. It stored
undefinedasnull, and resent everyundefinedarray item on each write. - Automerge threw on every write after the store held an
undefinedarray item, so nothing synced from that store again, and stored nested functions as{}.
getChangestakesjson: trueto diff the new state by that rule, passing only the values its changes carry throughtoJsonValue, newly exported from@homeostate/core, so a write does not copy the whole state. Custom backends should set it onwrite(next). - Yjs threw halfway through a write on an
-
314e268: Sync keys named after
Object.prototypemembers, and stop peers from replacing object prototypes through a__proto__key.getChangesand the sync engine compared keys within, so keys such asconstructorortoStringcounted as always present and their deletions never replicated; the Valtio adapter had the same fault. A__proto__key is now never diffed and is left out of remote state at any depth. The Yjs and Loro backends'read()turned a peer's__proto__entry into an object's prototype, so its properties, such asisAdmin, appeared on other users' state; it now returns plain objects. -
008091c: Diff each write against the
previousstate the sync engine passes, instead of serialising the document and comparing every item, so a local change costs the change rather than the whole state. A toggle at 500 todos took 146 µs with Yjs, 1.13 ms with Loro and 298 µs with Automerge, and now takes 14 µs, 35 µs and 182 µs. The Yjs backend still reads its document when written inside another transaction, and the Loro backend while other code has uncommitted edits, since those changes are not reported yet. -
Updated dependencies [732c2c8]
-
Updated dependencies [f901667]
-
Updated dependencies [d858951]
-
Updated dependencies [f77d8a6]
-
Updated dependencies [29f8f0d]
-
Updated dependencies [456fbad]
-
Updated dependencies [30e31df]
-
Updated dependencies [314e268]
-
Updated dependencies [4564712]
-
Updated dependencies [d8e9906]
-
Updated dependencies [008091c]
- @homeostate/core@0.3.0
0.1.3
Patch Changes
-
33c7b14: Mark the package side-effect free, so bundlers can drop modules that are not imported.
-
fb76f77: Preserve complete Unicode code points when diffing and applying string edits. Deleting or replacing supplementary characters such as emoji no longer corrupts text, deletes adjacent characters, or throws in the supplied CRDT backends.
String
Changeoffsets remain UTF-16 code units. A string deletion can now carry a numeric UTF-16 deletion length in its third tuple entry;undefinedstill means one unit. Custom backends consuminggetChangesmust honor this length in one operation rather than delete one code unit per step. Array and object deletion semantics are unchanged. -
Updated dependencies [a643bbe]
-
Updated dependencies [0bbf653]
-
Updated dependencies [fb76f77]
- @homeostate/core@0.2.0
0.1.2
Patch Changes
- 30abf27: Remove draft release warnings, update docs links
- Updated dependencies [30abf27]
- @homeostate/core@0.1.2
0.1.1
Patch Changes
- 8bd452e: Init release
- Updated dependencies [8bd452e]
- @homeostate/core@0.1.1