@lunora/replica 1.0.0-alpha.7 → 1.0.0-alpha.9

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.d.mts CHANGED
@@ -30,8 +30,9 @@ declare const applyDiff: (current: ReadonlyMap<string, Record<string, unknown>>,
30
30
  /**
31
31
  * Apply an array of diffs **in order**, returning the final row map.
32
32
  *
33
- * This is equivalent to calling {@link applyDiff} repeatedly but avoids
34
- * intermediate map copies.
33
+ * This is equivalent to calling {@link applyDiff} repeatedly but copies the
34
+ * input map exactly once rather than once per diff — catch-up replay of an
35
+ * N-diff backlog is a single copy, not N+1.
35
36
  * @experimental
36
37
  */
37
38
  declare const applyDiffs: (current: ReadonlyMap<string, Record<string, unknown>>, diffs: ReadonlyArray<TableDiff>) => Map<string, Record<string, unknown>>;
package/dist/index.d.ts CHANGED
@@ -30,8 +30,9 @@ declare const applyDiff: (current: ReadonlyMap<string, Record<string, unknown>>,
30
30
  /**
31
31
  * Apply an array of diffs **in order**, returning the final row map.
32
32
  *
33
- * This is equivalent to calling {@link applyDiff} repeatedly but avoids
34
- * intermediate map copies.
33
+ * This is equivalent to calling {@link applyDiff} repeatedly but copies the
34
+ * input map exactly once rather than once per diff — catch-up replay of an
35
+ * N-diff backlog is a single copy, not N+1.
35
36
  * @experimental
36
37
  */
37
38
  declare const applyDiffs: (current: ReadonlyMap<string, Record<string, unknown>>, diffs: ReadonlyArray<TableDiff>) => Map<string, Record<string, unknown>>;
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  export { createBetterSqlite3Adapter } from './adapters/better-sqlite3.mjs';
2
2
  export { createSqliteWasmAdapter } from './adapters/sqlite-wasm.mjs';
3
3
  export { createSqlJsAdapter } from './adapters/sqljs.mjs';
4
- export { applyDiff, applyDiffToSnapshot, applyDiffs } from './packem_shared/applyDiff-98tKzmiW.mjs';
4
+ export { applyDiff, applyDiffToSnapshot, applyDiffs } from './packem_shared/applyDiff-Ctu04rYc.mjs';
5
5
  export { defineEvents } from './packem_shared/defineEvents-DiBkPTh_.mjs';
6
6
  export { MaterializerRuntime, defineMaterializer } from './packem_shared/MaterializerRuntime-BoIrsMYB.mjs';
7
7
  export { applyDiffToDb, applyDiffsToDb } from './packem_shared/applyDiffToDb-DQ1xZp5J.mjs';
8
8
  export { EventEmitter } from './packem_shared/EventEmitter-CMZfct03.mjs';
9
9
  export { EventLog } from './packem_shared/EventLog-CnK-3Wge.mjs';
10
- export { EventLogDO } from './packem_shared/EventLogDO-DqlsVx0H.mjs';
10
+ export { EventLogDO } from './packem_shared/EventLogDO-DIJWdi7n.mjs';
11
11
  export { EventLogDOClient } from './packem_shared/EventLogDOClient-F4FO8Si4.mjs';
12
12
  export { EventSource, UNHANDLED } from './packem_shared/EventSource-D5yO9_aI.mjs';
13
13
  export { eventsContext } from './packem_shared/eventsContext-Bk_p48hj.mjs';
@@ -11,7 +11,8 @@ const canonicalizeForFingerprint = (value) => {
11
11
  }
12
12
  if (value !== null && typeof value === "object") {
13
13
  const record = value;
14
- const sortedKeys = Object.keys(record).toSorted((a, b) => a.localeCompare(b));
14
+ const sortedKeys = Object.keys(record);
15
+ sortedKeys.sort();
15
16
  const result = {};
16
17
  for (const key of sortedKeys) {
17
18
  result[key] = canonicalizeForFingerprint(record[key]);
@@ -4,7 +4,8 @@ const canonicalizeForHash = (value) => {
4
4
  }
5
5
  if (value !== null && typeof value === "object") {
6
6
  const record = value;
7
- const sortedKeys = Object.keys(record).toSorted((a, b) => a.localeCompare(b));
7
+ const sortedKeys = Object.keys(record);
8
+ sortedKeys.sort();
8
9
  const result = {};
9
10
  for (const key of sortedKeys) {
10
11
  result[key] = canonicalizeForHash(record[key]);
@@ -13,47 +14,67 @@ const canonicalizeForHash = (value) => {
13
14
  }
14
15
  return value;
15
16
  };
17
+ const hex4 = (limb) => limb.toString(16).padStart(4, "0");
18
+ const fnv1a64Hex = (input) => {
19
+ let h0 = 8997;
20
+ let h1 = 33826;
21
+ let h2 = 40164;
22
+ let h3 = 52210;
23
+ for (let index = 0; index < input.length; index += 1) {
24
+ const point = input.codePointAt(index) ?? 0;
25
+ h0 ^= point & 65535;
26
+ h1 ^= point >>> 16 & 65535;
27
+ const p0 = h0 * 435;
28
+ const p1 = h1 * 435;
29
+ const p2 = h2 * 435 + h0 * 256;
30
+ const p3 = h3 * 435 + h1 * 256;
31
+ const c1 = p1 + (p0 >>> 16);
32
+ const c2 = p2 + (c1 >>> 16);
33
+ const c3 = p3 + (c2 >>> 16);
34
+ h0 = p0 & 65535;
35
+ h1 = c1 & 65535;
36
+ h2 = c2 & 65535;
37
+ h3 = c3 & 65535;
38
+ }
39
+ return hex4(h3) + hex4(h2) + hex4(h1) + hex4(h0);
40
+ };
16
41
  const deriveInsertId = (diff, changeIndex, data) => {
17
42
  const diffIdentity = diff.id ?? String(diff.timestamp);
18
43
  const input = `${diff.table}::${diffIdentity}::${String(changeIndex)}::${JSON.stringify(canonicalizeForHash(data))}`;
19
- let hash = 0xcbf29ce484222325n;
20
- const prime = 0x00000100000001b3n;
21
- const mask64 = 0xffffffffffffffffn;
22
- for (let index = 0; index < input.length; index += 1) {
23
- hash ^= BigInt(input.codePointAt(index) ?? 0);
24
- hash = hash * prime & mask64;
25
- }
26
- return `row-${hash.toString(16).padStart(16, "0")}`;
44
+ return `row-${fnv1a64Hex(input)}`;
27
45
  };
28
- const applyDiff = (current, diff) => {
29
- const next = new Map(current);
46
+ const applyDiffInto = (target, diff) => {
30
47
  for (const [changeIndex, change] of diff.changes.entries()) {
31
48
  switch (change.type) {
32
49
  case "delete": {
33
- next.delete(change.id);
50
+ target.delete(change.id);
34
51
  break;
35
52
  }
36
53
  case "insert": {
37
54
  const rawId = change.data.id;
38
55
  const id = typeof rawId === "string" || typeof rawId === "number" ? String(rawId) : deriveInsertId(diff, changeIndex, change.data);
39
- next.set(id, { ...change.data, id });
56
+ target.set(id, { ...change.data, id });
40
57
  break;
41
58
  }
42
59
  case "update": {
43
- const existing = next.get(change.id);
60
+ const existing = target.get(change.id);
44
61
  if (existing) {
45
- next.set(change.id, { ...existing, ...change.data });
62
+ target.set(change.id, { ...existing, ...change.data });
46
63
  }
47
64
  break;
48
65
  }
49
66
  }
50
67
  }
68
+ };
69
+ const applyDiff = (current, diff) => {
70
+ const next = new Map(current);
71
+ applyDiffInto(next, diff);
51
72
  return next;
52
73
  };
53
74
  const applyDiffs = (current, diffs) => {
54
- let result = new Map(current);
75
+ const result = new Map(current);
55
76
  for (const diff of diffs) {
56
- result = applyDiff(result, diff);
77
+ applyDiffInto(result, diff);
57
78
  }
58
79
  return result;
59
80
  };
@@ -64,4 +85,4 @@ const applyDiffToSnapshot = (snapshot, diff) => {
64
85
  return next;
65
86
  };
66
87
 
67
- export { applyDiff, applyDiffToSnapshot, applyDiffs };
88
+ export { applyDiff, applyDiffToSnapshot, applyDiffs, canonicalizeForHash, deriveInsertId, fnv1a64Hex };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/replica",
3
- "version": "1.0.0-alpha.7",
3
+ "version": "1.0.0-alpha.9",
4
4
  "description": "Local-first replica runtime + local SQLite mirror for Lunora",
5
5
  "keywords": [
6
6
  "cloudflare",