@homeostate/core 0.1.1 → 0.2.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @homeostate/core
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a643bbe: Stop exporting `applyStringChanges`. It was only used inside core; `applyChanges` still applies string changes wherever they appear in the state.
8
+ - 0bbf653: Move `createMemoryBackend` and `MemoryBackend` to the `@homeostate/core/testing` entry, so the root entry only exports what production code uses. Mark the package side-effect free.
9
+ - fb76f77: Preserve complete Unicode code points when diffing and applying string edits. Deleting or
10
+ replacing supplementary characters such as emoji no longer corrupts text, deletes adjacent
11
+ characters, or throws in the supplied CRDT backends.
12
+
13
+ String `Change` offsets remain UTF-16 code units. A string deletion can now carry a numeric
14
+ UTF-16 deletion length in its third tuple entry; `undefined` still means one unit. Custom
15
+ backends consuming `getChanges` must honor this length in one operation rather than delete
16
+ one code unit per step. Array and object deletion semantics are unchanged.
17
+
18
+ ## 0.1.2
19
+
20
+ ### Patch Changes
21
+
22
+ - 30abf27: Remove draft release warnings, update docs links
23
+
3
24
  ## 0.1.1
4
25
 
5
26
  ### Patch Changes
package/README.md CHANGED
@@ -1,8 +1,5 @@
1
1
  # @homeostate/core
2
2
 
3
- > **Draft / placeholder release.** `0.0.0` reserves the name while the API is still
4
- > being designed. Nothing here is stable — do not depend on it yet.
5
-
6
3
  State-manager and CRDT-backend agnostic sync engine. It keeps a store, reached through a
7
4
  `StoreAdapter`, in sync with a `CrdtBackend` such as
