@absolutejs/sync 0.0.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.
Files changed (75) hide show
  1. package/README.md +281 -24
  2. package/dist/adapters/drizzle/collection.d.ts +27 -0
  3. package/dist/adapters/drizzle/index.d.ts +20 -0
  4. package/dist/adapters/drizzle/index.js +265 -0
  5. package/dist/adapters/drizzle/index.js.map +14 -0
  6. package/dist/adapters/drizzle/predicate.d.ts +20 -0
  7. package/dist/adapters/drizzle/read.d.ts +31 -0
  8. package/dist/adapters/drizzle/topics.d.ts +41 -0
  9. package/dist/adapters/drizzle/write.d.ts +69 -0
  10. package/dist/adapters/mysql/index.d.ts +75 -0
  11. package/dist/adapters/mysql/index.js +171 -0
  12. package/dist/adapters/mysql/index.js.map +11 -0
  13. package/dist/adapters/postgres/index.d.ts +53 -0
  14. package/dist/adapters/postgres/index.js +86 -0
  15. package/dist/adapters/postgres/index.js.map +10 -0
  16. package/dist/adapters/prisma/collection.d.ts +39 -0
  17. package/dist/adapters/prisma/index.d.ts +23 -0
  18. package/dist/adapters/prisma/index.js +231 -0
  19. package/dist/adapters/prisma/index.js.map +14 -0
  20. package/dist/adapters/prisma/predicate.d.ts +20 -0
  21. package/dist/adapters/prisma/read.d.ts +28 -0
  22. package/dist/adapters/prisma/topics.d.ts +29 -0
  23. package/dist/adapters/prisma/write.d.ts +65 -0
  24. package/dist/adapters/sqlite/index.d.ts +32 -0
  25. package/dist/adapters/sqlite/index.js +128 -0
  26. package/dist/adapters/sqlite/index.js.map +11 -0
  27. package/dist/angular/index.d.ts +1 -0
  28. package/dist/angular/index.js +347 -0
  29. package/dist/angular/index.js.map +11 -0
  30. package/dist/angular/sync-collection.service.d.ts +20 -0
  31. package/dist/client/index.d.ts +12 -30
  32. package/dist/client/index.js +1099 -3
  33. package/dist/client/index.js.map +10 -4
  34. package/dist/client/liveQuery.d.ts +75 -0
  35. package/dist/client/presence.d.ts +37 -0
  36. package/dist/client/subscriber.d.ts +30 -0
  37. package/dist/client/syncClient.d.ts +53 -0
  38. package/dist/client/syncCollection.d.ts +102 -0
  39. package/dist/client/syncStore.d.ts +81 -0
  40. package/dist/engine/aggregate.d.ts +45 -0
  41. package/dist/engine/cluster.d.ts +41 -0
  42. package/dist/engine/collection.d.ts +87 -0
  43. package/dist/engine/connection.d.ts +103 -0
  44. package/dist/engine/dataflow.d.ts +109 -0
  45. package/dist/engine/equiJoin.d.ts +51 -0
  46. package/dist/engine/graph.d.ts +85 -0
  47. package/dist/engine/index.d.ts +40 -0
  48. package/dist/engine/index.js +1774 -0
  49. package/dist/engine/index.js.map +23 -0
  50. package/dist/engine/materializedView.d.ts +53 -0
  51. package/dist/engine/mutation.d.ts +66 -0
  52. package/dist/engine/pollingSource.d.ts +42 -0
  53. package/dist/engine/presence.d.ts +46 -0
  54. package/dist/engine/reactive.d.ts +67 -0
  55. package/dist/engine/routes.d.ts +40 -0
  56. package/dist/engine/socket.d.ts +67 -0
  57. package/dist/engine/syncEngine.d.ts +132 -0
  58. package/dist/engine/types.d.ts +45 -0
  59. package/dist/index.d.ts +4 -0
  60. package/dist/index.js +327 -3
  61. package/dist/index.js.map +8 -5
  62. package/dist/react/index.d.ts +1 -0
  63. package/dist/react/index.js +332 -0
  64. package/dist/react/index.js.map +11 -0
  65. package/dist/react/useSyncCollection.d.ts +16 -0
  66. package/dist/reactiveHub.d.ts +6 -0
  67. package/dist/svelte/createSyncCollectionStore.d.ts +15 -0
  68. package/dist/svelte/index.d.ts +1 -0
  69. package/dist/svelte/index.js +338 -0
  70. package/dist/svelte/index.js.map +11 -0
  71. package/dist/vue/index.d.ts +1 -0
  72. package/dist/vue/index.js +331 -0
  73. package/dist/vue/index.js.map +11 -0
  74. package/dist/vue/useSyncCollection.d.ts +17 -0
  75. package/package.json +104 -6
