@absolutejs/sync-yjs 0.0.1 → 0.0.2
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.
- package/dist/index.js +11 -2
- package/dist/index.js.map +3 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -64,10 +64,19 @@ var reconcile = (text, next) => {
|
|
|
64
64
|
var createYjsText = (replica, initial) => {
|
|
65
65
|
const doc = docFrom(replica, initial);
|
|
66
66
|
const text = doc.getText(TEXT_KEY);
|
|
67
|
+
let lastVector = Y.encodeStateVector(doc);
|
|
67
68
|
return {
|
|
68
|
-
merge: (state) =>
|
|
69
|
+
merge: (state) => {
|
|
70
|
+
apply(doc, state);
|
|
71
|
+
lastVector = Y.encodeStateVector(doc);
|
|
72
|
+
},
|
|
69
73
|
setText: (next) => doc.transact(() => reconcile(text, next)),
|
|
70
74
|
state: () => encode(doc),
|
|
75
|
+
takeDelta: () => {
|
|
76
|
+
const update = Y.encodeStateAsUpdate(doc, lastVector);
|
|
77
|
+
lastVector = Y.encodeStateVector(doc);
|
|
78
|
+
return toBase64(update);
|
|
79
|
+
},
|
|
71
80
|
text: () => text.toString()
|
|
72
81
|
};
|
|
73
82
|
};
|
|
@@ -91,5 +100,5 @@ export {
|
|
|
91
100
|
createYjsText
|
|
92
101
|
};
|
|
93
102
|
|
|
94
|
-
//# debugId=
|
|
103
|
+
//# debugId=698DC1F045A2771E64756E2164756E21
|
|
95
104
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * A Yjs-backed collaborative-text CRDT for `@absolutejs/sync`.\n *\n * `@absolutejs/sync` ships a zero-dependency RGA text CRDT (`rgaText`) that is\n * fine for offline-merge and moderate collaboration. Yjs is the community staple\n * for production-scale collaborative text — it solves the real footguns (tombstone\n * growth, efficient deltas, interleaving). This adapter wraps Yjs behind the exact\n * same `CrdtText` / `TextCrdtAdapter` contract, so swapping `rgaText` for `yjsText`\n * needs no other change at the call site.\n *\n * The serialized state is a **base64 string** of a Yjs update, so it stays\n * JSON-safe for transport over the sync engine's change feed and storage as a row\n * field. Merges are commutative/associative/idempotent (Yjs guarantees it), so a\n * merge-on-write mutation combines concurrent edits without clobbering.\n */\nimport type { CrdtText, TextCrdtAdapter } from '@absolutejs/sync/crdt';\nimport * as Y from 'yjs';\n\nconst TEXT_KEY = 'text';\n\n// Isomorphic base64 (browser + Bun both expose btoa/atob), so the adapter runs\n// unchanged on the client (live editing) and the server (merge-on-write).\nconst toBase64 = (bytes: Uint8Array): string => {\n\tlet binary = '';\n\tfor (const byte of bytes) {\n\t\tbinary += String.fromCharCode(byte);\n\t}\n\n\treturn btoa(binary);\n};\n\nconst fromBase64 = (base64: string): Uint8Array => {\n\tconst binary = atob(base64);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let index = 0; index < binary.length; index += 1) {\n\t\tbytes[index] = binary.charCodeAt(index);\n\t}\n\n\treturn bytes;\n};\n\nconst encode = (doc: Y.Doc): string => toBase64(Y.encodeStateAsUpdate(doc));\n\nconst apply = (doc: Y.Doc, state: string) => {\n\tif (state.length > 0) {\n\t\tY.applyUpdate(doc, fromBase64(state));\n\t}\n};\n\n// A stable, deterministic Yjs clientID per replica id (Yjs needs distinct\n// clients to keep distinct identities; deriving it from `replica` also makes\n// merges reproducible instead of depending on a random per-instance id).\nconst clientIdFor = (replica: string): number => {\n\tlet hash = 0;\n\tfor (let index = 0; index < replica.length; index += 1) {\n\t\thash = (Math.imul(hash, 31) + replica.charCodeAt(index)) | 0;\n\t}\n\n\treturn hash >>> 0;\n};\n\nconst docFrom = (replica: string, initial?: string): Y.Doc => {\n\tconst doc = new Y.Doc();\n\tdoc.clientID = clientIdFor(replica);\n\tif (initial !== undefined) {\n\t\tapply(doc, initial);\n\t}\n\n\treturn doc;\n};\n\n/** Reconcile `text` to `next` by editing only the changed middle (keep the\n * common prefix/suffix), so two replicas editing different spots merge. */\nconst reconcile = (text: Y.Text, next: string) => {\n\tconst current = text.toString();\n\tif (current === next) {\n\t\treturn;\n\t}\n\tlet prefix = 0;\n\tconst maxPrefix = Math.min(current.length, next.length);\n\twhile (prefix < maxPrefix && current[prefix] === next[prefix]) {\n\t\tprefix += 1;\n\t}\n\tlet suffix = 0;\n\twhile (\n\t\tsuffix < maxPrefix - prefix &&\n\t\tcurrent[current.length - 1 - suffix] === next[next.length - 1 - suffix]\n\t) {\n\t\tsuffix += 1;\n\t}\n\tconst removed = current.length - prefix - suffix;\n\tconst inserted = next.slice(prefix, next.length - suffix);\n\tif (removed > 0) {\n\t\ttext.delete(prefix, removed);\n\t}\n\tif (inserted.length > 0) {\n\t\ttext.insert(prefix, inserted);\n\t}\n};\n\n/** Create a live Yjs-backed collaborative-text doc for `replica`. */\nexport const createYjsText = (\n\treplica: string,\n\tinitial?: string\n): CrdtText<string> => {\n\tconst doc = docFrom(replica, initial);\n\tconst text = doc.getText(TEXT_KEY);\n\n\treturn {\n\t\tmerge: (state) =>
|
|
5
|
+
"/**\n * A Yjs-backed collaborative-text CRDT for `@absolutejs/sync`.\n *\n * `@absolutejs/sync` ships a zero-dependency RGA text CRDT (`rgaText`) that is\n * fine for offline-merge and moderate collaboration. Yjs is the community staple\n * for production-scale collaborative text — it solves the real footguns (tombstone\n * growth, efficient deltas, interleaving). This adapter wraps Yjs behind the exact\n * same `CrdtText` / `TextCrdtAdapter` contract, so swapping `rgaText` for `yjsText`\n * needs no other change at the call site.\n *\n * The serialized state is a **base64 string** of a Yjs update, so it stays\n * JSON-safe for transport over the sync engine's change feed and storage as a row\n * field. Merges are commutative/associative/idempotent (Yjs guarantees it), so a\n * merge-on-write mutation combines concurrent edits without clobbering.\n */\nimport type { CrdtText, TextCrdtAdapter } from '@absolutejs/sync/crdt';\nimport * as Y from 'yjs';\n\nconst TEXT_KEY = 'text';\n\n// Isomorphic base64 (browser + Bun both expose btoa/atob), so the adapter runs\n// unchanged on the client (live editing) and the server (merge-on-write).\nconst toBase64 = (bytes: Uint8Array): string => {\n\tlet binary = '';\n\tfor (const byte of bytes) {\n\t\tbinary += String.fromCharCode(byte);\n\t}\n\n\treturn btoa(binary);\n};\n\nconst fromBase64 = (base64: string): Uint8Array => {\n\tconst binary = atob(base64);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let index = 0; index < binary.length; index += 1) {\n\t\tbytes[index] = binary.charCodeAt(index);\n\t}\n\n\treturn bytes;\n};\n\nconst encode = (doc: Y.Doc): string => toBase64(Y.encodeStateAsUpdate(doc));\n\nconst apply = (doc: Y.Doc, state: string) => {\n\tif (state.length > 0) {\n\t\tY.applyUpdate(doc, fromBase64(state));\n\t}\n};\n\n// A stable, deterministic Yjs clientID per replica id (Yjs needs distinct\n// clients to keep distinct identities; deriving it from `replica` also makes\n// merges reproducible instead of depending on a random per-instance id).\nconst clientIdFor = (replica: string): number => {\n\tlet hash = 0;\n\tfor (let index = 0; index < replica.length; index += 1) {\n\t\thash = (Math.imul(hash, 31) + replica.charCodeAt(index)) | 0;\n\t}\n\n\treturn hash >>> 0;\n};\n\nconst docFrom = (replica: string, initial?: string): Y.Doc => {\n\tconst doc = new Y.Doc();\n\tdoc.clientID = clientIdFor(replica);\n\tif (initial !== undefined) {\n\t\tapply(doc, initial);\n\t}\n\n\treturn doc;\n};\n\n/** Reconcile `text` to `next` by editing only the changed middle (keep the\n * common prefix/suffix), so two replicas editing different spots merge. */\nconst reconcile = (text: Y.Text, next: string) => {\n\tconst current = text.toString();\n\tif (current === next) {\n\t\treturn;\n\t}\n\tlet prefix = 0;\n\tconst maxPrefix = Math.min(current.length, next.length);\n\twhile (prefix < maxPrefix && current[prefix] === next[prefix]) {\n\t\tprefix += 1;\n\t}\n\tlet suffix = 0;\n\twhile (\n\t\tsuffix < maxPrefix - prefix &&\n\t\tcurrent[current.length - 1 - suffix] === next[next.length - 1 - suffix]\n\t) {\n\t\tsuffix += 1;\n\t}\n\tconst removed = current.length - prefix - suffix;\n\tconst inserted = next.slice(prefix, next.length - suffix);\n\tif (removed > 0) {\n\t\ttext.delete(prefix, removed);\n\t}\n\tif (inserted.length > 0) {\n\t\ttext.insert(prefix, inserted);\n\t}\n};\n\n/** Create a live Yjs-backed collaborative-text doc for `replica`. */\nexport const createYjsText = (\n\treplica: string,\n\tinitial?: string\n): CrdtText<string> => {\n\tconst doc = docFrom(replica, initial);\n\tconst text = doc.getText(TEXT_KEY);\n\t// State vector at the last sync point (a take or a merge). takeDelta encodes\n\t// every update since this vector and then advances it; merge advances it too,\n\t// so a remote op merged in isn't re-broadcast on the next take.\n\tlet lastVector = Y.encodeStateVector(doc);\n\n\treturn {\n\t\tmerge: (state) => {\n\t\t\tapply(doc, state);\n\t\t\tlastVector = Y.encodeStateVector(doc);\n\t\t},\n\t\tsetText: (next) => doc.transact(() => reconcile(text, next)),\n\t\tstate: () => encode(doc),\n\t\ttakeDelta: () => {\n\t\t\tconst update = Y.encodeStateAsUpdate(doc, lastVector);\n\t\t\tlastVector = Y.encodeStateVector(doc);\n\n\t\t\treturn toBase64(update);\n\t\t},\n\t\ttext: () => text.toString()\n\t};\n};\n\n/**\n * The Yjs collaborative-text backend as a {@link TextCrdtAdapter}. Drop-in for\n * the first-party `rgaText`: `create` mints a live doc, `merge` combines two\n * persisted states server-side, `empty`/`textOf` are conveniences.\n */\nexport const yjsText: TextCrdtAdapter<string> = {\n\tcreate: createYjsText,\n\tempty: () => encode(new Y.Doc()),\n\tmerge: (a, b) => {\n\t\tconst doc = new Y.Doc();\n\t\tapply(doc, a);\n\t\tapply(doc, b);\n\n\t\treturn encode(doc);\n\t},\n\ttextOf: (state) => {\n\t\tconst doc = new Y.Doc();\n\t\tapply(doc, state);\n\n\t\treturn doc.getText(TEXT_KEY).toString();\n\t}\n};\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;AAgBA;AAEA,IAAM,WAAW;AAIjB,IAAM,WAAW,CAAC,UAA8B;AAAA,EAC/C,IAAI,SAAS;AAAA,EACb,WAAW,QAAQ,OAAO;AAAA,IACzB,UAAU,OAAO,aAAa,IAAI;AAAA,EACnC;AAAA,EAEA,OAAO,KAAK,MAAM;AAAA;AAGnB,IAAM,aAAa,CAAC,WAA+B;AAAA,EAClD,MAAM,SAAS,KAAK,MAAM;AAAA,EAC1B,MAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAAA,EAC1C,SAAS,QAAQ,EAAG,QAAQ,OAAO,QAAQ,SAAS,GAAG;AAAA,IACtD,MAAM,SAAS,OAAO,WAAW,KAAK;AAAA,EACvC;AAAA,EAEA,OAAO;AAAA;AAGR,IAAM,SAAS,CAAC,QAAuB,SAAW,sBAAoB,GAAG,CAAC;AAE1E,IAAM,QAAQ,CAAC,KAAY,UAAkB;AAAA,EAC5C,IAAI,MAAM,SAAS,GAAG;AAAA,IACnB,cAAY,KAAK,WAAW,KAAK,CAAC;AAAA,EACrC;AAAA;AAMD,IAAM,cAAc,CAAC,YAA4B;AAAA,EAChD,IAAI,OAAO;AAAA,EACX,SAAS,QAAQ,EAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;AAAA,IACvD,OAAQ,KAAK,KAAK,MAAM,EAAE,IAAI,QAAQ,WAAW,KAAK,IAAK;AAAA,EAC5D;AAAA,EAEA,OAAO,SAAS;AAAA;AAGjB,IAAM,UAAU,CAAC,SAAiB,YAA4B;AAAA,EAC7D,MAAM,MAAM,IAAM;AAAA,EAClB,IAAI,WAAW,YAAY,OAAO;AAAA,EAClC,IAAI,YAAY,WAAW;AAAA,IAC1B,MAAM,KAAK,OAAO;AAAA,EACnB;AAAA,EAEA,OAAO;AAAA;AAKR,IAAM,YAAY,CAAC,MAAc,SAAiB;AAAA,EACjD,MAAM,UAAU,KAAK,SAAS;AAAA,EAC9B,IAAI,YAAY,MAAM;AAAA,IACrB;AAAA,EACD;AAAA,EACA,IAAI,SAAS;AAAA,EACb,MAAM,YAAY,KAAK,IAAI,QAAQ,QAAQ,KAAK,MAAM;AAAA,EACtD,OAAO,SAAS,aAAa,QAAQ,YAAY,KAAK,SAAS;AAAA,IAC9D,UAAU;AAAA,EACX;AAAA,EACA,IAAI,SAAS;AAAA,EACb,OACC,SAAS,YAAY,UACrB,QAAQ,QAAQ,SAAS,IAAI,YAAY,KAAK,KAAK,SAAS,IAAI,SAC/D;AAAA,IACD,UAAU;AAAA,EACX;AAAA,EACA,MAAM,UAAU,QAAQ,SAAS,SAAS;AAAA,EAC1C,MAAM,WAAW,KAAK,MAAM,QAAQ,KAAK,SAAS,MAAM;AAAA,EACxD,IAAI,UAAU,GAAG;AAAA,IAChB,KAAK,OAAO,QAAQ,OAAO;AAAA,EAC5B;AAAA,EACA,IAAI,SAAS,SAAS,GAAG;AAAA,IACxB,KAAK,OAAO,QAAQ,QAAQ;AAAA,EAC7B;AAAA;AAIM,IAAM,gBAAgB,CAC5B,SACA,YACsB;AAAA,EACtB,MAAM,MAAM,QAAQ,SAAS,OAAO;AAAA,EACpC,MAAM,OAAO,IAAI,QAAQ,QAAQ;AAAA,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "698DC1F045A2771E64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@absolutejs/sync-yjs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Yjs-backed collaborative-text CRDT adapter for @absolutejs/sync",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@absolutejs/sync": ">= 0.10.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@absolutejs/sync": "^0.
|
|
41
|
+
"@absolutejs/sync": "^1.0.0",
|
|
42
42
|
"@types/bun": "1.3.14",
|
|
43
43
|
"prettier": "3.5.3",
|
|
44
44
|
"typescript": "5.8.3"
|