@spooky-sync/core 0.0.1-canary.33 → 0.0.1-canary.331

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.
Files changed (266) hide show
  1. package/AGENTS.md +84 -0
  2. package/README.md +56 -16
  3. package/dist/index.d.ts +2080 -142
  4. package/dist/index.js +16009 -2480
  5. package/dist/live.d.ts +149 -0
  6. package/dist/live.js +739 -0
  7. package/dist/otel/index.d.ts +1 -1
  8. package/dist/otel/index.js +3 -3
  9. package/dist/pure.d.ts +373 -0
  10. package/dist/pure.js +24 -0
  11. package/dist/sqlite-open.js +392 -0
  12. package/dist/sqlite-worker.d.ts +1 -0
  13. package/dist/sqlite-worker.js +439 -0
  14. package/dist/sw.d.ts +207 -0
  15. package/dist/sw.js +393 -0
  16. package/dist/tabs-broker-worker.d.ts +8 -0
  17. package/dist/tabs-broker-worker.js +477 -0
  18. package/dist/types.d.ts +1857 -38
  19. package/dist/worker-edges.js +480 -0
  20. package/dist/worker-impersonation.js +60 -0
  21. package/dist/worker-ref-tables.d.ts +48 -0
  22. package/dist/worker-types.d.ts +112 -0
  23. package/dist/worker-types.js +73 -0
  24. package/package.json +28 -7
  25. package/scripts/check-broker-bundle.mjs +33 -0
  26. package/scripts/check-worker-entries.mjs +173 -0
  27. package/skills/sp00ky-core/SKILL.md +32 -7
  28. package/skills/sp00ky-core/references/config.md +7 -1
  29. package/src/boot/auth-flip.saga.test.ts +135 -0
  30. package/src/boot/auth-flip.saga.ts +77 -0
  31. package/src/boot/boot.saga.test.ts +203 -0
  32. package/src/boot/boot.saga.ts +146 -0
  33. package/src/boot/bucket-switch.saga.test.ts +187 -0
  34. package/src/boot/bucket-switch.saga.ts +133 -0
  35. package/src/boot/preload.saga.test.ts +80 -0
  36. package/src/boot/preload.saga.ts +40 -0
  37. package/src/bucket-blurhash.test.ts +148 -0
  38. package/src/build-globals.d.ts +12 -0
  39. package/src/client/bucket-handle.test.ts +195 -0
  40. package/src/client/bucket-handle.ts +352 -0
  41. package/src/client/router.test.ts +58 -0
  42. package/src/client/router.ts +100 -0
  43. package/src/client/runtime.test.ts +195 -0
  44. package/src/client/runtime.ts +296 -0
  45. package/src/client/services.test.ts +74 -0
  46. package/src/client/services.ts +434 -0
  47. package/src/client/sp00ky-client.test.ts +341 -0
  48. package/src/client/sp00ky-client.ts +625 -0
  49. package/src/events/events.test.ts +2 -1
  50. package/src/events/index.ts +3 -0
  51. package/src/index.ts +69 -1
  52. package/src/kernel/constants.test.ts +19 -0
  53. package/src/kernel/constants.ts +80 -0
  54. package/src/kernel/effects.test.ts +40 -0
  55. package/src/kernel/effects.ts +167 -0
  56. package/src/kernel/events.ts +57 -0
  57. package/src/kernel/interpreter.test.ts +116 -0
  58. package/src/kernel/interpreter.ts +148 -0
  59. package/src/kernel/saga.test.ts +89 -0
  60. package/src/kernel/saga.ts +73 -0
  61. package/src/live/index.test.ts +512 -0
  62. package/src/live/index.ts +1059 -0
  63. package/src/modules/app-release/index.test.ts +125 -0
  64. package/src/modules/app-release/index.ts +209 -0
  65. package/src/modules/auth/auth.local-first.test.ts +228 -0
  66. package/src/modules/auth/events/index.ts +9 -2
  67. package/src/modules/auth/impersonation-banner.ts +443 -0
  68. package/src/modules/auth/impersonation.integration.test.ts +205 -0
  69. package/src/modules/auth/impersonation.test.ts +377 -0
  70. package/src/modules/auth/impersonation.ts +179 -0
  71. package/src/modules/auth/index.ts +476 -22
  72. package/src/modules/crdt/acceptance.ts +75 -0
  73. package/src/modules/crdt/bytes.ts +50 -0
  74. package/src/modules/crdt/crdt-field.ts +125 -0
  75. package/src/modules/crdt/crdt.test.ts +469 -0
  76. package/src/modules/crdt/hash.ts +103 -0
  77. package/src/modules/crdt/index.ts +757 -0
  78. package/src/modules/crdt/loro-loader.ts +25 -0
  79. package/src/modules/crdt/mirror.ts +37 -0
  80. package/src/modules/devtools/flags.ts +349 -0
  81. package/src/modules/devtools/index.ts +746 -95
  82. package/src/modules/devtools/mutations.test.ts +92 -0
  83. package/src/modules/devtools/mutations.ts +160 -0
  84. package/src/modules/devtools/notify-throttle.test.ts +166 -0
  85. package/src/modules/devtools/state-shape.test.ts +263 -0
  86. package/src/modules/devtools/storage-info.test.ts +79 -0
  87. package/src/modules/devtools/storage-info.ts +168 -0
  88. package/src/modules/devtools/versions.test.ts +74 -0
  89. package/src/modules/devtools/versions.ts +110 -0
  90. package/src/modules/feature-flag/index.test.ts +298 -0
  91. package/src/modules/feature-flag/index.ts +316 -0
  92. package/src/modules/ref-tables.test.ts +93 -0
  93. package/src/modules/ref-tables.ts +91 -0
  94. package/src/modules/web-push/index.test.ts +824 -0
  95. package/src/modules/web-push/index.ts +1027 -0
  96. package/src/mutation/jobs.test.ts +25 -0
  97. package/src/mutation/jobs.ts +41 -0
  98. package/src/mutation/mutation-id.test.ts +37 -0
  99. package/src/mutation/mutation-id.ts +35 -0
  100. package/src/mutation/pending-writes.integration.test.ts +175 -0
  101. package/src/mutation/push.saga.test.ts +301 -0
  102. package/src/mutation/push.saga.ts +225 -0
  103. package/src/mutation/rows.test.ts +231 -0
  104. package/src/mutation/rows.ts +427 -0
  105. package/src/mutation/tray.saga.test.ts +63 -0
  106. package/src/mutation/tray.saga.ts +48 -0
  107. package/src/mutation/write.saga.test.ts +316 -0
  108. package/src/mutation/write.saga.ts +268 -0
  109. package/src/otel/index.ts +10 -7
  110. package/src/pure.ts +66 -0
  111. package/src/push/types.ts +174 -0
  112. package/src/query/anon-query-isolation.integration.test.ts +527 -0
  113. package/src/query/env.test.ts +23 -0
  114. package/src/query/env.ts +65 -0
  115. package/src/query/fetch.saga.test.ts +199 -0
  116. package/src/query/fetch.saga.ts +120 -0
  117. package/src/query/hash.test.ts +14 -0
  118. package/src/query/hash.ts +18 -0
  119. package/src/query/id-set-plan.test.ts +132 -0
  120. package/src/query/lifecycle.saga.test.ts +274 -0
  121. package/src/query/lifecycle.saga.ts +186 -0
  122. package/src/query/list-ref-live.integration.test.ts +203 -0
  123. package/src/query/materialize.saga.test.ts +115 -0
  124. package/src/query/materialize.saga.ts +84 -0
  125. package/src/query/materialize.test.ts +58 -0
  126. package/src/query/materialize.ts +57 -0
  127. package/src/query/membership.saga.test.ts +407 -0
  128. package/src/query/membership.saga.ts +282 -0
  129. package/src/query/membership.test.ts +118 -0
  130. package/src/query/membership.ts +201 -0
  131. package/src/query/register.saga.test.ts +311 -0
  132. package/src/query/register.saga.ts +235 -0
  133. package/src/query/render-set.test.ts +47 -0
  134. package/src/query/render-set.ts +71 -0
  135. package/src/query/sql.test.ts +118 -0
  136. package/src/query/sql.ts +157 -0
  137. package/src/query/window-query.test.ts +70 -0
  138. package/src/query/window-query.ts +208 -0
  139. package/src/services/blobs/blob-cache.test.ts +359 -0
  140. package/src/services/blobs/blob-cache.ts +603 -0
  141. package/src/services/blobs/blob-manifest.ts +227 -0
  142. package/src/services/blobs/blob-store.test.ts +77 -0
  143. package/src/services/blobs/blob-store.ts +359 -0
  144. package/src/services/blobs/blob.fixture.ts +90 -0
  145. package/src/services/blobs/index.ts +70 -0
  146. package/src/services/database/cache-engine.ts +209 -0
  147. package/src/services/database/connection-supervisor.test.ts +318 -0
  148. package/src/services/database/connection-supervisor.ts +422 -0
  149. package/src/services/database/database.query-timeout.test.ts +140 -0
  150. package/src/services/database/database.responses.test.ts +43 -0
  151. package/src/services/database/database.ts +156 -61
  152. package/src/services/database/engine-factory.ts +33 -0
  153. package/src/services/database/errors.ts +34 -0
  154. package/src/services/database/events/index.ts +2 -1
  155. package/src/services/database/index.ts +7 -0
  156. package/src/services/database/landed-body-clear.integration.test.ts +236 -0
  157. package/src/services/database/local-migrator.integration.test.ts +110 -0
  158. package/src/services/database/local-migrator.ts +61 -16
  159. package/src/services/database/local.test.ts +150 -0
  160. package/src/services/database/local.ts +508 -63
  161. package/src/services/database/plan-render.test.ts +159 -0
  162. package/src/services/database/plan-render.ts +108 -0
  163. package/src/services/database/relation-resolver.test.ts +484 -0
  164. package/src/services/database/relation-resolver.ts +0 -0
  165. package/src/services/database/remote.ts +285 -6
  166. package/src/services/database/sqlite-cache-engine.test.ts +628 -0
  167. package/src/services/database/sqlite-cache-engine.timeout.test.ts +61 -0
  168. package/src/services/database/sqlite-cache-engine.ts +1401 -0
  169. package/src/services/database/sqlite-column-reset.integration.test.ts +100 -0
  170. package/src/services/database/sqlite-column-reset.ts +91 -0
  171. package/src/services/database/sqlite-devtools-queries.integration.test.ts +143 -0
  172. package/src/services/database/sqlite-devtools-queries.test.ts +154 -0
  173. package/src/services/database/sqlite-lock-verify.test.ts +33 -0
  174. package/src/services/database/sqlite-lock-verify.ts +45 -0
  175. package/src/services/database/sqlite-open.test.ts +150 -0
  176. package/src/services/database/sqlite-open.ts +164 -0
  177. package/src/services/database/sqlite-plan-sql.test.ts +164 -0
  178. package/src/services/database/sqlite-plan-sql.ts +196 -0
  179. package/src/services/database/sqlite-projection.test.ts +121 -0
  180. package/src/services/database/sqlite-select.integration.test.ts +185 -0
  181. package/src/services/database/sqlite-select.test.ts +261 -0
  182. package/src/services/database/sqlite-select.ts +131 -0
  183. package/src/services/database/sqlite-transport.fixture.ts +30 -0
  184. package/src/services/database/sqlite-transport.ts +224 -0
  185. package/src/services/database/sqlite-worker.ts +437 -0
  186. package/src/services/database/surql-translate.ts +417 -0
  187. package/src/services/database/surreal-cache-engine.ts +163 -0
  188. package/src/services/logger/index.test.ts +132 -0
  189. package/src/services/logger/index.ts +189 -7
  190. package/src/services/persistence/localstorage.ts +2 -2
  191. package/src/services/persistence/resilient.ts +10 -3
  192. package/src/services/persistence/surrealdb.ts +4 -4
  193. package/src/services/stream-processor/index.ts +806 -67
  194. package/src/services/stream-processor/permissions.test.ts +47 -0
  195. package/src/services/stream-processor/permissions.ts +53 -0
  196. package/src/services/stream-processor/stream-processor.batch.test.ts +186 -0
  197. package/src/services/stream-processor/stream-processor.prime.test.ts +234 -0
  198. package/src/services/stream-processor/stream-processor.reset.test.ts +226 -0
  199. package/src/services/stream-processor/stream-processor.test.ts +1 -1
  200. package/src/services/stream-processor/wasm-types.ts +58 -2
  201. package/src/services/tabs/broker-client.ts +284 -0
  202. package/src/services/tabs/broker.test.ts +381 -0
  203. package/src/services/tabs/coordinator.test.ts +386 -0
  204. package/src/services/tabs/coordinator.ts +646 -0
  205. package/src/services/tabs/fake-ports.fixture.ts +112 -0
  206. package/src/services/tabs/leader-locks.ts +75 -0
  207. package/src/services/tabs/protocol.ts +262 -0
  208. package/src/services/tabs/support.ts +36 -0
  209. package/src/services/tabs/tabs-broker-worker.ts +652 -0
  210. package/src/sp00ky.ts +5 -395
  211. package/src/state/client-state.ts +208 -0
  212. package/src/state/lifecycle.test.ts +141 -0
  213. package/src/state/lifecycle.ts +99 -0
  214. package/src/state/reducers.test.ts +306 -0
  215. package/src/state/reducers.ts +531 -0
  216. package/src/state/selectors.test.ts +188 -0
  217. package/src/state/selectors.ts +258 -0
  218. package/src/sw/index.test.ts +582 -0
  219. package/src/sw/index.ts +607 -0
  220. package/src/sw/store.ts +96 -0
  221. package/src/sync/connection.saga.test.ts +97 -0
  222. package/src/sync/connection.saga.ts +89 -0
  223. package/src/sync/live.saga.test.ts +178 -0
  224. package/src/sync/live.saga.ts +107 -0
  225. package/src/sync/policy.health.test.ts +56 -0
  226. package/src/sync/policy.test.ts +663 -0
  227. package/src/sync/policy.ts +515 -0
  228. package/src/sync/poll.saga.test.ts +51 -0
  229. package/src/sync/poll.saga.ts +36 -0
  230. package/src/sync/tabs.saga.test.ts +78 -0
  231. package/src/sync/tabs.saga.ts +96 -0
  232. package/src/testing/adapters.ts +137 -0
  233. package/src/testing/build.ts +63 -0
  234. package/src/testing/fake-services.ts +63 -0
  235. package/src/testing/run-pure.test.ts +92 -0
  236. package/src/testing/run-pure.ts +128 -0
  237. package/src/testing/services.ts +17 -0
  238. package/src/types.ts +633 -10
  239. package/src/utils/blurhash.ts +90 -0
  240. package/src/utils/edges.ts +39 -0
  241. package/src/utils/error-classification.test.ts +54 -0
  242. package/src/utils/error-classification.ts +25 -0
  243. package/src/utils/index.ts +89 -12
  244. package/src/utils/parser.test.ts +67 -102
  245. package/src/utils/parser.ts +64 -2
  246. package/src/utils/semver.test.ts +32 -0
  247. package/src/utils/semver.ts +30 -0
  248. package/src/utils/sha256.ts +15 -0
  249. package/src/utils/surql.ts +29 -18
  250. package/src/utils/utils.test.ts +3 -2
  251. package/tsdown.config.ts +117 -3
  252. package/vitest.config.ts +48 -0
  253. package/src/modules/cache/index.ts +0 -241
  254. package/src/modules/cache/types.ts +0 -19
  255. package/src/modules/data/data.test.ts +0 -58
  256. package/src/modules/data/index.ts +0 -879
  257. package/src/modules/sync/engine.ts +0 -173
  258. package/src/modules/sync/events/index.ts +0 -77
  259. package/src/modules/sync/index.ts +0 -3
  260. package/src/modules/sync/queue/index.ts +0 -2
  261. package/src/modules/sync/queue/queue-down.ts +0 -89
  262. package/src/modules/sync/queue/queue-up.ts +0 -223
  263. package/src/modules/sync/scheduler.ts +0 -84
  264. package/src/modules/sync/sync.ts +0 -421
  265. package/src/modules/sync/utils.test.ts +0 -311
  266. package/src/modules/sync/utils.ts +0 -167