@@ -0,0 +1,1774 @@
1
+ // @bun
2
+ // src/engine/materializedView.ts
3
+ var emptyDiff = () => ({
4
+ added: [],
5
+ removed: [],
6
+ changed: []
7
+ });
8
+ var shallowEqual = (a, b) => {
9
+ if (a === b) {
10
+ return true;
11
+ }
12
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
13
+ return false;
14
+ }
15
+ const aKeys = Object.keys(a);
16
+ const bKeys = Object.keys(b);
17
+ if (aKeys.length !== bKeys.length) {
18
+ return false;
19
+ }
20
+ return aKeys.every((key) => a[key] === b[key]);
21
+ };
22
+ var isEmptyViewDiff = (diff) => diff.added.length === 0 && diff.removed.length === 0 && diff.changed.length === 0;
23
+ var createMaterializedView = (options) => {
24
+ const { key, match } = options;
25
+ const equals = options.equals ?? shallowEqual;
26
+ const set = new Map;
27
+ return {
28
+ hydrate: (rows) => {
29
+ set.clear();
30
+ for (const row of rows) {
31
+ set.set(key(row), row);
32
+ }
33
+ },
34
+ reset: (rows) => {
35
+ const next = new Map;
36
+ const added = [];
37
+ const changed = [];
38
+ for (const row of rows) {
39
+ const rowKey = key(row);
40
+ next.set(rowKey, row);
41
+ const previous = set.get(rowKey);
42
+ if (previous === undefined) {
43
+ added.push(row);
44
+ } else if (!equals(previous, row)) {
45
+ changed.push(row);
46
+ }
47
+ }
48
+ const removed = [];
49
+ for (const [rowKey, previous] of set) {
50
+ if (!next.has(rowKey)) {
51
+ removed.push(previous);
52
+ }
53
+ }
54
+ set.clear();
55
+ for (const [rowKey, row] of next) {
56
+ set.set(rowKey, row);
57
+ }
58
+ return { added, removed, changed };
59
+ },
60
+ apply: ({ op, row }) => {
61
+ const rowKey = key(row);
62
+ const existing = set.get(rowKey);
63
+ if (op === "delete") {
64
+ if (existing === undefined) {
65
+ return emptyDiff();
66
+ }
67
+ set.delete(rowKey);
68
+ return { added: [], removed: [existing], changed: [] };
69
+ }
70
+ if (match(row)) {
71
+ set.set(rowKey, row);
72
+ return existing === undefined ? { added: [row], removed: [], changed: [] } : { added: [], removed: [], changed: [row] };
73
+ }
74
+ if (existing !== undefined) {
75
+ set.delete(rowKey);
76
+ return { added: [], removed: [existing], changed: [] };
77
+ }
78
+ return emptyDiff();
79
+ },
80
+ rows: () => [...set.values()],
81
+ size: () => set.size
82
+ };
83
+ };
84
+ // src/engine/aggregate.ts
85
+ var newGroupState = () => ({
86
+ count: 0,
87
+ sum: 0,
88
+ valueCounts: new Map,
89
+ min: undefined,
90
+ max: undefined
91
+ });
92
+ var recomputeExtremes = (state) => {
93
+ if (state.valueCounts.size === 0) {
94
+ state.min = undefined;
95
+ state.max = undefined;
96
+ return;
97
+ }
98
+ let min = Infinity;
99
+ let max = -Infinity;
100
+ for (const value of state.valueCounts.keys()) {
101
+ if (value < min) {
102
+ min = value;
103
+ }
104
+ if (value > max) {
105
+ max = value;
106
+ }
107
+ }
108
+ state.min = min;
109
+ state.max = max;
110
+ };
111
+ var summarize = (group, state) => ({
112
+ group,
113
+ count: state.count,
114
+ sum: state.sum,
115
+ avg: state.count > 0 ? state.sum / state.count : 0,
116
+ min: state.min,
117
+ max: state.max
118
+ });
119
+ var createAggregate = (options) => {
120
+ const { key, groupBy, value } = options;
121
+ const groups = new Map;
122
+ const contributions = new Map;
123
+ const add = (group, contribution) => {
124
+ let state = groups.get(group);
125
+ if (state === undefined) {
126
+ state = newGroupState();
127
+ groups.set(group, state);
128
+ }
129
+ state.count += 1;
130
+ if (contribution === undefined) {
131
+ return;
132
+ }
133
+ state.sum += contribution;
134
+ state.valueCounts.set(contribution, (state.valueCounts.get(contribution) ?? 0) + 1);
135
+ state.min = state.min === undefined ? contribution : Math.min(state.min, contribution);
136
+ state.max = state.max === undefined ? contribution : Math.max(state.max, contribution);
137
+ };
138
+ const remove = (group, contribution) => {
139
+ const state = groups.get(group);
140
+ if (state === undefined) {
141
+ return;
142
+ }
143
+ state.count -= 1;
144
+ if (contribution !== undefined) {
145
+ state.sum -= contribution;
146
+ const remaining = (state.valueCounts.get(contribution) ?? 0) - 1;
147
+ if (remaining <= 0) {
148
+ state.valueCounts.delete(contribution);
149
+ if (contribution === state.min || contribution === state.max) {
150
+ recomputeExtremes(state);
151
+ }
152
+ } else {
153
+ state.valueCounts.set(contribution, remaining);
154
+ }
155
+ }
156
+ if (state.count <= 0) {
157
+ groups.delete(group);
158
+ }
159
+ };
160
+ const apply = (change) => {
161
+ const rowKey = key(change.row);
162
+ const previous = contributions.get(rowKey);
163
+ if (change.op === "delete") {
164
+ if (previous !== undefined) {
165
+ remove(previous.group, previous.value);
166
+ contributions.delete(rowKey);
167
+ }
168
+ return;
169
+ }
170
+ const group = groupBy ? groupBy(change.row) : "";
171
+ const contribution = value ? value(change.row) : undefined;
172
+ if (previous !== undefined) {
173
+ remove(previous.group, previous.value);
174
+ }
175
+ add(group, contribution);
176
+ contributions.set(rowKey, { group, value: contribution });
177
+ };
178
+ return {
179
+ hydrate: (rows) => {
180
+ groups.clear();
181
+ contributions.clear();
182
+ for (const row of rows) {
183
+ apply({ op: "insert", row });
184
+ }
185
+ },
186
+ apply,
187
+ groups: () => [...groups.entries()].map(([group, state]) => summarize(group, state)),
188
+ group: (group) => {
189
+ const state = groups.get(group);
190
+ return state === undefined ? undefined : summarize(group, state);
191
+ }
192
+ };
193
+ };
194
+ // src/engine/equiJoin.ts
195
+ var shallowEqual2 = (a, b) => {
196
+ if (a === b) {
197
+ return true;
198
+ }
199
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
200
+ return false;
201
+ }
202
+ const aKeys = Object.keys(a);
203
+ const bKeys = Object.keys(b);
204
+ return aKeys.length === bKeys.length && aKeys.every((key) => a[key] === b[key]);
205
+ };
206
+ var addToIndex = (index, joinValue, key) => {
207
+ let bucket = index.get(joinValue);
208
+ if (bucket === undefined) {
209
+ bucket = new Set;
210
+ index.set(joinValue, bucket);
211
+ }
212
+ bucket.add(key);
213
+ };
214
+ var removeFromIndex = (index, joinValue, key) => {
215
+ const bucket = index.get(joinValue);
216
+ if (bucket === undefined) {
217
+ return;
218
+ }
219
+ bucket.delete(key);
220
+ if (bucket.size === 0) {
221
+ index.delete(joinValue);
222
+ }
223
+ };
224
+ var createEquiJoin = (options) => {
225
+ const { leftKey, rightKey, leftOn, rightOn, select, selectUnmatched } = options;
226
+ const equals = options.equals ?? shallowEqual2;
227
+ const lefts = new Map;
228
+ const rights = new Map;
229
+ const leftByJoin = new Map;
230
+ const rightByJoin = new Map;
231
+ const output = new Map;
232
+ const outByLeft = new Map;
233
+ const outKey = (lk, rk) => `${lk} ${rk}`;
234
+ const unmatchedKey = (lk) => `${lk} ~`;
235
+ const leftOutputs = (lk, left) => {
236
+ const result = new Map;
237
+ const rks = rightByJoin.get(leftOn(left));
238
+ if (rks !== undefined && rks.size > 0) {
239
+ for (const rk of rks) {
240
+ const right = rights.get(rk);
241
+ if (right !== undefined) {
242
+ result.set(outKey(lk, rk), select(left, right));
243
+ }
244
+ }
245
+ } else if (selectUnmatched !== undefined) {
246
+ result.set(unmatchedKey(lk), selectUnmatched(left));
247
+ }
248
+ return result;
249
+ };
250
+ const reconcileLeft = (lk, after) => {
251
+ const before = outByLeft.get(lk) ?? new Set;
252
+ const added = [];
253
+ const removed = [];
254
+ const changed = [];
255
+ for (const [ok, value] of after) {
256
+ const previous = output.get(ok);
257
+ if (previous === undefined) {
258
+ added.push(value);
259
+ } else if (!equals(previous, value)) {
260
+ changed.push(value);
261
+ }
262
+ output.set(ok, value);
263
+ }
264
+ for (const ok of before) {
265
+ if (!after.has(ok)) {
266
+ const previous = output.get(ok);
267
+ if (previous !== undefined) {
268
+ removed.push(previous);
269
+ output.delete(ok);
270
+ }
271
+ }
272
+ }
273
+ if (after.size === 0) {
274
+ outByLeft.delete(lk);
275
+ } else {
276
+ outByLeft.set(lk, new Set(after.keys()));
277
+ }
278
+ return { added, removed, changed };
279
+ };
280
+ const mergeInto = (target, diff) => {
281
+ target.added.push(...diff.added);
282
+ target.removed.push(...diff.removed);
283
+ target.changed.push(...diff.changed);
284
+ };
285
+ return {
286
+ hydrate: (left, right) => {
287
+ lefts.clear();
288
+ rights.clear();
289
+ leftByJoin.clear();
290
+ rightByJoin.clear();
291
+ output.clear();
292
+ outByLeft.clear();
293
+ for (const right_ of right) {
294
+ const rk = rightKey(right_);
295
+ rights.set(rk, right_);
296
+ addToIndex(rightByJoin, rightOn(right_), rk);
297
+ }
298
+ for (const left_ of left) {
299
+ const lk = leftKey(left_);
300
+ lefts.set(lk, left_);
301
+ addToIndex(leftByJoin, leftOn(left_), lk);
302
+ const outs = leftOutputs(lk, left_);
303
+ for (const [ok, value] of outs) {
304
+ output.set(ok, value);
305
+ }
306
+ if (outs.size > 0) {
307
+ outByLeft.set(lk, new Set(outs.keys()));
308
+ }
309
+ }
310
+ },
311
+ applyLeft: ({ op, row }) => {
312
+ const lk = leftKey(row);
313
+ const existing = lefts.get(lk);
314
+ if (existing !== undefined) {
315
+ removeFromIndex(leftByJoin, leftOn(existing), lk);
316
+ }
317
+ if (op === "delete") {
318
+ lefts.delete(lk);
319
+ } else {
320
+ lefts.set(lk, row);
321
+ addToIndex(leftByJoin, leftOn(row), lk);
322
+ }
323
+ const after = op === "delete" ? new Map : leftOutputs(lk, row);
324
+ return reconcileLeft(lk, after);
325
+ },
326
+ applyRight: ({ op, row }) => {
327
+ const rk = rightKey(row);
328
+ const existing = rights.get(rk);
329
+ const affected = new Set;
330
+ const addAffected = (joinValue) => {
331
+ for (const lk of leftByJoin.get(joinValue) ?? []) {
332
+ affected.add(lk);
333
+ }
334
+ };
335
+ if (existing !== undefined) {
336
+ addAffected(rightOn(existing));
337
+ removeFromIndex(rightByJoin, rightOn(existing), rk);
338
+ }
339
+ if (op === "delete") {
340
+ rights.delete(rk);
341
+ } else {
342
+ rights.set(rk, row);
343
+ addToIndex(rightByJoin, rightOn(row), rk);
344
+ addAffected(rightOn(row));
345
+ }
346
+ const diff = { added: [], removed: [], changed: [] };
347
+ for (const lk of affected) {
348
+ const left = lefts.get(lk);
349
+ if (left !== undefined) {
350
+ mergeInto(diff, reconcileLeft(lk, leftOutputs(lk, left)));
351
+ }
352
+ }
353
+ return diff;
354
+ },
355
+ rows: () => [...output.values()],
356
+ size: () => output.size
357
+ };
358
+ };
359
+ // src/engine/dataflow.ts
360
+ var shallowEqual3 = (a, b) => {
361
+ if (a === b) {
362
+ return true;
363
+ }
364
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
365
+ return false;
366
+ }
367
+ const aKeys = Object.keys(a);
368
+ const bKeys = Object.keys(b);
369
+ return aKeys.length === bKeys.length && aKeys.every((key) => a[key] === b[key]);
370
+ };
371
+ var fromRowChange = (change, key) => ({
372
+ op: change.op === "delete" ? "delete" : "upsert",
373
+ key: key(change.row),
374
+ row: change.row
375
+ });
376
+ var filterOp = (predicate) => ({
377
+ push: (changes) => changes.map((change) => change.op === "upsert" && !predicate(change.row) ? { op: "delete", key: change.key, row: change.row } : change)
378
+ });
379
+ var mapOp = (transform, rekey) => ({
380
+ push: (changes) => changes.map((change) => {
381
+ const row = transform(change.row);
382
+ return {
383
+ op: change.op,
384
+ key: rekey ? rekey(row) : change.key,
385
+ row
386
+ };
387
+ })
388
+ });
389
+ var chain = (a, b) => ({
390
+ push: (changes) => b.push(a.push(changes))
391
+ });
392
+ var aggregateOp = (options) => {
393
+ const aggregate = createAggregate(options);
394
+ const groupOf = new Map;
395
+ return {
396
+ push: (changes) => {
397
+ const affected = new Set;
398
+ for (const change of changes) {
399
+ const inputKey = options.key(change.row);
400
+ const previousGroup = groupOf.get(inputKey);
401
+ if (previousGroup !== undefined) {
402
+ affected.add(previousGroup);
403
+ }
404
+ if (change.op === "delete") {
405
+ aggregate.apply({ op: "delete", row: change.row });
406
+ groupOf.delete(inputKey);
407
+ } else {
408
+ const group = options.groupBy ? options.groupBy(change.row) : "";
409
+ affected.add(group);
410
+ aggregate.apply({ op: "update", row: change.row });
411
+ groupOf.set(inputKey, group);
412
+ }
413
+ }
414
+ const out = [];
415
+ for (const group of affected) {
416
+ const summary = aggregate.group(group);
417
+ if (summary === undefined) {
418
+ out.push({
419
+ op: "delete",
420
+ key: group,
421
+ row: {
422
+ group,
423
+ count: 0,
424
+ sum: 0,
425
+ avg: 0,
426
+ min: undefined,
427
+ max: undefined
428
+ }
429
+ });
430
+ } else {
431
+ out.push({ op: "upsert", key: group, row: summary });
432
+ }
433
+ }
434
+ return out;
435
+ }
436
+ };
437
+ };
438
+ var orderByOp = (options) => {
439
+ const { key, compare } = options;
440
+ const offset = options.offset ?? 0;
441
+ const limit = options.limit ?? Number.POSITIVE_INFINITY;
442
+ const all = new Map;
443
+ let window = new Map;
444
+ return {
445
+ push: (changes) => {
446
+ for (const change of changes) {
447
+ if (change.op === "delete") {
448
+ all.delete(change.key);
449
+ } else {
450
+ all.set(change.key, change.row);
451
+ }
452
+ }
453
+ const windowed = [...all.values()].sort(compare).slice(offset, offset + limit);
454
+ const next = new Map;
455
+ for (const row of windowed) {
456
+ next.set(key(row), row);
457
+ }
458
+ const out = [];
459
+ for (const [rowKey, row] of window) {
460
+ if (!next.has(rowKey)) {
461
+ out.push({ op: "delete", key: rowKey, row });
462
+ }
463
+ }
464
+ for (const [rowKey, row] of next) {
465
+ out.push({ op: "upsert", key: rowKey, row });
466
+ }
467
+ window = next;
468
+ return out;
469
+ }
470
+ };
471
+ };
472
+ var joinNode = (options) => {
473
+ const join = createEquiJoin(options);
474
+ const key = options.key;
475
+ const toChanges = (diff) => {
476
+ const changes = [];
477
+ for (const row of diff.removed) {
478
+ changes.push({ op: "delete", key: key(row), row });
479
+ }
480
+ for (const row of diff.added) {
481
+ changes.push({ op: "upsert", key: key(row), row });
482
+ }
483
+ for (const row of diff.changed) {
484
+ changes.push({ op: "upsert", key: key(row), row });
485
+ }
486
+ return changes;
487
+ };
488
+ const asRowChange = (change) => ({
489
+ op: change.op === "delete" ? "delete" : "update",
490
+ row: change.row
491
+ });
492
+ return {
493
+ hydrate: (left, right) => join.hydrate(left, right),
494
+ pushLeft: (changes) => changes.flatMap((change) => toChanges(join.applyLeft(asRowChange(change)))),
495
+ pushRight: (changes) => changes.flatMap((change) => toChanges(join.applyRight(asRowChange(change)))),
496
+ rows: () => join.rows()
497
+ };
498
+ };
499
+ var materialize = (key, equals = shallowEqual3) => {
500
+ const set = new Map;
501
+ return {
502
+ hydrate: (rows) => {
503
+ set.clear();
504
+ for (const row of rows) {
505
+ set.set(key(row), row);
506
+ }
507
+ },
508
+ apply: (changes) => {
509
+ const added = [];
510
+ const removed = [];
511
+ const changed = [];
512
+ for (const change of changes) {
513
+ if (change.op === "delete") {
514
+ const previous2 = set.get(change.key);
515
+ if (previous2 !== undefined) {
516
+ removed.push(previous2);
517
+ set.delete(change.key);
518
+ }
519
+ continue;
520
+ }
521
+ const previous = set.get(change.key);
522
+ set.set(change.key, change.row);
523
+ if (previous === undefined) {
524
+ added.push(change.row);
525
+ } else if (!equals(previous, change.row)) {
526
+ changed.push(change.row);
527
+ }
528
+ }
529
+ return { added, removed, changed };
530
+ },
531
+ rows: () => [...set.values()]
532
+ };
533
+ };
534
+ // src/engine/pollingSource.ts
535
+ var OP_BY_NAME = {
536
+ insert: "insert",
537
+ INSERT: "insert",
538
+ update: "update",
539
+ UPDATE: "update",
540
+ delete: "delete",
541
+ DELETE: "delete"
542
+ };
543
+ var parseOutboxRow = (row) => {
544
+ if (typeof row.tbl !== "string") {
545
+ return;
546
+ }
547
+ const op = OP_BY_NAME[row.op];
548
+ if (op === undefined) {
549
+ return;
550
+ }
551
+ let payload = row.payload;
552
+ if (typeof payload === "string") {
553
+ try {
554
+ payload = JSON.parse(payload);
555
+ } catch {
556
+ return;
557
+ }
558
+ }
559
+ if (typeof payload !== "object" || payload === null) {
560
+ return;
561
+ }
562
+ return { table: row.tbl, change: { op, row: payload } };
563
+ };
564
+ var createPollingChangeSource = (options) => {
565
+ const intervalMs = options.intervalMs ?? 1000;
566
+ const parse = options.parse ?? parseOutboxRow;
567
+ const onError = options.onError ?? ((error) => {
568
+ console.warn("[sync] polling change source error:", error);
569
+ });
570
+ let cursor = options.startSeq ?? 0;
571
+ let running = false;
572
+ let timer;
573
+ const tick = async (emit) => {
574
+ if (!running) {
575
+ return;
576
+ }
577
+ try {
578
+ const rows = await options.poll(cursor);
579
+ for (const row of rows) {
580
+ const parsed = parse(row);
581
+ if (parsed !== undefined) {
582
+ await emit(parsed.table, parsed.change);
583
+ }
584
+ if (typeof row.seq === "number" && row.seq > cursor) {
585
+ cursor = row.seq;
586
+ }
587
+ }
588
+ if (rows.length > 0) {
589
+ await options.onProcessed?.(cursor);
590
+ }
591
+ } catch (error) {
592
+ onError(error);
593
+ }
594
+ if (running) {
595
+ timer = setTimeout(() => {
596
+ tick(emit);
597
+ }, intervalMs);
598
+ }
599
+ };
600
+ return {
601
+ start: async (emit) => {
602
+ if (running) {
603
+ return;
604
+ }
605
+ running = true;
606
+ await tick(emit);
607
+ },
608
+ stop: () => {
609
+ running = false;
610
+ if (timer !== undefined) {
611
+ clearTimeout(timer);
612
+ timer = undefined;
613
+ }
614
+ }
615
+ };
616
+ };
617
+ // src/engine/cluster.ts
618
+ var createInMemoryClusterBus = () => {
619
+ const listeners = new Set;
620
+ return {
621
+ publish: (message) => {
622
+ for (const listener of listeners) {
623
+ listener(message);
624
+ }
625
+ },
626
+ subscribe: (onMessage) => {
627
+ listeners.add(onMessage);
628
+ return () => {
629
+ listeners.delete(onMessage);
630
+ };
631
+ }
632
+ };
633
+ };
634
+ // src/engine/presence.ts
635
+ var createPresenceHub = () => {
636
+ const rooms = new Map;
637
+ const roomMembers = (room) => {
638
+ const members = rooms.get(room);
639
+ if (members === undefined) {
640
+ return [];
641
+ }
642
+ return [...members].map(([id, member]) => ({
643
+ id,
644
+ state: member.state
645
+ }));
646
+ };
647
+ const notify = (room, diff, exceptId) => {
648
+ const members = rooms.get(room);
649
+ if (members === undefined) {
650
+ return;
651
+ }
652
+ for (const [id, member] of members) {
653
+ if (id !== exceptId) {
654
+ member.onDiff(diff);
655
+ }
656
+ }
657
+ };
658
+ return {
659
+ join: (room, memberId, state, onDiff) => {
660
+ let members = rooms.get(room);
661
+ if (members === undefined) {
662
+ members = new Map;
663
+ rooms.set(room, members);
664
+ }
665
+ members.set(memberId, {
666
+ state,
667
+ onDiff
668
+ });
669
+ notify(room, { joined: [{ id: memberId, state }], updated: [], left: [] }, memberId);
670
+ const snapshot = roomMembers(room);
671
+ return {
672
+ members: snapshot,
673
+ set: (next) => {
674
+ const current = rooms.get(room)?.get(memberId);
675
+ if (current === undefined) {
676
+ return;
677
+ }
678
+ current.state = next;
679
+ notify(room, {
680
+ joined: [],
681
+ updated: [{ id: memberId, state: next }],
682
+ left: []
683
+ }, memberId);
684
+ },
685
+ leave: () => {
686
+ const roomNow = rooms.get(room);
687
+ if (roomNow?.delete(memberId) !== true) {
688
+ return;
689
+ }
690
+ notify(room, { joined: [], updated: [], left: [memberId] }, memberId);
691
+ if (roomNow.size === 0) {
692
+ rooms.delete(room);
693
+ }
694
+ }
695
+ };
696
+ },
697
+ members: (room) => roomMembers(room),
698
+ count: (room) => rooms.get(room)?.size ?? 0
699
+ };
700
+ };
701
+ // src/engine/collection.ts
702
+ var defineCollection = (definition) => definition;
703
+ var defineJoinCollection = (definition) => ({
704
+ ...definition,
705
+ kind: "join"
706
+ });
707
+ // src/engine/reactive.ts
708
+ var defineReactiveQuery = (definition) => ({ ...definition, kind: "reactive" });
709
+ // src/engine/graph.ts
710
+ var PLANS = new WeakMap;
711
+ var planTables = (plan) => {
712
+ const tables = [plan.source.table];
713
+ for (const step of plan.steps) {
714
+ if (step.kind === "join") {
715
+ tables.push(...planTables(step.rightPlan));
716
+ }
717
+ }
718
+ return [...new Set(tables)];
719
+ };
720
+ var instantiateStream = (source, steps, params, ctx) => {
721
+ const stages = [];
722
+ let currentKey = source.key;
723
+ for (const step of steps) {
724
+ if (step.kind === "filter") {
725
+ const predicate = step.predicate;
726
+ stages.push({
727
+ kind: "op",
728
+ op: filterOp((row) => predicate(row, params, ctx))
729
+ });
730
+ } else if (step.kind === "map") {
731
+ stages.push({ kind: "op", op: mapOp(step.transform, step.rekey) });
732
+ if (step.rekey) {
733
+ currentKey = step.rekey;
734
+ }
735
+ } else if (step.kind === "join") {
736
+ const right = instantiateStream(step.rightPlan.source, step.rightPlan.steps, params, ctx);
737
+ stages.push({
738
+ kind: "join",
739
+ node: joinNode({
740
+ leftKey: currentKey,
741
+ rightKey: right.outKey,
742
+ leftOn: step.on,
743
+ rightOn: step.rightOn,
744
+ select: step.select,
745
+ selectUnmatched: step.selectUnmatched,
746
+ key: step.key
747
+ }),
748
+ right
749
+ });
750
+ currentKey = step.key;
751
+ } else if (step.kind === "aggregate") {
752
+ stages.push({
753
+ kind: "op",
754
+ op: aggregateOp({
755
+ key: step.key,
756
+ groupBy: step.groupBy,
757
+ value: step.value
758
+ })
759
+ });
760
+ currentKey = (group) => group.group;
761
+ } else {
762
+ stages.push({
763
+ kind: "op",
764
+ op: orderByOp({
765
+ key: step.key,
766
+ compare: step.compare,
767
+ limit: step.limit,
768
+ offset: step.offset
769
+ })
770
+ });
771
+ currentKey = step.key;
772
+ }
773
+ }
774
+ const propagate = (changes, fromStage, side) => {
775
+ let cs = changes;
776
+ for (let i = fromStage;i < stages.length; i += 1) {
777
+ const stage = stages[i];
778
+ if (stage.kind === "join") {
779
+ cs = i === fromStage && side === "right" ? stage.node.pushRight(cs) : stage.node.pushLeft(cs);
780
+ } else {
781
+ cs = stage.op.push(cs);
782
+ }
783
+ }
784
+ return cs;
785
+ };
786
+ const sourceChange = (change) => {
787
+ const key = source.key(change.row);
788
+ if (change.op === "delete" || source.match !== undefined && !source.match(change.row, params, ctx)) {
789
+ return { op: "delete", key, row: change.row };
790
+ }
791
+ return { op: "upsert", key, row: change.row };
792
+ };
793
+ const entries = new Map;
794
+ const addEntry = (table, entry) => {
795
+ const list = entries.get(table);
796
+ if (list === undefined) {
797
+ entries.set(table, [entry]);
798
+ } else {
799
+ list.push(entry);
800
+ }
801
+ };
802
+ addEntry(source.table, {
803
+ stageIndex: 0,
804
+ side: "left",
805
+ produce: (change) => [sourceChange(change)]
806
+ });
807
+ stages.forEach((stage, index) => {
808
+ if (stage.kind === "join") {
809
+ for (const table of stage.right.tables) {
810
+ addEntry(table, {
811
+ stageIndex: index,
812
+ side: "right",
813
+ produce: (change) => stage.right.applyStream(table, change)
814
+ });
815
+ }
816
+ }
817
+ });
818
+ return {
819
+ tables: planTables({ source, steps }),
820
+ outKey: currentKey,
821
+ hydrateStream: async () => {
822
+ for (let i = 0;i < stages.length; i += 1) {
823
+ const stage = stages[i];
824
+ if (stage.kind === "join") {
825
+ propagate(await stage.right.hydrateStream(), i, "right");
826
+ }
827
+ }
828
+ const rootRows = [...await source.hydrate(params, ctx)];
829
+ return propagate(rootRows.map((row) => ({
830
+ op: "upsert",
831
+ key: source.key(row),
832
+ row
833
+ })), 0, "left");
834
+ },
835
+ applyStream: (table, change) => {
836
+ const list = entries.get(table);
837
+ if (list === undefined) {
838
+ return [];
839
+ }
840
+ const out = [];
841
+ for (const entry of list) {
842
+ out.push(...propagate(entry.produce(change), entry.stageIndex, entry.side));
843
+ }
844
+ return out;
845
+ }
846
+ };
847
+ };
848
+ var instantiate = (source, steps, params, ctx) => {
849
+ const graph = instantiateStream(source, steps, params, ctx);
850
+ const sink = materialize(graph.outKey);
851
+ return {
852
+ tables: graph.tables,
853
+ hydrate: async () => {
854
+ sink.apply(await graph.hydrateStream());
855
+ return sink.rows();
856
+ },
857
+ applyChange: (table, change) => sink.apply(graph.applyStream(table, change))
858
+ };
859
+ };
860
+ var makeQuery = (source, steps) => {
861
+ const addJoin = (right, options) => {
862
+ const rightPlan = PLANS.get(right) ?? {
863
+ source: right,
864
+ steps: []
865
+ };
866
+ return makeQuery(source, [
867
+ ...steps,
868
+ { kind: "join", rightPlan, ...options }
869
+ ]);
870
+ };
871
+ const queryInstance = {
872
+ filter: (predicate) => makeQuery(source, [...steps, { kind: "filter", predicate }]),
873
+ map: (transform, rekey) => makeQuery(source, [...steps, { kind: "map", transform, rekey }]),
874
+ join: (right, options) => addJoin(right, options),
875
+ leftJoin: (right, options) => addJoin(right, options),
876
+ groupBy: (options) => makeQuery(source, [...steps, { kind: "aggregate", ...options }]),
877
+ orderBy: (options) => makeQuery(source, [...steps, { kind: "orderBy", ...options }]),
878
+ tables: () => planTables({ source, steps }),
879
+ instantiate: (params, ctx) => instantiate(source, steps, params, ctx)
880
+ };
881
+ PLANS.set(queryInstance, { source, steps });
882
+ return queryInstance;
883
+ };
884
+ var query = (source) => makeQuery(source, []);
885
+ var defineGraphCollection = (definition) => ({ ...definition, kind: "graph" });
886
+ // src/engine/mutation.ts
887
+ var defineMutation = (definition) => definition;
888
+ // src/engine/syncEngine.ts
889
+ class UnauthorizedError extends Error {
890
+ constructor(subject) {
891
+ super(`Not authorized: ${subject}`);
892
+ this.name = "UnauthorizedError";
893
+ }
894
+ }
895
+ var defaultKey = (row) => row.id;
896
+ var shallowEqual4 = (a, b) => {
897
+ if (a === b) {
898
+ return true;
899
+ }
900
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
901
+ return false;
902
+ }
903
+ const aKeys = Object.keys(a);
904
+ const bKeys = Object.keys(b);
905
+ return aKeys.length === bKeys.length && aKeys.every((k) => a[k] === b[k]);
906
+ };
907
+ var createSyncEngine = (options = {}) => {
908
+ const registry = new Map;
909
+ const mutations = new Map;
910
+ const writers = new Map;
911
+ const readers = new Map;
912
+ const reactiveSubs = new Set;
913
+ const active = new Map;
914
+ const tableIndex = new Map;
915
+ const changeLogSize = options.changeLogSize ?? 1024;
916
+ const changeLog = [];
917
+ let version = 0;
918
+ const runInTransaction = options.transaction;
919
+ const instanceId = globalThis.crypto?.randomUUID?.() ?? `i${Math.random()}`;
920
+ let clusterBus;
921
+ const broadcast = (changes) => {
922
+ if (clusterBus !== undefined && changes.length > 0) {
923
+ clusterBus.publish({ changes, origin: instanceId });
924
+ }
925
+ };
926
+ const subsFor = (collection) => {
927
+ let set = active.get(collection);
928
+ if (set === undefined) {
929
+ set = new Set;
930
+ active.set(collection, set);
931
+ }
932
+ return set;
933
+ };
934
+ const addTableIndex = (table, name) => {
935
+ let set = tableIndex.get(table);
936
+ if (set === undefined) {
937
+ set = new Set;
938
+ tableIndex.set(table, set);
939
+ }
940
+ set.add(name);
941
+ };
942
+ const sideChange = (change, match) => change.op !== "delete" && match !== undefined && !match(change.row) ? { op: "delete", row: change.row } : change;
943
+ const EMPTY_DIFF = {
944
+ added: [],
945
+ removed: [],
946
+ changed: []
947
+ };
948
+ const subscriptionDiff = async (subscription, table, change) => {
949
+ if (subscription.kind === "graph") {
950
+ return subscription.instance.applyChange(table, change);
951
+ }
952
+ if (subscription.kind === "join") {
953
+ const js = subscription.join;
954
+ if (table === js.leftTable) {
955
+ return js.op.applyLeft(sideChange(change, js.leftMatch));
956
+ }
957
+ if (table === js.rightTable) {
958
+ return js.op.applyRight(sideChange(change, js.rightMatch));
959
+ }
960
+ return EMPTY_DIFF;
961
+ }
962
+ if (subscription.kind === "reactive") {
963
+ return EMPTY_DIFF;
964
+ }
965
+ if (subscription.incremental) {
966
+ try {
967
+ return subscription.view.apply(change);
968
+ } catch {
969
+ return subscription.view.reset(await subscription.rehydrate());
970
+ }
971
+ }
972
+ return subscription.view.reset(await subscription.rehydrate());
973
+ };
974
+ const subscriptionsForTable = function* (table) {
975
+ const names = tableIndex.get(table);
976
+ if (names === undefined) {
977
+ return;
978
+ }
979
+ for (const name of names) {
980
+ const set = active.get(name);
981
+ if (set === undefined) {
982
+ continue;
983
+ }
984
+ yield* set;
985
+ }
986
+ };
987
+ const mergeViewDiffs = (diffs, key) => {
988
+ const net = new Map;
989
+ for (const diff of diffs) {
990
+ for (const row of diff.removed) {
991
+ const previous = net.get(key(row));
992
+ if (previous?.state === "added") {
993
+ net.delete(key(row));
994
+ } else {
995
+ net.set(key(row), { state: "removed", row });
996
+ }
997
+ }
998
+ for (const row of diff.added) {
999
+ const previous = net.get(key(row));
1000
+ net.set(key(row), {
1001
+ state: previous?.state === "removed" ? "changed" : "added",
1002
+ row
1003
+ });
1004
+ }
1005
+ for (const row of diff.changed) {
1006
+ const previous = net.get(key(row));
1007
+ net.set(key(row), {
1008
+ state: previous?.state === "added" ? "added" : "changed",
1009
+ row
1010
+ });
1011
+ }
1012
+ }
1013
+ const added = [];
1014
+ const changed = [];
1015
+ const removed = [];
1016
+ for (const { state, row } of net.values()) {
1017
+ if (state === "added") {
1018
+ added.push(row);
1019
+ } else if (state === "changed") {
1020
+ changed.push(row);
1021
+ } else {
1022
+ removed.push(row);
1023
+ }
1024
+ }
1025
+ return { added, changed, removed };
1026
+ };
1027
+ const depKey = (table, key) => `${table} ${key}`;
1028
+ const changedKeyFor = (table, change) => readers.get(table)?.key?.(change.row);
1029
+ const makeReadHandle = (ctx, readTables, readKeys, rangeDeps) => {
1030
+ const readerFor = (table) => {
1031
+ const reader = readers.get(table);
1032
+ if (reader === undefined) {
1033
+ throw new Error(`No reader registered for table "${table}" \u2014 register one with engine.registerReader`);
1034
+ }
1035
+ return reader;
1036
+ };
1037
+ return {
1038
+ all: async (table) => {
1039
+ readTables.add(table);
1040
+ return [...await readerFor(table).all(ctx)];
1041
+ },
1042
+ get: async (table, key) => {
1043
+ const reader = readerFor(table);
1044
+ if (reader.get === undefined) {
1045
+ throw new Error(`Reader for table "${table}" has no get(); use db.all() or add get`);
1046
+ }
1047
+ if (reader.key !== undefined) {
1048
+ readKeys.add(depKey(table, key));
1049
+ } else {
1050
+ readTables.add(table);
1051
+ }
1052
+ return await reader.get(key, ctx);
1053
+ },
1054
+ where: async (table, predicate) => {
1055
+ const reader = readerFor(table);
1056
+ const matched = [...await reader.all(ctx)].filter(predicate);
1057
+ if (reader.key !== undefined) {
1058
+ const key = reader.key;
1059
+ rangeDeps.push({
1060
+ table,
1061
+ predicate,
1062
+ keys: new Set(matched.map(key))
1063
+ });
1064
+ } else {
1065
+ readTables.add(table);
1066
+ }
1067
+ return matched;
1068
+ }
1069
+ };
1070
+ };
1071
+ const diffRerun = (sub, rows) => {
1072
+ const next = new Map;
1073
+ for (const row of rows) {
1074
+ next.set(sub.key(row), row);
1075
+ }
1076
+ const added = [];
1077
+ const removed = [];
1078
+ const changed = [];
1079
+ for (const [rowKey, row] of next) {
1080
+ const previous = sub.current.get(rowKey);
1081
+ if (previous === undefined) {
1082
+ added.push(row);
1083
+ } else if (!shallowEqual4(previous, row)) {
1084
+ changed.push(row);
1085
+ }
1086
+ }
1087
+ for (const [rowKey, row] of sub.current) {
1088
+ if (!next.has(rowKey)) {
1089
+ removed.push(row);
1090
+ }
1091
+ }
1092
+ sub.current = next;
1093
+ return { added, removed, changed };
1094
+ };
1095
+ const inRange = (dep, change) => dep.table === change.table && (change.key !== undefined && dep.keys.has(change.key) || dep.predicate(change.row));
1096
+ const isReactiveAffected = (sub, changes) => changes.some((change) => sub.readTables.has(change.table) || change.key !== undefined && sub.readKeys.has(depKey(change.table, change.key)) || sub.rangeDeps.some((dep) => inRange(dep, change)));
1097
+ const reactivePairs = async (changes) => {
1098
+ const pairs = [];
1099
+ for (const sub of reactiveSubs) {
1100
+ if (!isReactiveAffected(sub, changes)) {
1101
+ continue;
1102
+ }
1103
+ const { rows, readTables, readKeys, rangeDeps } = await sub.rerun();
1104
+ sub.readTables = readTables;
1105
+ sub.readKeys = readKeys;
1106
+ sub.rangeDeps = rangeDeps;
1107
+ const diff = diffRerun(sub, rows);
1108
+ if (!isEmptyViewDiff(diff)) {
1109
+ pairs.push([sub, diff]);
1110
+ }
1111
+ }
1112
+ return pairs;
1113
+ };
1114
+ const logChange = (changeVersion, entry) => {
1115
+ changeLog.push(entry);
1116
+ if (changeLog.length > changeLogSize) {
1117
+ changeLog.shift();
1118
+ }
1119
+ };
1120
+ const applyChange = async (table, change, shouldBroadcast = true) => {
1121
+ version += 1;
1122
+ const changeVersion = version;
1123
+ logChange(changeVersion, { version: changeVersion, table, change });
1124
+ const emissions = [];
1125
+ for (const subscription of subscriptionsForTable(table)) {
1126
+ const diff = await subscriptionDiff(subscription, table, change);
1127
+ if (!isEmptyViewDiff(diff)) {
1128
+ emissions.push([subscription, diff]);
1129
+ }
1130
+ }
1131
+ emissions.push(...await reactivePairs([
1132
+ { table, key: changedKeyFor(table, change), row: change.row }
1133
+ ]));
1134
+ for (const [subscription, diff] of emissions) {
1135
+ subscription.onDiff(diff, changeVersion);
1136
+ }
1137
+ if (shouldBroadcast) {
1138
+ broadcast([{ table, change }]);
1139
+ }
1140
+ };
1141
+ const applyChangeBatch = async (changes, shouldBroadcast = true) => {
1142
+ if (changes.length === 0) {
1143
+ return;
1144
+ }
1145
+ version += 1;
1146
+ const batchVersion = version;
1147
+ const perSubscription = new Map;
1148
+ const reactiveChanges = [];
1149
+ for (const { table, change } of changes) {
1150
+ logChange(batchVersion, { version: batchVersion, table, change });
1151
+ reactiveChanges.push({
1152
+ table,
1153
+ key: changedKeyFor(table, change),
1154
+ row: change.row
1155
+ });
1156
+ for (const subscription of subscriptionsForTable(table)) {
1157
+ const diff = await subscriptionDiff(subscription, table, change);
1158
+ const list = perSubscription.get(subscription);
1159
+ if (list === undefined) {
1160
+ perSubscription.set(subscription, [diff]);
1161
+ } else {
1162
+ list.push(diff);
1163
+ }
1164
+ }
1165
+ }
1166
+ const emissions = [];
1167
+ for (const [subscription, diffs] of perSubscription) {
1168
+ const merged = diffs.length === 1 ? diffs[0] : mergeViewDiffs(diffs, subscription.key);
1169
+ if (!isEmptyViewDiff(merged)) {
1170
+ emissions.push([subscription, merged]);
1171
+ }
1172
+ }
1173
+ emissions.push(...await reactivePairs(reactiveChanges));
1174
+ for (const [subscription, diff] of emissions) {
1175
+ subscription.onDiff(diff, batchVersion);
1176
+ }
1177
+ if (shouldBroadcast) {
1178
+ broadcast(changes);
1179
+ }
1180
+ };
1181
+ const canResume = (since, incremental) => {
1182
+ if (!incremental) {
1183
+ return false;
1184
+ }
1185
+ if (since >= version) {
1186
+ return true;
1187
+ }
1188
+ const oldest = changeLog[0];
1189
+ return oldest !== undefined && oldest.version <= since + 1;
1190
+ };
1191
+ const buildCatchup = (since, tables, key, match) => {
1192
+ const latest = new Map;
1193
+ for (const entry of changeLog) {
1194
+ if (entry.version <= since || !tables.includes(entry.table)) {
1195
+ continue;
1196
+ }
1197
+ const row = entry.change.row;
1198
+ const present = entry.change.op !== "delete" && match(row) ? "upsert" : "remove";
1199
+ latest.set(key(row), { op: present, row });
1200
+ }
1201
+ const changed = [];
1202
+ const removed = [];
1203
+ for (const { op, row } of latest.values()) {
1204
+ (op === "upsert" ? changed : removed).push(row);
1205
+ }
1206
+ return { added: [], removed, changed };
1207
+ };
1208
+ const subscribeJoin = async (collection, definition, params, ctx, onDiff, set) => {
1209
+ if (definition.authorize !== undefined) {
1210
+ const allowed = await definition.authorize(params, ctx);
1211
+ if (!allowed) {
1212
+ throw new UnauthorizedError(`subscribe to collection "${collection}"`);
1213
+ }
1214
+ }
1215
+ const { left, right } = definition;
1216
+ const op = createEquiJoin({
1217
+ leftKey: left.key,
1218
+ rightKey: right.key,
1219
+ leftOn: left.on,
1220
+ rightOn: right.on,
1221
+ select: definition.select
1222
+ });
1223
+ op.hydrate([...await left.hydrate(params, ctx)], [...await right.hydrate(params, ctx)]);
1224
+ const atVersion = version;
1225
+ const subscription = {
1226
+ kind: "join",
1227
+ collection,
1228
+ join: {
1229
+ op,
1230
+ leftTable: left.table,
1231
+ rightTable: right.table,
1232
+ leftMatch: left.match ? (row) => left.match(row, params, ctx) : undefined,
1233
+ rightMatch: right.match ? (row) => right.match(row, params, ctx) : undefined
1234
+ },
1235
+ key: definition.key,
1236
+ onDiff
1237
+ };
1238
+ set.add(subscription);
1239
+ return {
1240
+ initial: op.rows(),
1241
+ version: atVersion,
1242
+ unsubscribe: () => {
1243
+ set.delete(subscription);
1244
+ }
1245
+ };
1246
+ };
1247
+ const subscribeGraph = async (collection, definition, params, ctx, onDiff, set) => {
1248
+ if (definition.authorize !== undefined) {
1249
+ const allowed = await definition.authorize(params, ctx);
1250
+ if (!allowed) {
1251
+ throw new UnauthorizedError(`subscribe to collection "${collection}"`);
1252
+ }
1253
+ }
1254
+ const instance = definition.query.instantiate(params, ctx);
1255
+ const initial = await instance.hydrate();
1256
+ const atVersion = version;
1257
+ const subscription = {
1258
+ kind: "graph",
1259
+ collection,
1260
+ instance,
1261
+ key: definition.key,
1262
+ onDiff
1263
+ };
1264
+ set.add(subscription);
1265
+ return {
1266
+ initial,
1267
+ version: atVersion,
1268
+ unsubscribe: () => {
1269
+ set.delete(subscription);
1270
+ }
1271
+ };
1272
+ };
1273
+ const subscribeReactive = async (collection, definition, params, ctx, onDiff, set) => {
1274
+ if (definition.authorize !== undefined) {
1275
+ const allowed = await definition.authorize(params, ctx);
1276
+ if (!allowed) {
1277
+ throw new UnauthorizedError(`subscribe to collection "${collection}"`);
1278
+ }
1279
+ }
1280
+ const rerun = async () => {
1281
+ const readTables = new Set;
1282
+ const readKeys = new Set;
1283
+ const rangeDeps = [];
1284
+ const db = makeReadHandle(ctx, readTables, readKeys, rangeDeps);
1285
+ const rows = [...await definition.run({ ctx, db, params })];
1286
+ return { rangeDeps, readKeys, readTables, rows };
1287
+ };
1288
+ const first = await rerun();
1289
+ const current = new Map;
1290
+ for (const row of first.rows) {
1291
+ current.set(definition.key(row), row);
1292
+ }
1293
+ const atVersion = version;
1294
+ const subscription = {
1295
+ kind: "reactive",
1296
+ collection,
1297
+ key: definition.key,
1298
+ rerun,
1299
+ current,
1300
+ readTables: first.readTables,
1301
+ readKeys: first.readKeys,
1302
+ rangeDeps: first.rangeDeps,
1303
+ onDiff
1304
+ };
1305
+ set.add(subscription);
1306
+ reactiveSubs.add(subscription);
1307
+ return {
1308
+ initial: first.rows,
1309
+ version: atVersion,
1310
+ unsubscribe: () => {
1311
+ set.delete(subscription);
1312
+ reactiveSubs.delete(subscription);
1313
+ }
1314
+ };
1315
+ };
1316
+ return {
1317
+ register: (collection) => {
1318
+ registry.set(collection.name, collection);
1319
+ for (const table of collection.tables ?? [collection.name]) {
1320
+ addTableIndex(table, collection.name);
1321
+ }
1322
+ },
1323
+ registerJoin: (collection) => {
1324
+ registry.set(collection.name, collection);
1325
+ addTableIndex(collection.left.table, collection.name);
1326
+ addTableIndex(collection.right.table, collection.name);
1327
+ },
1328
+ registerGraph: (collection) => {
1329
+ registry.set(collection.name, collection);
1330
+ for (const table of collection.query.tables()) {
1331
+ addTableIndex(table, collection.name);
1332
+ }
1333
+ },
1334
+ subscribe: async ({ collection, params, ctx, onDiff, since }) => {
1335
+ const registered = registry.get(collection);
1336
+ if (registered === undefined) {
1337
+ throw new Error(`Unknown collection "${collection}"`);
1338
+ }
1339
+ const typedOnDiff = onDiff;
1340
+ const subscribeSet = subsFor(collection);
1341
+ const registeredKind = registered.kind;
1342
+ if (registeredKind === "join") {
1343
+ const joined = await subscribeJoin(collection, registered, params, ctx, typedOnDiff, subscribeSet);
1344
+ return joined;
1345
+ }
1346
+ if (registeredKind === "graph") {
1347
+ const graphed = await subscribeGraph(collection, registered, params, ctx, typedOnDiff, subscribeSet);
1348
+ return graphed;
1349
+ }
1350
+ if (registeredKind === "reactive") {
1351
+ const reactived = await subscribeReactive(collection, registered, params, ctx, typedOnDiff, subscribeSet);
1352
+ return reactived;
1353
+ }
1354
+ const definition = registered;
1355
+ if (definition.authorize !== undefined) {
1356
+ const allowed = await definition.authorize(params, ctx);
1357
+ if (!allowed) {
1358
+ throw new UnauthorizedError(`subscribe to collection "${collection}"`);
1359
+ }
1360
+ }
1361
+ const key = definition.key ?? defaultKey;
1362
+ const rehydrate = async () => definition.hydrate(params, ctx);
1363
+ const match = definition.match;
1364
+ const tables = definition.tables ?? [collection];
1365
+ const incremental = match !== undefined && tables.length === 1;
1366
+ const boundMatch = incremental ? (row) => match(row, params, ctx) : () => true;
1367
+ const view = createMaterializedView({
1368
+ key,
1369
+ match: boundMatch
1370
+ });
1371
+ const resuming = since !== undefined && canResume(since, incremental);
1372
+ view.hydrate([...await rehydrate()]);
1373
+ const atVersion = version;
1374
+ const subscription = {
1375
+ kind: "view",
1376
+ collection,
1377
+ view,
1378
+ incremental,
1379
+ rehydrate,
1380
+ key,
1381
+ onDiff: typedOnDiff
1382
+ };
1383
+ subscribeSet.add(subscription);
1384
+ const unsubscribe = () => {
1385
+ subscribeSet.delete(subscription);
1386
+ };
1387
+ if (resuming) {
1388
+ return {
1389
+ initial: [],
1390
+ catchup: buildCatchup(since, tables, key, boundMatch),
1391
+ version: atVersion,
1392
+ unsubscribe
1393
+ };
1394
+ }
1395
+ return {
1396
+ initial: view.rows(),
1397
+ version: atVersion,
1398
+ unsubscribe
1399
+ };
1400
+ },
1401
+ hydrate: async (collection, params, ctx) => {
1402
+ const definition = registry.get(collection);
1403
+ if (definition === undefined) {
1404
+ throw new Error(`Unknown collection "${collection}"`);
1405
+ }
1406
+ if (definition.authorize !== undefined) {
1407
+ const allowed = await definition.authorize(params, ctx);
1408
+ if (!allowed) {
1409
+ throw new UnauthorizedError(`hydrate collection "${collection}"`);
1410
+ }
1411
+ }
1412
+ return [...await definition.hydrate(params, ctx)];
1413
+ },
1414
+ applyChange: (table, change) => applyChange(table, change),
1415
+ connectSource: async (source) => {
1416
+ await source.start((table, change) => applyChange(table, change));
1417
+ return async () => {
1418
+ await source.stop();
1419
+ };
1420
+ },
1421
+ connectCluster: async (bus) => {
1422
+ const unsubscribe = await bus.subscribe((message) => {
1423
+ if (message.origin === instanceId) {
1424
+ return;
1425
+ }
1426
+ applyChangeBatch(message.changes, false);
1427
+ });
1428
+ clusterBus = bus;
1429
+ return async () => {
1430
+ clusterBus = undefined;
1431
+ await unsubscribe();
1432
+ };
1433
+ },
1434
+ subscriptionCount: (collection) => {
1435
+ if (collection !== undefined) {
1436
+ return active.get(collection)?.size ?? 0;
1437
+ }
1438
+ let total = 0;
1439
+ for (const set of active.values()) {
1440
+ total += set.size;
1441
+ }
1442
+ return total;
1443
+ },
1444
+ registerMutation: (mutation) => {
1445
+ mutations.set(mutation.name, mutation);
1446
+ },
1447
+ registerWriter: (table, writer) => {
1448
+ writers.set(table, writer);
1449
+ },
1450
+ registerReactive: (query2) => {
1451
+ registry.set(query2.name, query2);
1452
+ },
1453
+ registerReader: (table, reader) => {
1454
+ readers.set(table, reader);
1455
+ },
1456
+ runMutation: async (name, args, ctx) => {
1457
+ const mutation = mutations.get(name);
1458
+ if (mutation === undefined) {
1459
+ throw new Error(`Unknown mutation "${name}"`);
1460
+ }
1461
+ if (mutation.authorize !== undefined) {
1462
+ const allowed = await mutation.authorize(args, ctx);
1463
+ if (!allowed) {
1464
+ throw new UnauthorizedError(`run mutation "${name}"`);
1465
+ }
1466
+ }
1467
+ const writerFor = (table) => {
1468
+ const writer = writers.get(table);
1469
+ if (writer === undefined) {
1470
+ throw new Error(`No writer registered for table "${table}" \u2014 register one with engine.registerWriter, or use actions.change`);
1471
+ }
1472
+ return writer;
1473
+ };
1474
+ const runHandler = async (tx) => {
1475
+ const buffered2 = [];
1476
+ const actions = {
1477
+ change: (collection, change) => {
1478
+ buffered2.push({
1479
+ table: collection,
1480
+ change
1481
+ });
1482
+ return Promise.resolve();
1483
+ },
1484
+ insert: async (table, data) => {
1485
+ const row = await writerFor(table).insert(data, ctx, tx);
1486
+ buffered2.push({ table, change: { op: "insert", row } });
1487
+ return row;
1488
+ },
1489
+ update: async (table, data) => {
1490
+ const row = await writerFor(table).update(data, ctx, tx);
1491
+ buffered2.push({ table, change: { op: "update", row } });
1492
+ return row;
1493
+ },
1494
+ delete: async (table, row) => {
1495
+ await writerFor(table).delete(row, ctx, tx);
1496
+ buffered2.push({ table, change: { op: "delete", row } });
1497
+ }
1498
+ };
1499
+ const handlerResult = await mutation.handler(args, ctx, actions);
1500
+ return { buffered: buffered2, result: handlerResult };
1501
+ };
1502
+ const { buffered, result } = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
1503
+ await applyChangeBatch(buffered);
1504
+ return result;
1505
+ }
1506
+ };
1507
+ };
1508
+ // src/engine/routes.ts
1509
+ var emptyContext = () => ({});
1510
+ var hydrateRoute = (engine, collection, resolveContext = emptyContext) => {
1511
+ return async (context) => {
1512
+ const rows = await engine.hydrate(collection.name, context.query, resolveContext(context));
1513
+ return rows;
1514
+ };
1515
+ };
1516
+ var mutateRoute = (engine, mutation, resolveContext = emptyContext) => {
1517
+ return async (context) => {
1518
+ const result = await engine.runMutation(mutation.name, context.body, resolveContext(context));
1519
+ return result;
1520
+ };
1521
+ };
1522
+ // src/engine/connection.ts
1523
+ var parseFrame = (raw) => {
1524
+ let value = raw;
1525
+ if (typeof value === "string") {
1526
+ try {
1527
+ value = JSON.parse(value);
1528
+ } catch {
1529
+ return;
1530
+ }
1531
+ }
1532
+ if (typeof value !== "object" || value === null) {
1533
+ return;
1534
+ }
1535
+ const frame = value;
1536
+ if (frame.type === "subscribe") {
1537
+ return typeof frame.id === "string" && typeof frame.collection === "string" ? {
1538
+ type: "subscribe",
1539
+ id: frame.id,
1540
+ collection: frame.collection,
1541
+ params: frame.params,
1542
+ since: typeof frame.since === "number" ? frame.since : undefined
1543
+ } : undefined;
1544
+ }
1545
+ if (frame.type === "unsubscribe") {
1546
+ return typeof frame.id === "string" ? { type: "unsubscribe", id: frame.id } : undefined;
1547
+ }
1548
+ if (frame.type === "mutate") {
1549
+ return typeof frame.mutationId === "number" && typeof frame.name === "string" ? {
1550
+ type: "mutate",
1551
+ mutationId: frame.mutationId,
1552
+ name: frame.name,
1553
+ args: frame.args
1554
+ } : undefined;
1555
+ }
1556
+ if (frame.type === "presence-join") {
1557
+ return typeof frame.room === "string" && typeof frame.memberId === "string" ? {
1558
+ type: "presence-join",
1559
+ room: frame.room,
1560
+ memberId: frame.memberId,
1561
+ state: frame.state
1562
+ } : undefined;
1563
+ }
1564
+ if (frame.type === "presence-set") {
1565
+ return typeof frame.room === "string" ? { type: "presence-set", room: frame.room, state: frame.state } : undefined;
1566
+ }
1567
+ if (frame.type === "presence-leave") {
1568
+ return typeof frame.room === "string" ? { type: "presence-leave", room: frame.room } : undefined;
1569
+ }
1570
+ return;
1571
+ };
1572
+ var createSyncConnection = ({
1573
+ engine,
1574
+ ctx,
1575
+ send,
1576
+ presence
1577
+ }) => {
1578
+ const subscriptions = new Map;
1579
+ const presenceRooms = new Map;
1580
+ let pending = [];
1581
+ let pendingVersion;
1582
+ let flushScheduled = false;
1583
+ const flush = () => {
1584
+ if (pending.length === 0) {
1585
+ return;
1586
+ }
1587
+ const diffs = pending;
1588
+ const version = pendingVersion;
1589
+ pending = [];
1590
+ pendingVersion = undefined;
1591
+ if (diffs.length === 1) {
1592
+ const only = diffs[0];
1593
+ send({
1594
+ type: "diff",
1595
+ id: only.id,
1596
+ added: only.added,
1597
+ removed: only.removed,
1598
+ changed: only.changed,
1599
+ version
1600
+ });
1601
+ } else {
1602
+ send({ type: "frame", diffs, version });
1603
+ }
1604
+ };
1605
+ const scheduleFlush = () => {
1606
+ if (flushScheduled) {
1607
+ return;
1608
+ }
1609
+ flushScheduled = true;
1610
+ queueMicrotask(() => {
1611
+ flushScheduled = false;
1612
+ flush();
1613
+ });
1614
+ };
1615
+ const bufferDiff = (diff, diffVersion) => {
1616
+ if (pending.length > 0 && pendingVersion !== diffVersion) {
1617
+ flush();
1618
+ }
1619
+ pending.push(diff);
1620
+ pendingVersion = diffVersion;
1621
+ scheduleFlush();
1622
+ };
1623
+ const handle = async (raw) => {
1624
+ const frame = parseFrame(raw);
1625
+ if (frame === undefined) {
1626
+ send({ type: "error", message: "Malformed sync frame" });
1627
+ return;
1628
+ }
1629
+ if (frame.type === "mutate") {
1630
+ try {
1631
+ const result = await engine.runMutation(frame.name, frame.args, ctx);
1632
+ flush();
1633
+ send({ type: "ack", mutationId: frame.mutationId, result });
1634
+ } catch (error) {
1635
+ send({
1636
+ type: "reject",
1637
+ mutationId: frame.mutationId,
1638
+ message: error instanceof Error ? error.message : String(error)
1639
+ });
1640
+ }
1641
+ return;
1642
+ }
1643
+ if (frame.type === "unsubscribe") {
1644
+ subscriptions.get(frame.id)?.unsubscribe();
1645
+ subscriptions.delete(frame.id);
1646
+ return;
1647
+ }
1648
+ if (frame.type === "presence-join") {
1649
+ if (presence === undefined) {
1650
+ send({ type: "error", message: "Presence is not enabled" });
1651
+ return;
1652
+ }
1653
+ presenceRooms.get(frame.room)?.leave();
1654
+ const handle2 = presence.join(frame.room, frame.memberId, frame.state, (diff) => {
1655
+ send({
1656
+ type: "presence",
1657
+ room: frame.room,
1658
+ joined: diff.joined,
1659
+ updated: diff.updated,
1660
+ left: diff.left
1661
+ });
1662
+ });
1663
+ presenceRooms.set(frame.room, handle2);
1664
+ send({
1665
+ type: "presence",
1666
+ room: frame.room,
1667
+ joined: handle2.members,
1668
+ updated: [],
1669
+ left: []
1670
+ });
1671
+ return;
1672
+ }
1673
+ if (frame.type === "presence-set") {
1674
+ presenceRooms.get(frame.room)?.set(frame.state);
1675
+ return;
1676
+ }
1677
+ if (frame.type === "presence-leave") {
1678
+ presenceRooms.get(frame.room)?.leave();
1679
+ presenceRooms.delete(frame.room);
1680
+ return;
1681
+ }
1682
+ if (subscriptions.has(frame.id)) {
1683
+ send({
1684
+ type: "error",
1685
+ id: frame.id,
1686
+ message: `Subscription id "${frame.id}" already in use`
1687
+ });
1688
+ return;
1689
+ }
1690
+ try {
1691
+ const subscription = await engine.subscribe({
1692
+ collection: frame.collection,
1693
+ params: frame.params,
1694
+ ctx,
1695
+ since: frame.since,
1696
+ onDiff: (diff, diffVersion) => {
1697
+ bufferDiff({
1698
+ id: frame.id,
1699
+ added: diff.added,
1700
+ removed: diff.removed,
1701
+ changed: diff.changed
1702
+ }, diffVersion);
1703
+ }
1704
+ });
1705
+ subscriptions.set(frame.id, subscription);
1706
+ if (subscription.catchup !== undefined) {
1707
+ send({
1708
+ type: "diff",
1709
+ id: frame.id,
1710
+ added: subscription.catchup.added,
1711
+ removed: subscription.catchup.removed,
1712
+ changed: subscription.catchup.changed,
1713
+ version: subscription.version
1714
+ });
1715
+ } else {
1716
+ send({
1717
+ type: "snapshot",
1718
+ id: frame.id,
1719
+ rows: subscription.initial,
1720
+ version: subscription.version
1721
+ });
1722
+ }
1723
+ } catch (error) {
1724
+ send({
1725
+ type: "error",
1726
+ id: frame.id,
1727
+ message: error instanceof Error ? error.message : String(error)
1728
+ });
1729
+ }
1730
+ };
1731
+ const close = () => {
1732
+ for (const subscription of subscriptions.values()) {
1733
+ subscription.unsubscribe();
1734
+ }
1735
+ subscriptions.clear();
1736
+ for (const handle2 of presenceRooms.values()) {
1737
+ handle2.leave();
1738
+ }
1739
+ presenceRooms.clear();
1740
+ };
1741
+ return { handle, close };
1742
+ };
1743
+ export {
1744
+ query,
1745
+ parseOutboxRow,
1746
+ orderByOp,
1747
+ mutateRoute,
1748
+ materialize,
1749
+ mapOp,
1750
+ joinNode,
1751
+ isEmptyViewDiff,
1752
+ hydrateRoute,
1753
+ fromRowChange,
1754
+ filterOp,
1755
+ defineReactiveQuery,
1756
+ defineMutation,
1757
+ defineJoinCollection,
1758
+ defineGraphCollection,
1759
+ defineCollection,
1760
+ createSyncEngine,
1761
+ createSyncConnection,
1762
+ createPresenceHub,
1763
+ createPollingChangeSource,
1764
+ createMaterializedView,
1765
+ createInMemoryClusterBus,
1766
+ createEquiJoin,
1767
+ createAggregate,
1768
+ chain,
1769
+ aggregateOp,
1770
+ UnauthorizedError
1771
+ };
1772
+
1773
+ //# debugId=4ADB152722C87EA464756E2164756E21
1774
+ //# sourceMappingURL=index.js.map