8
5
  [`@homeostate/crdt-yjs`](https://github.com/mixedrays/homeostate/tree/main/packages/crdt-yjs),
@@ -30,9 +27,30 @@ const engine = createSyncEngine(createYjsBackend(doc, "shared"), adapter, {
30
27
  engine.connect();
31
28
  ```
32
29
 
33
- `createMemoryBackend()` is a plain-JSON backend without replication, meant for tests.
30
+ `createMemoryBackend()` from `@homeostate/core/testing` is a plain-JSON backend without
31
+ replication, meant for tests.
34
32
  `getChanges` is exported so a backend can turn a `write(next)` into fine-grained operations.
35
33
 
34
+ ## Applying string changes
35
+
36
+ String changes from `getChanges` use UTF-16 offsets into the progressively edited string,
37
+ matching JavaScript string indexing. The diff compares whole Unicode code points so an edit
38
+ does not split a valid surrogate pair. It does not treat a multi-code-point grapheme, such as
39
+ a joined emoji or a letter with combining marks, as one indivisible character.
40
+
41
+ For a string `DELETE`, the third tuple entry is the number of UTF-16 units to remove;
42
+ `undefined` means one unit. Custom backends must honor this length in one operation:
43
+
44
+ ```ts
45
+ getChanges("😀a😃b", "😀ab");
46
+ // [[ChangeType.DELETE, 3, 2]] — remove the whole 😃 surrogate pair
47
+ ```
48
+
49
+ Inserts carry complete strings. Array and object deletes still carry `undefined` and remove
50
+ one element or property. Core's `applyChanges` and the supplied CRDT backends handle string
51
+ deletion lengths; an external backend using a different text-index unit must translate both
52
+ offsets and lengths before applying them.
53
+
36
54
  ## Connecting
37
55
 
38
56
  `connect()` reconciles the store and the backend per key, over the view the `filter` allows:
package/dist/apply.d.ts CHANGED
@@ -17,6 +17,7 @@ export interface ApplyOps {
17
17
  /**
18
18
  * Revises `value` by the character-level edit script `changes`, which is what `getChanges`
19
19
  * returns for two strings. Each index addresses the string as revised by the steps before it.
20
+ * Offsets and deletion lengths are UTF-16 units; an omitted length means one unit.
20
21
  */
21
22
  export declare const applyStringChanges: (value: string, changes: Change[]) => string;
22
23
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAItD;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,gFAAgF;IAChF,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAChE,wCAAwC;IACxC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,6FAA6F;IAC7F,MAAM,CACJ,MAAM,EAAE,OAAO,EAAE,EACjB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,OAAO,EAAE,GAClB,IAAI,CAAC;CACT;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,SAAS,MAAM,EAAE,KAAG,MAQ3D,CAAC;AAEZ;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,GACvB,QAAQ,MAAM,EACd,SAAS,MAAM,EAAE,EACjB,KAAK,QAAQ,KACZ,IA6BF,CAAC"}
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAItD;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,gFAAgF;IAChF,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAChE,wCAAwC;IACxC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,6FAA6F;IAC7F,MAAM,CACJ,MAAM,EAAE,OAAO,EAAE,EACjB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,OAAO,EAAE,GAClB,IAAI,CAAC;CACT;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,SAAS,MAAM,EAAE,KAAG,MAW3D,CAAC;AAEZ;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,GACvB,QAAQ,MAAM,EACd,SAAS,MAAM,EAAE,EACjB,KAAK,QAAQ,KACZ,IA6BF,CAAC"}
package/dist/apply.js CHANGED
@@ -2,13 +2,15 @@ import { ChangeType } from "./change.js";
2
2
  /**
3
3
  * Revises `value` by the character-level edit script `changes`, which is what `getChanges`
4
4
  * returns for two strings. Each index addresses the string as revised by the steps before it.
5
+ * Offsets and deletion lengths are UTF-16 units; an omitted length means one unit.
5
6
  */
6
7
  export const applyStringChanges = (value, changes) => changes.reduce((revised, [type, index, inserted]) => {
7
8
  const at = index;
8
9
  if (type === ChangeType.INSERT)
9
10
  return revised.slice(0, at) + inserted + revised.slice(at);
10
11
  if (type === ChangeType.DELETE)
11
- return revised.slice(0, at) + revised.slice(at + 1);
12
+ return (revised.slice(0, at) +
13
+ revised.slice(at + (typeof inserted === "number" ? inserted : 1)));
12
14
  return revised;
13
15
  }, value);
14
16
  /**
package/dist/apply.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"apply.js","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAe,MAAM,aAAa,CAAC;AAyBtD;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAa,EAAE,OAAiB,EAAU,EAAE,CAC7E,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE;IAClD,MAAM,EAAE,GAAG,KAAe,CAAC;IAC3B,IAAI,IAAI,KAAK,UAAU,CAAC,MAAM;QAC5B,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAI,QAAmB,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACzE,IAAI,IAAI,KAAK,UAAU,CAAC,MAAM;QAC5B,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACtD,OAAO,OAAO,CAAC;AACjB,CAAC,EAAE,KAAK,CAAC,CAAC;AAEZ;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,MAAc,EACd,OAAiB,EACjB,GAAa,EACP,EAAE;IACR,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAoB,CAAC,CAAC,CAAC,IAAI,CAAC;IAEnE,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACzC,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;gBACxB,MAAM,KAAK,GAAI,MAAgB,CAAC,GAAa,CAAC,CAAC;gBAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ;oBAC3B,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,kBAAkB,CAAC,KAAK,EAAE,KAAiB,CAAC,CAAC,CAAC;qBAChE,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;oBAClD,YAAY,CAAC,KAAK,EAAE,KAAiB,EAAE,GAAG,CAAC,CAAC;gBAC9C,MAAM;YACR,CAAC;YAED,KAAK,UAAU,CAAC,MAAM;gBACpB,IAAI,KAAK;oBAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAa,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;;oBAC9C,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAa,CAAC,CAAC;gBACvC,MAAM;YAER,KAAK,UAAU,CAAC,MAAM;gBACpB,IAAI,KAAK;oBAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAa,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;;oBACnD,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;gBACjC,MAAM;YAER,KAAK,UAAU,CAAC,MAAM;gBACpB,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;gBAC5B,MAAM;QACV,CAAC;IACH,CAAC;AACH,CAAC,CAAC"}
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAe,MAAM,aAAa,CAAC;AAyBtD;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAa,EAAE,OAAiB,EAAU,EAAE,CAC7E,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE;IAClD,MAAM,EAAE,GAAG,KAAe,CAAC;IAC3B,IAAI,IAAI,KAAK,UAAU,CAAC,MAAM;QAC5B,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAI,QAAmB,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACzE,IAAI,IAAI,KAAK,UAAU,CAAC,MAAM;QAC5B,OAAO,CACL,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAClE,CAAC;IACJ,OAAO,OAAO,CAAC;AACjB,CAAC,EAAE,KAAK,CAAC,CAAC;AAEZ;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,MAAc,EACd,OAAiB,EACjB,GAAa,EACP,EAAE;IACR,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAoB,CAAC,CAAC,CAAC,IAAI,CAAC;IAEnE,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACzC,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;gBACxB,MAAM,KAAK,GAAI,MAAgB,CAAC,GAAa,CAAC,CAAC;gBAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ;oBAC3B,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,kBAAkB,CAAC,KAAK,EAAE,KAAiB,CAAC,CAAC,CAAC;qBAChE,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;oBAClD,YAAY,CAAC,KAAK,EAAE,KAAiB,EAAE,GAAG,CAAC,CAAC;gBAC9C,MAAM;YACR,CAAC;YAED,KAAK,UAAU,CAAC,MAAM;gBACpB,IAAI,KAAK;oBAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAa,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;;oBAC9C,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAa,CAAC,CAAC;gBACvC,MAAM;YAER,KAAK,UAAU,CAAC,MAAM;gBACpB,IAAI,KAAK;oBAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAa,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;;oBACnD,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;gBACjC,MAAM;YAER,KAAK,UAAU,CAAC,MAAM;gBACpB,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;gBAC5B,MAAM;QACV,CAAC;IACH,CAAC;AACH,CAAC,CAAC"}
package/dist/change.d.ts CHANGED
@@ -16,8 +16,14 @@ export declare enum ChangeType {
16
16
  *
17
17
  * Steps apply in order. A numeric key addresses the array or string as revised by the
18
18
  * steps before it, so appliers never sort or clamp. `value` is the inserted or replacing
19
- * value (a string insert may carry several characters), `undefined` for a delete, and the
20
- * nested `Change[]` for a pending entry.
19
+ * value (a string insert may carry several characters), and the nested `Change[]` for a
20
+ * pending entry. Object and array deletes carry `undefined`.
21
+ *
22
+ * String offsets and deletion lengths use UTF-16 code units. A string delete carries its
23
+ * length in `value`, or `undefined` for the default length of 1. Generated string edits
24
+ * preserve code point boundaries: deleting a supplementary character is one step with
25
+ * length 2, never two steps that split its surrogate pair. These are not grapheme edits;
26
+ * combining marks and joined emoji may span multiple code points.
21
27
  */
22
28
  export type Change = [ChangeType, string | number, unknown];
23
29
  //# sourceMappingURL=change.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"change.d.ts","sourceRoot":"","sources":["../src/change.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,UAAU;IACpB,4BAA4B;IAC5B,MAAM,WAAW;IACjB,4BAA4B;IAC5B,MAAM,WAAW;IACjB,2BAA2B;IAC3B,MAAM,WAAW;IACjB,uEAAuE;IACvE,OAAO,YAAY;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC"}
1
+ {"version":3,"file":"change.d.ts","sourceRoot":"","sources":["../src/change.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,UAAU;IACpB,4BAA4B;IAC5B,MAAM,WAAW;IACjB,4BAA4B;IAC5B,MAAM,WAAW;IACjB,2BAA2B;IAC3B,MAAM,WAAW;IACjB,uEAAuE;IACvE,OAAO,YAAY;CACpB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC"}
package/dist/diff.js CHANGED
@@ -33,34 +33,39 @@ const sharesCharacter = (a, b) => {
33
33
  return true;
34
34
  return false;
35
35
  };
36
+ const deleteCharacter = (index, character) => [
37
+ ChangeType.DELETE,
38
+ index,
39
+ character.length === 1 ? undefined : character.length,
40
+ ];
36
41
  const getStringChanges = (a, b) => {
37
42
  if (a === b)
38
43
  return [];
39
- if (!sharesCharacter(a, b)) {
40
- const deletes = Array.from(a, () => [
41
- ChangeType.DELETE,
42
- 0,
43
- undefined,
44
- ]);
44
+ // Compare whole code points, but keep Change offsets in JavaScript's UTF-16 units.
45
+ // Comparing code units can retain half a surrogate pair and corrupt a CRDT text.
46
+ const from = Array.from(a);
47
+ const to = Array.from(b);
48
+ if (!sharesCharacter(from, to)) {
49
+ const deletes = from.map((character) => deleteCharacter(0, character));
45
50
  return b.length === 0 ? deletes : [...deletes, [ChangeType.INSERT, 0, b]];
46
51
  }
47
52
  const changes = [];
48
53
  let index = 0;
49
- for (const { step, b: position } of editScript(a, b, (x, y) => x === y)) {
54
+ for (const { step, a: source, b: position } of editScript(from, to, (x, y) => x === y)) {
50
55
  if (step === "eq")
51
- index++;
56
+ index += from[source].length;
52
57
  else if (step === "del")
53
- changes.push([ChangeType.DELETE, index, undefined]);
58
+ changes.push(deleteCharacter(index, from[source]));
54
59
  else {
55
60
  const last = changes.length > 0 ? changes[changes.length - 1] : undefined;
56
61
  if (last !== undefined &&
57
62
  last[0] === ChangeType.INSERT &&
58
63
  last[1] + last[2].length === index) {
59
- last[2] = last[2] + b[position];
64
+ last[2] = last[2] + to[position];
60
65
  }
61
66
  else
62
- changes.push([ChangeType.INSERT, index, b[position]]);
63
- index++;
67
+ changes.push([ChangeType.INSERT, index, to[position]]);
68
+ index += to[position].length;
64
69
  }
65
70
  }
66
71
  return changes;
package/dist/diff.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  export { createSyncEngine } from "./sync-engine.js";
2
- export { createMemoryBackend } from "./memory-backend.js";
3
- export type { MemoryBackend } from "./memory-backend.js";
4
2
  export { getChanges } from "./diff.js";
5
- export { applyChanges, applyStringChanges } from "./apply.js";
3
+ export { applyChanges } from "./apply.js";
6
4
  export type { ApplyOps } from "./apply.js";
7
5
  export type { Diffable } from "./diff.js";
8
6
  export { ChangeType } from "./change.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC9D,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EACV,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,WAAW,GACZ,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EACV,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,WAAW,GACZ,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export { createSyncEngine } from "./sync-engine.js";
2
- export { createMemoryBackend } from "./memory-backend.js";
3
2
  export { getChanges } from "./diff.js";
4
- export { applyChanges, applyStringChanges } from "./apply.js";
3
+ export { applyChanges } from "./apply.js";
5
4
  export { ChangeType } from "./change.js";
6
5
  export { defaultSyncFilter } from "./types.js";
7
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAG9D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
@@ -15,7 +15,7 @@ import type { CrdtBackend, StoreAdapter, SyncEngine, SyncEngineConfig } from "./
15
15
  * @example
16
16
  * ```typescript
17
17
  * const backend = createYjsBackend(new Y.Doc(), 'shared');
18
- * const adapter = new ZustandAdapter(store);
18
+ * const adapter = createZustandAdapter(store);
19
19
  * const engine = createSyncEngine(backend, adapter);
20
20
  * engine.connect();
21
21
  * ```
@@ -16,7 +16,7 @@ import { patchState } from "./patching.js";
16
16
  * @example
17
17
  * ```typescript
18
18
  * const backend = createYjsBackend(new Y.Doc(), 'shared');
19
- * const adapter = new ZustandAdapter(store);
19
+ * const adapter = createZustandAdapter(store);
20
20
  * const engine = createSyncEngine(backend, adapter);
21
21
  * engine.connect();
22
22
  * ```
@@ -0,0 +1,3 @@
1
+ export { createMemoryBackend } from "./memory-backend.js";
2
+ export type { MemoryBackend } from "./memory-backend.js";
3
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { createMemoryBackend } from "./memory-backend.js";
2
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homeostate/core",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "State-manager and CRDT-backend agnostic sync engine between a store adapter and a CrdtBackend",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@
9
9
  "url": "git+https://github.com/mixedrays/homeostate.git",
10
10
  "directory": "packages/core"
11
11
  },
12
- "homepage": "https://github.com/mixedrays/homeostate#readme",
12
+ "homepage": "https://homeostate.pages.dev",
13
13
  "bugs": {
14
14
  "url": "https://github.com/mixedrays/homeostate/issues"
15
15
  },
@@ -25,10 +25,17 @@
25
25
  ".": {
26
26
  "types": "./dist/index.d.ts",
27
27
  "default": "./dist/index.js"
28
+ },
29
+ "./testing": {
30
+ "types": "./dist/testing.d.ts",
31
+ "default": "./dist/testing.js"
28
32
  }
29
33
  },
34
+ "sideEffects": false,
30
35
  "files": [
31
36
  "dist",
37
+ "src",
38
+ "!src/**/__tests__",
32
39
  "README.md",
33
40
  "LICENSE",
34
41
  "THIRD_PARTY_NOTICES.md",
package/src/apply.ts ADDED
@@ -0,0 +1,91 @@
1
+ import { ChangeType, type Change } from "./change.js";
2
+
3
+ type Plain = Record<string, unknown>;
4
+
5
+ /**
6
+ * How one store writes into its containers. `applyChanges` decides *what* to write; an
7
+ * implementation of this decides *how*, so the same edit script drives a MobX observable
8
+ * tree, a Valtio proxy, or a plain object.
9
+ *
10
+ * Every method receives a container that already lives in the store, never a copy.
11
+ */
12
+ export interface ApplyOps {
13
+ /** Assign `value` at `key`, creating the property if the target is a record. */
14
+ set(target: object, key: string | number, value: unknown): void;
15
+ /** Remove the record property `key`. */
16
+ remove(target: object, key: string): void;
17
+ /** `Array.prototype.splice`: drop `deleteCount` elements at `index`, then add `inserted`. */
18
+ splice(
19
+ target: unknown[],
20
+ index: number,
21
+ deleteCount: number,
22
+ inserted: unknown[],
23
+ ): void;
24
+ }
25
+
26
+ /**
27
+ * Revises `value` by the character-level edit script `changes`, which is what `getChanges`
28
+ * returns for two strings. Each index addresses the string as revised by the steps before it.
29
+ * Offsets and deletion lengths are UTF-16 units; an omitted length means one unit.
30
+ */
31
+ export const applyStringChanges = (value: string, changes: Change[]): string =>
32
+ changes.reduce((revised, [type, index, inserted]) => {
33
+ const at = index as number;
34
+ if (type === ChangeType.INSERT)
35
+ return revised.slice(0, at) + (inserted as string) + revised.slice(at);
36
+ if (type === ChangeType.DELETE)
37
+ return (
38
+ revised.slice(0, at) +
39
+ revised.slice(at + (typeof inserted === "number" ? inserted : 1))
40
+ );
41
+ return revised;
42
+ }, value);
43
+
44
+ /**
45
+ * Applies `changes` to `target` in place, mutating only the paths the edit script names.
46
+ * Everything else keeps its identity, which is what fine-grained stores react to.
47
+ *
48
+ * `changes` must be `getChanges(before, after)` where `before` describes the shape `target`
49
+ * currently holds — an adapter gets that by diffing against the snapshot it last handed out.
50
+ * A `PENDING` step carries no replacement value, so it can only be followed by recursing into
51
+ * the container already there; a target that does not mirror `before` leaves such a step
52
+ * unapplied rather than writing something wrong.
53
+ *
54
+ * @param target The container to mutate — a record, or an array for a positional edit script.
55
+ * @param changes The edit script to apply, in order.
56
+ * @param ops How this store writes; see {@link ApplyOps}.
57
+ */
58
+ export const applyChanges = (
59
+ target: object,
60
+ changes: Change[],
61
+ ops: ApplyOps,
62
+ ): void => {
63
+ const array = Array.isArray(target) ? (target as unknown[]) : null;
64
+
65
+ for (const [type, key, value] of changes) {
66
+ switch (type) {
67
+ case ChangeType.PENDING: {
68
+ const child = (target as Plain)[key as string];
69
+ if (typeof child === "string")
70
+ ops.set(target, key, applyStringChanges(child, value as Change[]));
71
+ else if (child !== null && typeof child === "object")
72
+ applyChanges(child, value as Change[], ops);
73
+ break;
74
+ }
75
+
76
+ case ChangeType.DELETE:
77
+ if (array) ops.splice(array, key as number, 1, []);
78
+ else ops.remove(target, key as string);
79
+ break;
80
+
81
+ case ChangeType.INSERT:
82
+ if (array) ops.splice(array, key as number, 0, [value]);
83
+ else ops.set(target, key, value);
84
+ break;
85
+
86
+ case ChangeType.UPDATE:
87
+ ops.set(target, key, value);
88
+ break;
89
+ }
90
+ }
91
+ };
package/src/change.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Describes the change that needs to be made.
3
+ */
4
+ export enum ChangeType {
5
+ /** A value was inserted. */
6
+ INSERT = "insert",
7
+ /** A value was replaced. */
8
+ UPDATE = "update",
9
+ /** A value was deleted. */
10
+ DELETE = "delete",
11
+ /** The value requires a recursive diff to identify further changes. */
12
+ PENDING = "pending",
13
+ }
14
+
15
+ /**
16
+ * One step in turning a container into another: `[type, key, value]`.
17
+ *
18
+ * Steps apply in order. A numeric key addresses the array or string as revised by the
19
+ * steps before it, so appliers never sort or clamp. `value` is the inserted or replacing
20
+ * value (a string insert may carry several characters), and the nested `Change[]` for a
21
+ * pending entry. Object and array deletes carry `undefined`.
22
+ *
23
+ * String offsets and deletion lengths use UTF-16 code units. A string delete carries its
24
+ * length in `value`, or `undefined` for the default length of 1. Generated string edits
25
+ * preserve code point boundaries: deleting a supplementary character is one step with
26
+ * length 2, never two steps that split its surrogate pair. These are not grapheme edits;
27
+ * combining marks and joined emoji may span multiple code points.
28
+ */
29
+ export type Change = [ChangeType, string | number, unknown];
package/src/diff.ts ADDED
@@ -0,0 +1,238 @@
1
+ import { ChangeType, type Change } from "./change.js";
2
+
3
+ export type Diffable = Record<string, unknown> | Array<unknown> | string;
4
+
5
+ const isArray = (value: unknown): value is Array<unknown> =>
6
+ Array.isArray(value);
7
+
8
+ const isString = (value: unknown): value is string => typeof value === "string";
9
+
10
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
11
+ value !== null && typeof value === "object" && !Array.isArray(value);
12
+
13
+ const isDiffable = (value: unknown): value is Diffable =>
14
+ isArray(value) || isString(value) || isRecord(value);
15
+
16
+ const isSameKind = (a: Diffable, b: Diffable): boolean =>
17
+ isString(a) ? isString(b) : isArray(a) ? isArray(b) : isRecord(b);
18
+
19
+ const nestedChanges = (a: unknown, b: unknown): Change[] | null =>
20
+ isDiffable(a) && isDiffable(b) && isSameKind(a, b) ? getChanges(a, b) : null;
21
+
22
+ const deepEqual = (a: unknown, b: unknown): boolean => {
23
+ if (a === b) return true;
24
+ if (isArray(a) && isArray(b))
25
+ return (
26
+ a.length === b.length && a.every((value, i) => deepEqual(value, b[i]))
27
+ );
28
+ if (isRecord(a) && isRecord(b)) {
29
+ const keys = Object.keys(a);
30
+ return (
31
+ keys.length === Object.keys(b).length &&
32
+ keys.every((key) => key in b && deepEqual(a[key], b[key]))
33
+ );
34
+ }
35
+ return false;
36
+ };
37
+
38
+ export const getChanges = (a: Diffable, b: Diffable): Change[] => {
39
+ if (isString(a) && isString(b)) return getStringChanges(a, b);
40
+ if (isArray(a) && isArray(b)) return getArrayChanges(a, b);
41
+ if (isRecord(a) && isRecord(b)) return getRecordChanges(a, b);
42
+ return [];
43
+ };
44
+
45
+ const sharesCharacter = (a: string[], b: string[]): boolean => {
46
+ const characters = new Set(a);
47
+ for (const character of b) if (characters.has(character)) return true;
48
+ return false;
49
+ };
50
+
51
+ const deleteCharacter = (index: number, character: string): Change => [
52
+ ChangeType.DELETE,
53
+ index,
54
+ character.length === 1 ? undefined : character.length,
55
+ ];
56
+
57
+ const getStringChanges = (a: string, b: string): Change[] => {
58
+ if (a === b) return [];
59
+ // Compare whole code points, but keep Change offsets in JavaScript's UTF-16 units.
60
+ // Comparing code units can retain half a surrogate pair and corrupt a CRDT text.
61
+ const from = Array.from(a);
62
+ const to = Array.from(b);
63
+ if (!sharesCharacter(from, to)) {
64
+ const deletes = from.map((character) => deleteCharacter(0, character));
65
+ return b.length === 0 ? deletes : [...deletes, [ChangeType.INSERT, 0, b]];
66
+ }
67
+
68
+ const changes: Change[] = [];
69
+ let index = 0;
70
+
71
+ for (const { step, a: source, b: position } of editScript(
72
+ from,
73
+ to,
74
+ (x, y) => x === y,
75
+ )) {
76
+ if (step === "eq") index += from[source].length;
77
+ else if (step === "del") changes.push(deleteCharacter(index, from[source]));
78
+ else {
79
+ const last = changes.length > 0 ? changes[changes.length - 1] : undefined;
80
+ if (
81
+ last !== undefined &&
82
+ last[0] === ChangeType.INSERT &&
83
+ (last[1] as number) + (last[2] as string).length === index
84
+ ) {
85
+ last[2] = (last[2] as string) + to[position];
86
+ } else changes.push([ChangeType.INSERT, index, to[position]]);
87
+ index += to[position].length;
88
+ }
89
+ }
90
+
91
+ return changes;
92
+ };
93
+
94
+ const getArrayChanges = (a: Array<unknown>, b: Array<unknown>): Change[] => {
95
+ const changes: Change[] = [];
96
+ let index = 0;
97
+ let deleted: number[] = [];
98
+ let inserted: number[] = [];
99
+
100
+ const flush = (): void => {
101
+ const pairs = Math.min(deleted.length, inserted.length);
102
+ for (let i = 0; i < pairs; i++) {
103
+ const next = b[inserted[i]];
104
+ const nested = nestedChanges(a[deleted[i]], next);
105
+ if (nested === null) changes.push([ChangeType.UPDATE, index, next]);
106
+ else if (nested.length > 0)
107
+ changes.push([ChangeType.PENDING, index, nested]);
108
+ index++;
109
+ }
110
+ for (let i = pairs; i < deleted.length; i++)
111
+ changes.push([ChangeType.DELETE, index, undefined]);
112
+ for (let i = pairs; i < inserted.length; i++) {
113
+ changes.push([ChangeType.INSERT, index, b[inserted[i]]]);
114
+ index++;
115
+ }
116
+ deleted = [];
117
+ inserted = [];
118
+ };
119
+
120
+ for (const { step, a: from, b: to } of editScript(a, b, deepEqual)) {
121
+ if (step === "del") deleted.push(from);
122
+ else if (step === "ins") inserted.push(to);
123
+ else {
124
+ flush();
125
+ index++;
126
+ }
127
+ }
128
+ flush();
129
+
130
+ return changes;
131
+ };
132
+
133
+ const getRecordChanges = (
134
+ a: Record<string, unknown>,
135
+ b: Record<string, unknown>,
136
+ ): Change[] => {
137
+ const changes: Change[] = [];
138
+
139
+ for (const property of Object.keys(a))
140
+ if (!(property in b))
141
+ changes.push([ChangeType.DELETE, property, undefined]);
142
+
143
+ for (const [property, value] of Object.entries(b)) {
144
+ if (!(property in a)) changes.push([ChangeType.INSERT, property, value]);
145
+ else {
146
+ const nested = nestedChanges(a[property], value);
147
+ if (nested === null) {
148
+ if (a[property] !== value)
149
+ changes.push([ChangeType.UPDATE, property, value]);
150
+ } else if (nested.length > 0)
151
+ changes.push([ChangeType.PENDING, property, nested]);
152
+ }
153
+ }
154
+
155
+ return changes;
156
+ };
157
+
158
+ interface EditOp {
159
+ step: "eq" | "ins" | "del";
160
+ a: number;
161
+ b: number;
162
+ }
163
+
164
+ /**
165
+ * Shortest edit script from `a` to `b` by Wu et al.'s O(NP) algorithm. Adapted from
166
+ * https://github.com/cubicdaiya/onp/blob/master/javascript/onp.js.
167
+ */
168
+ const editScript = <T>(
169
+ a: ArrayLike<T>,
170
+ b: ArrayLike<T>,
171
+ equal: (x: T, y: T) => boolean,
172
+ ): EditOp[] => {
173
+ const swapped = a.length > b.length;
174
+ const s = swapped ? b : a;
175
+ const t = swapped ? a : b;
176
+ const m = s.length;
177
+ const n = t.length;
178
+ const delta = n - m;
179
+ const offset = m + 1;
180
+ const furthest = new Array<number>(m + n + 3).fill(-1);
181
+ const pointAt = new Array<number>(m + n + 3).fill(-1);
182
+ const points: { x: number; y: number; prev: number }[] = [];
183
+
184
+ const snake = (k: number): void => {
185
+ const fromBelow = furthest[k + offset - 1] + 1;
186
+ const fromAbove = furthest[k + offset + 1];
187
+ let y = Math.max(fromBelow, fromAbove);
188
+ let x = y - k;
189
+ while (x < m && y < n && equal(s[x], t[y])) {
190
+ x++;
191
+ y++;
192
+ }
193
+ furthest[k + offset] = y;
194
+ pointAt[k + offset] = points.length;
195
+ points.push({
196
+ x,
197
+ y,
198
+ prev:
199
+ fromBelow > fromAbove
200
+ ? pointAt[k + offset - 1]
201
+ : pointAt[k + offset + 1],
202
+ });
203
+ };
204
+
205
+ for (let p = 0; furthest[delta + offset] !== n; p++) {
206
+ for (let k = -p; k < delta; k++) snake(k);
207
+ for (let k = delta + p; k > delta; k--) snake(k);
208
+ snake(delta);
209
+ }
210
+
211
+ const path: { x: number; y: number }[] = [];
212
+ for (let i = pointAt[delta + offset]; i !== -1; i = points[i].prev)
213
+ path.push(points[i]);
214
+
215
+ const ops: EditOp[] = [];
216
+ let x = 0;
217
+ let y = 0;
218
+ const op = (step: EditOp["step"]): EditOp =>
219
+ swapped ? { step, a: y, b: x } : { step, a: x, b: y };
220
+
221
+ for (let i = path.length - 1; i >= 0; i--) {
222
+ const end = path[i];
223
+ if (end.y - end.x > y - x) {
224
+ ops.push(op(swapped ? "del" : "ins"));
225
+ y++;
226
+ } else if (end.y - end.x < y - x) {
227
+ ops.push(op(swapped ? "ins" : "del"));
228
+ x++;
229
+ }
230
+ while (x < end.x) {
231
+ ops.push(op("eq"));
232
+ x++;
233
+ y++;
234
+ }
235
+ }
236
+
237
+ return ops;
238
+ };
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ export { createSyncEngine } from "./sync-engine.js";
2
+ export { getChanges } from "./diff.js";
3
+ export { applyChanges } from "./apply.js";
4
+ export type { ApplyOps } from "./apply.js";
5
+ export type { Diffable } from "./diff.js";
6
+ export { ChangeType } from "./change.js";
7
+ export type { Change } from "./change.js";
8
+ export { defaultSyncFilter } from "./types.js";
9
+ export type {
10
+ CrdtBackend,
11
+ SeedStrategy,
12
+ StoreAdapter,
13
+ SyncEngine,
14
+ SyncEngineConfig,
15
+ Unsubscribe,
16
+ } from "./types.js";
@@ -0,0 +1,38 @@
1
+ import type { CrdtBackend, Unsubscribe } from "./types.js";
2
+
3
+ export interface MemoryBackend extends CrdtBackend {
4
+ /** Replace the held state as if a remote peer had written it, then notify subscribers */
5
+ receive: (next: unknown) => void;
6
+ }
7
+
8
+ const clone = <T>(value: T): T =>
9
+ value === undefined ? value : JSON.parse(JSON.stringify(value));
10
+
11
+ /**
12
+ * Plain-JSON backend with no replication. It proves the engine has no
13
+ * CRDT-library assumptions and gives tests a fast in-process peer.
14
+ */
15
+ export const createMemoryBackend = (initial: unknown = {}): MemoryBackend => {
16
+ let state = clone(initial);
17
+ const listeners = new Set<() => void>();
18
+
19
+ return {
20
+ read: () => clone(state),
21
+
22
+ write: (next) => {
23
+ state = clone(next);
24
+ },
25
+
26
+ subscribe: (onRemoteChange): Unsubscribe => {
27
+ listeners.add(onRemoteChange);
28
+ return () => {
29
+ listeners.delete(onRemoteChange);
30
+ };
31
+ },
32
+
33
+ receive: (next) => {
34
+ state = clone(next);
35
+ listeners.forEach((listener) => listener());
36
+ },
37
+ };
38
+ };
@@ -0,0 +1,86 @@
1
+ import { applyStringChanges } from "./apply.js";
2
+ import { ChangeType, type Change } from "./change.js";
3
+ import { getChanges, type Diffable } from "./diff.js";
4
+
5
+ const applyChanges = (state: Diffable, changes: Change[]): Diffable => {
6
+ if (typeof state === "string") return applyStringChanges(state, changes);
7
+ if (Array.isArray(state)) return applyChangesToArray(state, changes);
8
+ return applyChangesToObject(state, changes);
9
+ };
10
+
11
+ const applyChangesToArray = (
12
+ array: unknown[],
13
+ changes: Change[],
14
+ ): unknown[] => {
15
+ const revised = [...array];
16
+
17
+ for (const [type, index, value] of changes) {
18
+ const i = index as number;
19
+
20
+ switch (type) {
21
+ case ChangeType.INSERT:
22
+ revised.splice(i, 0, value);
23
+ break;
24
+
25
+ case ChangeType.UPDATE:
26
+ revised[i] = value;
27
+ break;
28
+
29
+ case ChangeType.PENDING:
30
+ revised[i] = applyChanges(revised[i] as Diffable, value as Change[]);
31
+ break;
32
+
33
+ case ChangeType.DELETE:
34
+ revised.splice(i, 1);
35
+ break;
36
+ }
37
+ }
38
+
39
+ return revised;
40
+ };
41
+
42
+ const applyChangesToObject = (
43
+ object: Record<string, unknown>,
44
+ changes: Change[],
45
+ ): Record<string, unknown> => {
46
+ const revised = { ...object };
47
+
48
+ for (const [type, property, value] of changes) {
49
+ const key = property as string;
50
+
51
+ switch (type) {
52
+ case ChangeType.INSERT:
53
+ case ChangeType.UPDATE:
54
+ revised[key] = value;
55
+ break;
56
+
57
+ case ChangeType.PENDING:
58
+ revised[key] = applyChanges(object[key] as Diffable, value as Change[]);
59
+ break;
60
+
61
+ case ChangeType.DELETE:
62
+ delete revised[key];
63
+ break;
64
+ }
65
+ }
66
+
67
+ return revised;
68
+ };
69
+
70
+ /**
71
+ * Returns a value identical to newState, built from oldState by copying every
72
+ * container along a changed path and sharing every unchanged subtree. Neither
73
+ * input is mutated. If oldState and newState are already identical (indicated
74
+ * by an empty diff), then oldState itself is returned.
75
+ *
76
+ * @param oldState The state we want to patch.
77
+ * @param newState The state we want the result to match.
78
+ *
79
+ * @returns The patched state, identical to newState.
80
+ */
81
+ export const patchState = <T>(oldState: T, newState: T): T => {
82
+ const changes = getChanges(oldState as Diffable, newState as Diffable);
83
+
84
+ if (changes.length === 0) return oldState;
85
+ return applyChanges(oldState as Diffable, changes) as T;
86
+ };
@@ -0,0 +1,135 @@
1
+ import type {
2
+ CrdtBackend,
3
+ StoreAdapter,
4
+ SyncEngine,
5
+ SyncEngineConfig,
6
+ Unsubscribe,
7
+ } from "./types.js";
8
+ import { defaultSyncFilter } from "./types.js";
9
+ import { patchState } from "./patching.js";
10
+
11
+ type Plain = Record<string, unknown>;
12
+
13
+ /**
14
+ * Creates a sync engine that keeps a store (via its adapter) and a CRDT backend
15
+ * in sync in both directions.
16
+ *
17
+ * This is the core abstraction that makes the sync logic state-manager and
18
+ * CRDT-library agnostic.
19
+ *
20
+ * On `connect()` the two sides are reconciled per key over the filtered view: a key the
21
+ * backend holds wins over the store's value, and a synced key the backend lacks stays in
22
+ * the store and is seeded into the backend unless `seed` is `'never'`. Afterwards the
23
+ * backend owns the synced document: every write replaces it with the store's filtered
24
+ * state, and a key removed from the backend is removed from the store.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const backend = createYjsBackend(new Y.Doc(), 'shared');
29
+ * const adapter = createZustandAdapter(store);
30
+ * const engine = createSyncEngine(backend, adapter);
31
+ * engine.connect();
32
+ * ```
33
+ */
34
+ export function createSyncEngine<S extends object>(
35
+ backend: CrdtBackend,
36
+ adapter: StoreAdapter<S>,
37
+ config: SyncEngineConfig = {},
38
+ ): SyncEngine {
39
+ const { filter = defaultSyncFilter, seed = "if-empty" } = config;
40
+
41
+ let connected = false;
42
+ let storeUnsubscribe: Unsubscribe | null = null;
43
+ let backendUnsubscribe: Unsubscribe | null = null;
44
+ let applyingRemote = false;
45
+
46
+ const filterState = (state: object): Plain => {
47
+ const filtered: Plain = {};
48
+ for (const [key, value] of Object.entries(state)) {
49
+ if (filter(key, value)) filtered[key] = value;
50
+ }
51
+ return filtered;
52
+ };
53
+
54
+ const readBackend = (): Plain => {
55
+ const value = backend.read();
56
+ return value !== null && typeof value === "object" ? (value as Plain) : {};
57
+ };
58
+
59
+ /**
60
+ * Builds the next store state from `remote`. With `keepLocalOnly`, a synced key the
61
+ * backend does not hold is left in place instead of being deleted; that is the
62
+ * connect-time reconciliation. Otherwise `remote` is the whole synced state.
63
+ */
64
+ const mergeStates = (
65
+ current: S,
66
+ remote: Plain,
67
+ keepLocalOnly: boolean,
68
+ ): S => {
69
+ const synced = filterState(current);
70
+ const target = keepLocalOnly ? { ...synced, ...remote } : remote;
71
+ const patched = patchState(synced, target);
72
+ if (patched === synced) return current;
73
+
74
+ const merged: Plain = { ...current, ...patched };
75
+ for (const key of Object.keys(synced)) {
76
+ if (!(key in patched)) delete merged[key];
77
+ }
78
+ return merged as S;
79
+ };
80
+
81
+ const syncToBackend = (): void => {
82
+ if (applyingRemote) return;
83
+ backend.write(filterState(adapter.getState()));
84
+ };
85
+
86
+ const applyRemote = (remote: Plain, keepLocalOnly: boolean): void => {
87
+ applyingRemote = true;
88
+ try {
89
+ const current = adapter.getState();
90
+ const merged = mergeStates(current, remote, keepLocalOnly);
91
+ if (merged !== current) adapter.setState(merged);
92
+ } finally {
93
+ applyingRemote = false;
94
+ }
95
+ };
96
+
97
+ const syncToStore = (): void => {
98
+ applyRemote(filterState(readBackend()), false);
99
+ };
100
+
101
+ return {
102
+ connect: (): void => {
103
+ if (connected) return;
104
+
105
+ const remote = filterState(readBackend());
106
+ const local = filterState(adapter.getState());
107
+ const remoteKeys = new Set(Object.keys(remote));
108
+ const missing = Object.keys(local).filter((key) => !remoteKeys.has(key));
109
+
110
+ if (seed === "if-empty" && missing.length > 0) {
111
+ const seeded: Plain = { ...remote };
112
+ for (const key of missing) seeded[key] = local[key];
113
+ backend.write(seeded);
114
+ }
115
+
116
+ applyRemote(remote, true);
117
+
118
+ backendUnsubscribe = backend.subscribe(syncToStore);
119
+ storeUnsubscribe = adapter.subscribe(syncToBackend);
120
+ connected = true;
121
+ },
122
+
123
+ disconnect: (): void => {
124
+ if (!connected) return;
125
+
126
+ backendUnsubscribe?.();
127
+ backendUnsubscribe = null;
128
+ storeUnsubscribe?.();
129
+ storeUnsubscribe = null;
130
+ connected = false;
131
+ },
132
+
133
+ isConnected: (): boolean => connected,
134
+ };
135
+ }
package/src/testing.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { createMemoryBackend } from "./memory-backend.js";
2
+ export type { MemoryBackend } from "./memory-backend.js";
package/src/types.ts ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Core types for the state-manager and CRDT-backend agnostic sync engine.
3
+ * These interfaces let any store be kept in sync through any replicated backend.
4
+ */
5
+
6
+ /**
7
+ * Unsubscribe function returned by subscribe
8
+ */
9
+ export type Unsubscribe = () => void;
10
+
11
+ /**
12
+ * Adapter interface that bridges a specific state manager with the sync engine.
13
+ * Implement this interface to add support for any state manager (Zustand, Redux, MobX, etc.)
14
+ *
15
+ * State must be plain JSON: objects, arrays, strings, numbers, booleans, and null.
16
+ * Functions are dropped by the default filter; other values such as Date, Map, or Set
17
+ * are neither diffed nor synced.
18
+ */
19
+ export interface StoreAdapter<S extends object> {
20
+ /** Get the current state from the store */
21
+ getState: () => S;
22
+
23
+ /**
24
+ * Replace the store state. Called only for changes coming from the backend; the
25
+ * engine ignores store notifications raised while it runs, so the adapter needs no
26
+ * echo suppression of its own.
27
+ */
28
+ setState: (state: S) => void;
29
+
30
+ /**
31
+ * Subscribe to store changes that should be written to the backend.
32
+ * @returns Unsubscribe function
33
+ */
34
+ subscribe: (onStoreChange: () => void) => Unsubscribe;
35
+ }
36
+
37
+ /**
38
+ * Backend that holds the synced subtree in a CRDT or any other replicated store.
39
+ * The engine only ever exchanges plain JSON with it.
40
+ */
41
+ export interface CrdtBackend {
42
+ /** Plain JSON snapshot of the synced subtree. Must not alias backend internals. */
43
+ read: () => unknown;
44
+
45
+ /**
46
+ * Make the backend equal to `next` in one atomic transaction.
47
+ * The backend decides the granularity of the operations; `getChanges` is exported for that.
48
+ */
49
+ write: (next: unknown) => void;
50
+
51
+ /**
52
+ * Notify about changes that did not come through this backend's own `write`.
53
+ * @returns Unsubscribe function
54
+ */
55
+ subscribe: (onRemoteChange: () => void) => Unsubscribe;
56
+ }
57
+
58
+ /**
59
+ * Whether `connect()` may write the store's synced keys that the backend does not hold.
60
+ * Reconciliation is per key over the filtered view: a key the backend holds always wins
61
+ * over the store's value, a key only the store holds always stays in the store.
62
+ * - `'if-empty'`: seed the keys the backend lacks, in one write (default)
63
+ * - `'never'`: leave seeding to the caller; the keys are written on the next local change
64
+ */
65
+ export type SeedStrategy = "if-empty" | "never";
66
+
67
+ /**
68
+ * Configuration options for the sync engine
69
+ */
70
+ export interface SyncEngineConfig {
71
+ /**
72
+ * Filter function to determine which state keys should be synced, in both directions.
73
+ * Return true to sync the key, false to exclude it.
74
+ * By default, functions are excluded from sync.
75
+ */
76
+ filter?: (key: string, value: unknown) => boolean;
77
+ /** Seeding strategy used by `connect()`; defaults to `'if-empty'` */
78
+ seed?: SeedStrategy;
79
+ }
80
+
81
+ /**
82
+ * The sync engine interface - manages bidirectional sync between a store and a backend
83
+ */
84
+ export interface SyncEngine {
85
+ /** Start synchronization */
86
+ connect: () => void;
87
+ /** Stop synchronization and cleanup */
88
+ disconnect: () => void;
89
+ /** Check if engine is connected */
90
+ isConnected: () => boolean;
91
+ }
92
+
93
+ /**
94
+ * Default filter that excludes functions from sync
95
+ */
96
+ export const defaultSyncFilter = (_key: string, value: unknown): boolean => {
97
+ return typeof value !== "function";
98
+ };