@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.
- package/README.md +281 -24
- package/dist/adapters/drizzle/collection.d.ts +27 -0
- package/dist/adapters/drizzle/index.d.ts +20 -0
- package/dist/adapters/drizzle/index.js +265 -0
- package/dist/adapters/drizzle/index.js.map +14 -0
- package/dist/adapters/drizzle/predicate.d.ts +20 -0
- package/dist/adapters/drizzle/read.d.ts +31 -0
- package/dist/adapters/drizzle/topics.d.ts +41 -0
- package/dist/adapters/drizzle/write.d.ts +69 -0
- package/dist/adapters/mysql/index.d.ts +75 -0
- package/dist/adapters/mysql/index.js +171 -0
- package/dist/adapters/mysql/index.js.map +11 -0
- package/dist/adapters/postgres/index.d.ts +53 -0
- package/dist/adapters/postgres/index.js +86 -0
- package/dist/adapters/postgres/index.js.map +10 -0
- package/dist/adapters/prisma/collection.d.ts +39 -0
- package/dist/adapters/prisma/index.d.ts +23 -0
- package/dist/adapters/prisma/index.js +231 -0
- package/dist/adapters/prisma/index.js.map +14 -0
- package/dist/adapters/prisma/predicate.d.ts +20 -0
- package/dist/adapters/prisma/read.d.ts +28 -0
- package/dist/adapters/prisma/topics.d.ts +29 -0
- package/dist/adapters/prisma/write.d.ts +65 -0
- package/dist/adapters/sqlite/index.d.ts +32 -0
- package/dist/adapters/sqlite/index.js +128 -0
- package/dist/adapters/sqlite/index.js.map +11 -0
- package/dist/angular/index.d.ts +1 -0
- package/dist/angular/index.js +347 -0
- package/dist/angular/index.js.map +11 -0
- package/dist/angular/sync-collection.service.d.ts +20 -0
- package/dist/client/index.d.ts +12 -30
- package/dist/client/index.js +1099 -3
- package/dist/client/index.js.map +10 -4
- package/dist/client/liveQuery.d.ts +75 -0
- package/dist/client/presence.d.ts +37 -0
- package/dist/client/subscriber.d.ts +30 -0
- package/dist/client/syncClient.d.ts +53 -0
- package/dist/client/syncCollection.d.ts +102 -0
- package/dist/client/syncStore.d.ts +81 -0
- package/dist/engine/aggregate.d.ts +45 -0
- package/dist/engine/cluster.d.ts +41 -0
- package/dist/engine/collection.d.ts +87 -0
- package/dist/engine/connection.d.ts +103 -0
- package/dist/engine/dataflow.d.ts +109 -0
- package/dist/engine/equiJoin.d.ts +51 -0
- package/dist/engine/graph.d.ts +85 -0
- package/dist/engine/index.d.ts +40 -0
- package/dist/engine/index.js +1774 -0
- package/dist/engine/index.js.map +23 -0
- package/dist/engine/materializedView.d.ts +53 -0
- package/dist/engine/mutation.d.ts +66 -0
- package/dist/engine/pollingSource.d.ts +42 -0
- package/dist/engine/presence.d.ts +46 -0
- package/dist/engine/reactive.d.ts +67 -0
- package/dist/engine/routes.d.ts +40 -0
- package/dist/engine/socket.d.ts +67 -0
- package/dist/engine/syncEngine.d.ts +132 -0
- package/dist/engine/types.d.ts +45 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +327 -3
- package/dist/index.js.map +8 -5
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +332 -0
- package/dist/react/index.js.map +11 -0
- package/dist/react/useSyncCollection.d.ts +16 -0
- package/dist/reactiveHub.d.ts +6 -0
- package/dist/svelte/createSyncCollectionStore.d.ts +15 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +338 -0
- package/dist/svelte/index.js.map +11 -0
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.js +331 -0
- package/dist/vue/index.js.map +11 -0
- package/dist/vue/useSyncCollection.d.ts +17 -0
- 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
|