# selfstore vs Yjs Source: https://selfstore.dev/compare/yjs Yjs is the CRDT that merges concurrent edits losslessly; selfstore is the durability, encryption and destinations around a document. Use both. ## What Yjs is The CRDT that won. A high-performance implementation of shared types - text, arrays, maps - that merge without a coordinator and without losing a keystroke, under the hardest case there is: two people typing in the same paragraph at the same moment. It is what sits under a great many collaborative editors, and if your problem is concurrent editing, nothing here competes with it. What Yjs deliberately does not decide is everything around the document. Persistence is a provider you choose (`y-indexeddb`), transport is another (`y-websocket`, and a server to run it), and encryption at rest, a backup the user can hold, a destination they own and the screens to connect one are simply not in scope. That is a healthy design. It also means a Yjs app still has the whole storage question in front of it. ## What selfstore is That question, answered, with no server: an IndexedDB working copy, portable encrypted ZIP backups on an [open spec](https://selfstore.dev/docs/format), durable homes on storage the user already has, and deterministic multi-device merge. Its own merge is last-write-wins, which is honestly the wrong answer for a paragraph two people are typing in. So do not ask it to be: keep Yjs for the document, and let selfstore carry it. ## They compose, and here is why that works selfstore merges binary files by a **union on their id**. Yjs updates are commutative and idempotent - apply them in any order, apply one twice, you land on the same document. Store each update under the id of its own bytes, and the union *is* the CRDT merge: no device's update is lost when the copies meet, and folding them is the whole read path. ```ts const doc = new Y.Doc(); const fold = () => { for (const f of store.allFiles()) if (f.mime === UPDATE_MIME) Y.applyUpdate(doc, f.bytes, FOLDED); }; fold(); // what this device holds store.onChange(fold); // and what arrives from another doc.on('update', (u, origin) => { if (origin !== FOLDED) void store.putFile({ bytes: u, mime: UPDATE_MIME }); }); ``` `putFile` defaults a file's id to the SHA-256 of its bytes, which is what makes this safe: a single stable id for the whole document would put two devices' different bodies under one key, and a union has no clock to order them - one would be dropped in silence. The library refuses that by default. | | Yjs | selfstore | | --- | --- | --- | | Solves | Concurrent edits, losslessly | Durability, portability, destinations | | Merge | Real CRDT | Last-write-wins per record | | Live collaboration | Yes, with a server | No | | Local persistence | A provider you add | Built in | | Encryption | Yours to build | Built in, AES-256-GCM + Argon2id | | User-holdable backup | Yours to build | First-class, spec'd ZIP | ## Use Yjs alone when - You have a collaboration server anyway, and storage is already solved. - The document is the whole app and it never leaves the session. ## Use both when - You want real concurrent-edit merging **and** the file the user walks away with, end-to-end encrypted, on their own Drive or disk, with nothing hosted. The complete integration - compaction pass and its honest limit included - is [`examples/yjs-document.ts`](https://github.com/selfstoredev/selfstore/blob/main/examples/yjs-document.ts). Map of this site for a model: https://selfstore.dev/llms.txt Every page in one file: https://selfstore.dev/llms-full.txt