@@ -1,311 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { RecordId } from 'surrealdb';
3
- import {
4
- diffRecordVersionArray,
5
- applyRecordVersionDiff,
6
- createDiffFromDbOp,
7
- ArraySyncer,
8
- } from './utils';
9
- import { RecordVersionArray, RecordVersionDiff } from '../../types';
10
- import { encodeRecordId } from '../../utils/index';
11
-
12
- function rid(table: string, id: string): RecordId<string> {
13
- return new RecordId(table, id);
14
- }
15
-
16
- describe('diffRecordVersionArray', () => {
17
- it('detects added records (in remote, not local)', () => {
18
- const local: RecordVersionArray = [['user:1', 1]];
19
- const remote: RecordVersionArray = [
20
- ['user:1', 1],
21
- ['user:2', 1],
22
- ];
23
- const diff = diffRecordVersionArray(local, remote);
24
-
25
- expect(diff.added).toHaveLength(1);
26
- expect(encodeRecordId(diff.added[0].id)).toBe('user:2');
27
- expect(diff.added[0].version).toBe(1);
28
- expect(diff.updated).toHaveLength(0);
29
- expect(diff.removed).toHaveLength(0);
30
- });
31
-
32
- it('detects updated records (remote version > local version)', () => {
33
- const local: RecordVersionArray = [['user:1', 1]];
34
- const remote: RecordVersionArray = [['user:1', 3]];
35
- const diff = diffRecordVersionArray(local, remote);
36
-
37
- expect(diff.updated).toHaveLength(1);
38
- expect(encodeRecordId(diff.updated[0].id)).toBe('user:1');
39
- expect(diff.updated[0].version).toBe(3);
40
- expect(diff.added).toHaveLength(0);
41
- expect(diff.removed).toHaveLength(0);
42
- });
43
-
44
- it('detects removed records (in local, not remote)', () => {
45
- const local: RecordVersionArray = [
46
- ['user:1', 1],
47
- ['user:2', 1],
48
- ];
49
- const remote: RecordVersionArray = [['user:1', 1]];
50
- const diff = diffRecordVersionArray(local, remote);
51
-
52
- expect(diff.removed).toHaveLength(1);
53
- expect(encodeRecordId(diff.removed[0])).toBe('user:2');
54
- expect(diff.added).toHaveLength(0);
55
- expect(diff.updated).toHaveLength(0);
56
- });
57
-
58
- it('handles null arrays', () => {
59
- const diff = diffRecordVersionArray(null, null);
60
- expect(diff.added).toHaveLength(0);
61
- expect(diff.updated).toHaveLength(0);
62
- expect(diff.removed).toHaveLength(0);
63
- });
64
-
65
- it('handles empty arrays', () => {
66
- const diff = diffRecordVersionArray([], []);
67
- expect(diff.added).toHaveLength(0);
68
- expect(diff.updated).toHaveLength(0);
69
- expect(diff.removed).toHaveLength(0);
70
- });
71
-
72
- it('no diff when arrays match', () => {
73
- const arr: RecordVersionArray = [
74
- ['user:1', 1],
75
- ['user:2', 2],
76
- ];
77
- const diff = diffRecordVersionArray(arr, arr);
78
- expect(diff.added).toHaveLength(0);
79
- expect(diff.updated).toHaveLength(0);
80
- expect(diff.removed).toHaveLength(0);
81
- });
82
-
83
- it('handles mixed adds/updates/removes', () => {
84
- const local: RecordVersionArray = [
85
- ['user:1', 1],
86
- ['user:2', 1],
87
- ['user:3', 1],
88
- ];
89
- const remote: RecordVersionArray = [
90
- ['user:1', 1], // same
91
- ['user:2', 3], // updated
92
- // user:3 removed
93
- ['user:4', 1], // added
94
- ];
95
- const diff = diffRecordVersionArray(local, remote);
96
-
97
- expect(diff.added).toHaveLength(1);
98
- expect(encodeRecordId(diff.added[0].id)).toBe('user:4');
99
- expect(diff.updated).toHaveLength(1);
100
- expect(encodeRecordId(diff.updated[0].id)).toBe('user:2');
101
- expect(diff.removed).toHaveLength(1);
102
- expect(encodeRecordId(diff.removed[0])).toBe('user:3');
103
- });
104
- });
105
-
106
- describe('applyRecordVersionDiff', () => {
107
- it('applies additions', () => {
108
- const current: RecordVersionArray = [['user:1', 1]];
109
- const diff: RecordVersionDiff = {
110
- added: [{ id: rid('user', '2'), version: 1 }],
111
- updated: [],
112
- removed: [],
113
- };
114
- const result = applyRecordVersionDiff(current, diff);
115
- expect(result).toEqual([
116
- ['user:1', 1],
117
- ['user:2', 1],
118
- ]);
119
- });
120
-
121
- it('applies updates', () => {
122
- const current: RecordVersionArray = [['user:1', 1]];
123
- const diff: RecordVersionDiff = {
124
- added: [],
125
- updated: [{ id: rid('user', '1'), version: 5 }],
126
- removed: [],
127
- };
128
- const result = applyRecordVersionDiff(current, diff);
129
- expect(result).toEqual([['user:1', 5]]);
130
- });
131
-
132
- it('applies removals', () => {
133
- const current: RecordVersionArray = [
134
- ['user:1', 1],
135
- ['user:2', 2],
136
- ];
137
- const diff: RecordVersionDiff = {
138
- added: [],
139
- updated: [],
140
- removed: [rid('user', '1')],
141
- };
142
- const result = applyRecordVersionDiff(current, diff);
143
- expect(result).toEqual([['user:2', 2]]);
144
- });
145
-
146
- it('result is sorted by record ID', () => {
147
- const current: RecordVersionArray = [['user:c', 1]];
148
- const diff: RecordVersionDiff = {
149
- added: [
150
- { id: rid('user', 'a'), version: 1 },
151
- { id: rid('user', 'z'), version: 1 },
152
- ],
153
- updated: [],
154
- removed: [],
155
- };
156
- const result = applyRecordVersionDiff(current, diff);
157
- expect(result).toEqual([
158
- ['user:a', 1],
159
- ['user:c', 1],
160
- ['user:z', 1],
161
- ]);
162
- });
163
-
164
- it('empty diff returns original (sorted)', () => {
165
- const current: RecordVersionArray = [
166
- ['user:b', 2],
167
- ['user:a', 1],
168
- ];
169
- const diff: RecordVersionDiff = { added: [], updated: [], removed: [] };
170
- const result = applyRecordVersionDiff(current, diff);
171
- expect(result).toEqual([
172
- ['user:a', 1],
173
- ['user:b', 2],
174
- ]);
175
- });
176
- });
177
-
178
- describe('createDiffFromDbOp', () => {
179
- it('CREATE populates added array', () => {
180
- const recordId = rid('user', '1');
181
- const diff = createDiffFromDbOp('CREATE', recordId, 1);
182
- expect(diff.added).toHaveLength(1);
183
- expect(diff.added[0].id).toBe(recordId);
184
- expect(diff.added[0].version).toBe(1);
185
- expect(diff.updated).toHaveLength(0);
186
- expect(diff.removed).toHaveLength(0);
187
- });
188
-
189
- it('UPDATE populates updated array', () => {
190
- const recordId = rid('user', '1');
191
- const diff = createDiffFromDbOp('UPDATE', recordId, 2);
192
- expect(diff.updated).toHaveLength(1);
193
- expect(diff.updated[0].id).toBe(recordId);
194
- expect(diff.updated[0].version).toBe(2);
195
- expect(diff.added).toHaveLength(0);
196
- expect(diff.removed).toHaveLength(0);
197
- });
198
-
199
- it('DELETE populates removed array', () => {
200
- const recordId = rid('user', '1');
201
- const diff = createDiffFromDbOp('DELETE', recordId, 1);
202
- expect(diff.removed).toHaveLength(1);
203
- expect(diff.removed[0]).toBe(recordId);
204
- expect(diff.added).toHaveLength(0);
205
- expect(diff.updated).toHaveLength(0);
206
- });
207
-
208
- it('skips if existing version >= new version', () => {
209
- const recordId = rid('user', '1');
210
- const versions: RecordVersionArray = [['user:1', 5]];
211
- const diff = createDiffFromDbOp('UPDATE', recordId, 3, versions);
212
- expect(diff.added).toHaveLength(0);
213
- expect(diff.updated).toHaveLength(0);
214
- expect(diff.removed).toHaveLength(0);
215
- });
216
-
217
- it('applies if existing version < new version', () => {
218
- const recordId = rid('user', '1');
219
- const versions: RecordVersionArray = [['user:1', 2]];
220
- const diff = createDiffFromDbOp('UPDATE', recordId, 5, versions);
221
- expect(diff.updated).toHaveLength(1);
222
- expect(diff.updated[0].version).toBe(5);
223
- });
224
- });
225
-
226
- describe('ArraySyncer', () => {
227
- it('insert adds to local array', () => {
228
- const syncer = new ArraySyncer(
229
- [['user:1', 1]],
230
- [['user:1', 1]]
231
- );
232
-
233
- syncer.insert('user:2', 1);
234
- const diff = syncer.nextSet();
235
- expect(diff).not.toBeNull();
236
- // local now has user:2 which remote does not → user:2 is in local removed from remote perspective
237
- // Actually: local=[user:1, user:2], remote=[user:1] → user:2 is "removed" (in local, not remote)
238
- expect(diff!.removed).toHaveLength(1);
239
- expect(encodeRecordId(diff!.removed[0])).toBe('user:2');
240
- });
241
-
242
- it('update modifies version in local array', () => {
243
- const syncer = new ArraySyncer(
244
- [['user:1', 1]],
245
- [['user:1', 1]]
246
- );
247
-
248
- syncer.update('user:1', 5);
249
- const diff = syncer.nextSet();
250
- // local version (5) > remote version (1), so no "updated" from diff perspective
251
- // diff finds remote additions/updates relative to local; local version higher means no remote update
252
- expect(diff).not.toBeNull();
253
- expect(diff!.added).toHaveLength(0);
254
- expect(diff!.updated).toHaveLength(0);
255
- expect(diff!.removed).toHaveLength(0);
256
- });
257
-
258
- it('delete removes from local array', () => {
259
- const syncer = new ArraySyncer(
260
- [
261
- ['user:1', 1],
262
- ['user:2', 1],
263
- ],
264
- [
265
- ['user:1', 1],
266
- ['user:2', 1],
267
- ]
268
- );
269
-
270
- syncer.delete('user:2');
271
- const diff = syncer.nextSet();
272
- expect(diff).not.toBeNull();
273
- // remote has user:2, local does not → added from remote perspective
274
- expect(diff!.added).toHaveLength(1);
275
- expect(encodeRecordId(diff!.added[0].id)).toBe('user:2');
276
- });
277
-
278
- it('nextSet returns correct diff against remote', () => {
279
- const syncer = new ArraySyncer(
280
- [['user:1', 1]],
281
- [
282
- ['user:1', 1],
283
- ['user:2', 1],
284
- ]
285
- );
286
-
287
- const diff = syncer.nextSet();
288
- expect(diff).not.toBeNull();
289
- expect(diff!.added).toHaveLength(1);
290
- expect(encodeRecordId(diff!.added[0].id)).toBe('user:2');
291
- });
292
-
293
- it('maintains sorting after mutations', () => {
294
- const syncer = new ArraySyncer(
295
- [['user:c', 1]],
296
- []
297
- );
298
-
299
- syncer.insert('user:a', 1);
300
- syncer.insert('user:z', 1);
301
-
302
- // nextSet triggers sort
303
- const diff = syncer.nextSet();
304
- // All 3 are in local but not remote → 3 removed items
305
- expect(diff!.removed).toHaveLength(3);
306
- // Check they come in sorted order
307
- const removedIds = diff!.removed.map((r) => encodeRecordId(r));
308
- const sorted = [...removedIds].sort();
309
- expect(removedIds).toEqual(sorted);
310
- });
311
- });
@@ -1,167 +0,0 @@
1
- import { RecordId } from 'surrealdb';
2
- import { RecordVersionArray, RecordVersionDiff } from '../../types';
3
- import { parseRecordIdString, encodeRecordId } from '../../utils/index';
4
-
5
- export class ArraySyncer {
6
- private localArray: RecordVersionArray;
7
- private remoteArray: RecordVersionArray;
8
- private needsSort = false;
9
-
10
- constructor(localArray: RecordVersionArray, remoteArray: RecordVersionArray) {
11
- this.remoteArray = remoteArray.sort((a, b) => a[0].localeCompare(b[0]));
12
- this.localArray = localArray.sort((a, b) => a[0].localeCompare(b[0]));
13
- }
14
-
15
- /**
16
- * Inserts an item into the local array
17
- */
18
- insert(recordId: string, version: number) {
19
- this.localArray.push([recordId, version]);
20
- this.needsSort = true;
21
- }
22
-
23
- /**
24
- * Updates the current local RecordVersionArray state.
25
- */
26
- update(recordId: string, version: number) {
27
- this.localArray = this.localArray.map((record) => {
28
- if (record[0] === recordId) {
29
- this.needsSort = true;
30
- return [recordId, version];
31
- }
32
- return record;
33
- });
34
- }
35
-
36
- /**
37
- * Deletes an item from the local array
38
- */
39
- delete(recordId: string) {
40
- this.localArray = this.localArray.filter((record) => record[0] !== recordId);
41
- }
42
-
43
- /**
44
- * Returns the difference between the local and remote arrays.
45
- * Includes sets of added, updated, and removed records.
46
- */
47
- nextSet(): RecordVersionDiff | null {
48
- if (this.needsSort) {
49
- this.localArray.sort((a, b) => a[0].localeCompare(b[0]));
50
- this.needsSort = false;
51
- }
52
- const diff = diffRecordVersionArray(this.localArray, this.remoteArray);
53
- return diff;
54
- }
55
- }
56
-
57
- export function diffRecordVersionArray(
58
- local: RecordVersionArray | null,
59
- remote: RecordVersionArray | null
60
- ): RecordVersionDiff {
61
- const localArray = local || [];
62
- const remoteArray = remote || [];
63
-
64
- // Convert arrays to Maps for O(1) lookup
65
- const localMap = new Map<string, number>(localArray);
66
- const remoteMap = new Map<string, number>(remoteArray);
67
-
68
- const added: string[] = [];
69
- const updated: string[] = [];
70
- const removed: string[] = [];
71
-
72
- // Find added and updated records
73
- for (const [recordId, remoteVersion] of remoteMap) {
74
- const localVersion = localMap.get(recordId);
75
-
76
- if (localVersion === undefined) {
77
- // Record exists in remote but not in local
78
- added.push(recordId);
79
- } else if (localVersion < remoteVersion) {
80
- // Record exists in both but remote has newer version
81
- updated.push(recordId);
82
- }
83
- }
84
-
85
- // Find removed records
86
- for (const [recordId] of localMap) {
87
- if (!remoteMap.has(recordId)) {
88
- removed.push(recordId);
89
- }
90
- }
91
-
92
- return {
93
- added: added.map((id) => ({
94
- id: parseRecordIdString(id),
95
- version: remoteMap.get(id)!,
96
- })),
97
- updated: updated.map((id) => ({
98
- id: parseRecordIdString(id),
99
- version: remoteMap.get(id)!,
100
- })),
101
- removed: removed.map(parseRecordIdString),
102
- };
103
- }
104
-
105
- /**
106
- * Applies a RecordVersionDiff to a RecordVersionArray and returns a new sorted array.
107
- */
108
- export function applyRecordVersionDiff(
109
- current: RecordVersionArray,
110
- diff: RecordVersionDiff
111
- ): RecordVersionArray {
112
- const currentMap = new Map(current);
113
-
114
- // Apply removals
115
- for (const id of diff.removed) {
116
- currentMap.delete(encodeRecordId(id));
117
- }
118
-
119
- // Apply additions
120
- for (const item of diff.added) {
121
- currentMap.set(encodeRecordId(item.id), item.version);
122
- }
123
-
124
- // Apply updates
125
- for (const item of diff.updated) {
126
- currentMap.set(encodeRecordId(item.id), item.version);
127
- }
128
-
129
- return Array.from(currentMap).sort((a, b) => a[0].localeCompare(b[0]));
130
- }
131
-
132
- export function createDiffFromDbOp(
133
- op: 'CREATE' | 'UPDATE' | 'DELETE',
134
- recordId: RecordId,
135
- version: number,
136
- versions?: RecordVersionArray
137
- ): RecordVersionDiff {
138
- const old = versions?.find((record) => record[0] === encodeRecordId(recordId));
139
-
140
- if (old && old[1] >= version) {
141
- return {
142
- added: [],
143
- updated: [],
144
- removed: [],
145
- };
146
- }
147
-
148
- if (op === 'CREATE') {
149
- return {
150
- added: [{ id: recordId, version }],
151
- updated: [],
152
- removed: [],
153
- };
154
- } else if (op === 'UPDATE') {
155
- return {
156
- added: [],
157
- updated: [{ id: recordId, version }],
158
- removed: [],
159
- };
160
- } else {
161
- return {
162
- added: [],
163
- updated: [],
164
- removed: [recordId],
165
- };
166
- }
167
- }