@absolutejs/sync 0.0.1 → 0.1.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 +264 -24
- package/dist/adapters/drizzle/index.d.ts +17 -0
- package/dist/adapters/drizzle/index.js +128 -0
- package/dist/adapters/drizzle/index.js.map +12 -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 +8 -30
- package/dist/client/index.js +744 -3
- package/dist/client/index.js.map +8 -4
- package/dist/client/liveQuery.d.ts +75 -0
- package/dist/client/subscriber.d.ts +30 -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/collection.d.ts +87 -0
- package/dist/engine/connection.d.ts +71 -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 +34 -0
- package/dist/engine/index.js +1269 -0
- package/dist/engine/index.js.map +20 -0
- package/dist/engine/materializedView.d.ts +53 -0
- package/dist/engine/mutation.d.ts +30 -0
- package/dist/engine/pollingSource.d.ts +42 -0
- package/dist/engine/routes.d.ts +40 -0
- package/dist/engine/socket.d.ts +64 -0
- package/dist/engine/syncEngine.d.ts +100 -0
- package/dist/engine/types.d.ts +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +160 -2
- package/dist/index.js.map +7 -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 +102 -6
|
@@ -0,0 +1,1269 @@
|
|
|
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/collection.ts
|
|
618
|
+
var defineCollection = (definition) => definition;
|
|
619
|
+
var defineJoinCollection = (definition) => ({
|
|
620
|
+
...definition,
|
|
621
|
+
kind: "join"
|
|
622
|
+
});
|
|
623
|
+
// src/engine/graph.ts
|
|
624
|
+
var PLANS = new WeakMap;
|
|
625
|
+
var planTables = (plan) => {
|
|
626
|
+
const tables = [plan.source.table];
|
|
627
|
+
for (const step of plan.steps) {
|
|
628
|
+
if (step.kind === "join") {
|
|
629
|
+
tables.push(...planTables(step.rightPlan));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return [...new Set(tables)];
|
|
633
|
+
};
|
|
634
|
+
var instantiateStream = (source, steps, params, ctx) => {
|
|
635
|
+
const stages = [];
|
|
636
|
+
let currentKey = source.key;
|
|
637
|
+
for (const step of steps) {
|
|
638
|
+
if (step.kind === "filter") {
|
|
639
|
+
const predicate = step.predicate;
|
|
640
|
+
stages.push({
|
|
641
|
+
kind: "op",
|
|
642
|
+
op: filterOp((row) => predicate(row, params, ctx))
|
|
643
|
+
});
|
|
644
|
+
} else if (step.kind === "map") {
|
|
645
|
+
stages.push({ kind: "op", op: mapOp(step.transform, step.rekey) });
|
|
646
|
+
if (step.rekey) {
|
|
647
|
+
currentKey = step.rekey;
|
|
648
|
+
}
|
|
649
|
+
} else if (step.kind === "join") {
|
|
650
|
+
const right = instantiateStream(step.rightPlan.source, step.rightPlan.steps, params, ctx);
|
|
651
|
+
stages.push({
|
|
652
|
+
kind: "join",
|
|
653
|
+
node: joinNode({
|
|
654
|
+
leftKey: currentKey,
|
|
655
|
+
rightKey: right.outKey,
|
|
656
|
+
leftOn: step.on,
|
|
657
|
+
rightOn: step.rightOn,
|
|
658
|
+
select: step.select,
|
|
659
|
+
selectUnmatched: step.selectUnmatched,
|
|
660
|
+
key: step.key
|
|
661
|
+
}),
|
|
662
|
+
right
|
|
663
|
+
});
|
|
664
|
+
currentKey = step.key;
|
|
665
|
+
} else if (step.kind === "aggregate") {
|
|
666
|
+
stages.push({
|
|
667
|
+
kind: "op",
|
|
668
|
+
op: aggregateOp({
|
|
669
|
+
key: step.key,
|
|
670
|
+
groupBy: step.groupBy,
|
|
671
|
+
value: step.value
|
|
672
|
+
})
|
|
673
|
+
});
|
|
674
|
+
currentKey = (group) => group.group;
|
|
675
|
+
} else {
|
|
676
|
+
stages.push({
|
|
677
|
+
kind: "op",
|
|
678
|
+
op: orderByOp({
|
|
679
|
+
key: step.key,
|
|
680
|
+
compare: step.compare,
|
|
681
|
+
limit: step.limit,
|
|
682
|
+
offset: step.offset
|
|
683
|
+
})
|
|
684
|
+
});
|
|
685
|
+
currentKey = step.key;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
const propagate = (changes, fromStage, side) => {
|
|
689
|
+
let cs = changes;
|
|
690
|
+
for (let i = fromStage;i < stages.length; i += 1) {
|
|
691
|
+
const stage = stages[i];
|
|
692
|
+
if (stage.kind === "join") {
|
|
693
|
+
cs = i === fromStage && side === "right" ? stage.node.pushRight(cs) : stage.node.pushLeft(cs);
|
|
694
|
+
} else {
|
|
695
|
+
cs = stage.op.push(cs);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return cs;
|
|
699
|
+
};
|
|
700
|
+
const sourceChange = (change) => {
|
|
701
|
+
const key = source.key(change.row);
|
|
702
|
+
if (change.op === "delete" || source.match !== undefined && !source.match(change.row, params, ctx)) {
|
|
703
|
+
return { op: "delete", key, row: change.row };
|
|
704
|
+
}
|
|
705
|
+
return { op: "upsert", key, row: change.row };
|
|
706
|
+
};
|
|
707
|
+
const entries = new Map;
|
|
708
|
+
const addEntry = (table, entry) => {
|
|
709
|
+
const list = entries.get(table);
|
|
710
|
+
if (list === undefined) {
|
|
711
|
+
entries.set(table, [entry]);
|
|
712
|
+
} else {
|
|
713
|
+
list.push(entry);
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
addEntry(source.table, {
|
|
717
|
+
stageIndex: 0,
|
|
718
|
+
side: "left",
|
|
719
|
+
produce: (change) => [sourceChange(change)]
|
|
720
|
+
});
|
|
721
|
+
stages.forEach((stage, index) => {
|
|
722
|
+
if (stage.kind === "join") {
|
|
723
|
+
for (const table of stage.right.tables) {
|
|
724
|
+
addEntry(table, {
|
|
725
|
+
stageIndex: index,
|
|
726
|
+
side: "right",
|
|
727
|
+
produce: (change) => stage.right.applyStream(table, change)
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
return {
|
|
733
|
+
tables: planTables({ source, steps }),
|
|
734
|
+
outKey: currentKey,
|
|
735
|
+
hydrateStream: async () => {
|
|
736
|
+
for (let i = 0;i < stages.length; i += 1) {
|
|
737
|
+
const stage = stages[i];
|
|
738
|
+
if (stage.kind === "join") {
|
|
739
|
+
propagate(await stage.right.hydrateStream(), i, "right");
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const rootRows = [...await source.hydrate(params, ctx)];
|
|
743
|
+
return propagate(rootRows.map((row) => ({
|
|
744
|
+
op: "upsert",
|
|
745
|
+
key: source.key(row),
|
|
746
|
+
row
|
|
747
|
+
})), 0, "left");
|
|
748
|
+
},
|
|
749
|
+
applyStream: (table, change) => {
|
|
750
|
+
const list = entries.get(table);
|
|
751
|
+
if (list === undefined) {
|
|
752
|
+
return [];
|
|
753
|
+
}
|
|
754
|
+
const out = [];
|
|
755
|
+
for (const entry of list) {
|
|
756
|
+
out.push(...propagate(entry.produce(change), entry.stageIndex, entry.side));
|
|
757
|
+
}
|
|
758
|
+
return out;
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
};
|
|
762
|
+
var instantiate = (source, steps, params, ctx) => {
|
|
763
|
+
const graph = instantiateStream(source, steps, params, ctx);
|
|
764
|
+
const sink = materialize(graph.outKey);
|
|
765
|
+
return {
|
|
766
|
+
tables: graph.tables,
|
|
767
|
+
hydrate: async () => {
|
|
768
|
+
sink.apply(await graph.hydrateStream());
|
|
769
|
+
return sink.rows();
|
|
770
|
+
},
|
|
771
|
+
applyChange: (table, change) => sink.apply(graph.applyStream(table, change))
|
|
772
|
+
};
|
|
773
|
+
};
|
|
774
|
+
var makeQuery = (source, steps) => {
|
|
775
|
+
const addJoin = (right, options) => {
|
|
776
|
+
const rightPlan = PLANS.get(right) ?? {
|
|
777
|
+
source: right,
|
|
778
|
+
steps: []
|
|
779
|
+
};
|
|
780
|
+
return makeQuery(source, [
|
|
781
|
+
...steps,
|
|
782
|
+
{ kind: "join", rightPlan, ...options }
|
|
783
|
+
]);
|
|
784
|
+
};
|
|
785
|
+
const queryInstance = {
|
|
786
|
+
filter: (predicate) => makeQuery(source, [...steps, { kind: "filter", predicate }]),
|
|
787
|
+
map: (transform, rekey) => makeQuery(source, [...steps, { kind: "map", transform, rekey }]),
|
|
788
|
+
join: (right, options) => addJoin(right, options),
|
|
789
|
+
leftJoin: (right, options) => addJoin(right, options),
|
|
790
|
+
groupBy: (options) => makeQuery(source, [...steps, { kind: "aggregate", ...options }]),
|
|
791
|
+
orderBy: (options) => makeQuery(source, [...steps, { kind: "orderBy", ...options }]),
|
|
792
|
+
tables: () => planTables({ source, steps }),
|
|
793
|
+
instantiate: (params, ctx) => instantiate(source, steps, params, ctx)
|
|
794
|
+
};
|
|
795
|
+
PLANS.set(queryInstance, { source, steps });
|
|
796
|
+
return queryInstance;
|
|
797
|
+
};
|
|
798
|
+
var query = (source) => makeQuery(source, []);
|
|
799
|
+
var defineGraphCollection = (definition) => ({ ...definition, kind: "graph" });
|
|
800
|
+
// src/engine/mutation.ts
|
|
801
|
+
var defineMutation = (definition) => definition;
|
|
802
|
+
// src/engine/syncEngine.ts
|
|
803
|
+
class UnauthorizedError extends Error {
|
|
804
|
+
constructor(subject) {
|
|
805
|
+
super(`Not authorized: ${subject}`);
|
|
806
|
+
this.name = "UnauthorizedError";
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
var defaultKey = (row) => row.id;
|
|
810
|
+
var createSyncEngine = (options = {}) => {
|
|
811
|
+
const registry = new Map;
|
|
812
|
+
const mutations = new Map;
|
|
813
|
+
const active = new Map;
|
|
814
|
+
const tableIndex = new Map;
|
|
815
|
+
const changeLogSize = options.changeLogSize ?? 1024;
|
|
816
|
+
const changeLog = [];
|
|
817
|
+
let version = 0;
|
|
818
|
+
const subsFor = (collection) => {
|
|
819
|
+
let set = active.get(collection);
|
|
820
|
+
if (set === undefined) {
|
|
821
|
+
set = new Set;
|
|
822
|
+
active.set(collection, set);
|
|
823
|
+
}
|
|
824
|
+
return set;
|
|
825
|
+
};
|
|
826
|
+
const addTableIndex = (table, name) => {
|
|
827
|
+
let set = tableIndex.get(table);
|
|
828
|
+
if (set === undefined) {
|
|
829
|
+
set = new Set;
|
|
830
|
+
tableIndex.set(table, set);
|
|
831
|
+
}
|
|
832
|
+
set.add(name);
|
|
833
|
+
};
|
|
834
|
+
const sideChange = (change, match) => change.op !== "delete" && match !== undefined && !match(change.row) ? { op: "delete", row: change.row } : change;
|
|
835
|
+
const applyToSubscription = async (subscription, table, change, changeVersion) => {
|
|
836
|
+
let diff;
|
|
837
|
+
if (subscription.kind === "graph") {
|
|
838
|
+
diff = subscription.instance.applyChange(table, change);
|
|
839
|
+
} else if (subscription.kind === "join") {
|
|
840
|
+
const js = subscription.join;
|
|
841
|
+
if (table === js.leftTable) {
|
|
842
|
+
diff = js.op.applyLeft(sideChange(change, js.leftMatch));
|
|
843
|
+
} else if (table === js.rightTable) {
|
|
844
|
+
diff = js.op.applyRight(sideChange(change, js.rightMatch));
|
|
845
|
+
} else {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
} else if (subscription.incremental) {
|
|
849
|
+
try {
|
|
850
|
+
diff = subscription.view.apply(change);
|
|
851
|
+
} catch {
|
|
852
|
+
diff = subscription.view.reset(await subscription.rehydrate());
|
|
853
|
+
}
|
|
854
|
+
} else {
|
|
855
|
+
diff = subscription.view.reset(await subscription.rehydrate());
|
|
856
|
+
}
|
|
857
|
+
if (!isEmptyViewDiff(diff)) {
|
|
858
|
+
subscription.onDiff(diff, changeVersion);
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
const applyChange = async (table, change) => {
|
|
862
|
+
version += 1;
|
|
863
|
+
changeLog.push({ version, table, change });
|
|
864
|
+
if (changeLog.length > changeLogSize) {
|
|
865
|
+
changeLog.shift();
|
|
866
|
+
}
|
|
867
|
+
const changeVersion = version;
|
|
868
|
+
const collectionNames = tableIndex.get(table);
|
|
869
|
+
if (collectionNames === undefined) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
for (const name of collectionNames) {
|
|
873
|
+
const set = active.get(name);
|
|
874
|
+
if (set === undefined || set.size === 0) {
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
for (const subscription of set) {
|
|
878
|
+
await applyToSubscription(subscription, table, change, changeVersion);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
const canResume = (since, incremental) => {
|
|
883
|
+
if (!incremental) {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
if (since >= version) {
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
889
|
+
const oldest = changeLog[0];
|
|
890
|
+
return oldest !== undefined && oldest.version <= since + 1;
|
|
891
|
+
};
|
|
892
|
+
const buildCatchup = (since, tables, key, match) => {
|
|
893
|
+
const latest = new Map;
|
|
894
|
+
for (const entry of changeLog) {
|
|
895
|
+
if (entry.version <= since || !tables.includes(entry.table)) {
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
const row = entry.change.row;
|
|
899
|
+
const present = entry.change.op !== "delete" && match(row) ? "upsert" : "remove";
|
|
900
|
+
latest.set(key(row), { op: present, row });
|
|
901
|
+
}
|
|
902
|
+
const changed = [];
|
|
903
|
+
const removed = [];
|
|
904
|
+
for (const { op, row } of latest.values()) {
|
|
905
|
+
(op === "upsert" ? changed : removed).push(row);
|
|
906
|
+
}
|
|
907
|
+
return { added: [], removed, changed };
|
|
908
|
+
};
|
|
909
|
+
const subscribeJoin = async (collection, definition, params, ctx, onDiff, set) => {
|
|
910
|
+
if (definition.authorize !== undefined) {
|
|
911
|
+
const allowed = await definition.authorize(params, ctx);
|
|
912
|
+
if (!allowed) {
|
|
913
|
+
throw new UnauthorizedError(`subscribe to collection "${collection}"`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const { left, right } = definition;
|
|
917
|
+
const op = createEquiJoin({
|
|
918
|
+
leftKey: left.key,
|
|
919
|
+
rightKey: right.key,
|
|
920
|
+
leftOn: left.on,
|
|
921
|
+
rightOn: right.on,
|
|
922
|
+
select: definition.select
|
|
923
|
+
});
|
|
924
|
+
op.hydrate([...await left.hydrate(params, ctx)], [...await right.hydrate(params, ctx)]);
|
|
925
|
+
const atVersion = version;
|
|
926
|
+
const subscription = {
|
|
927
|
+
kind: "join",
|
|
928
|
+
collection,
|
|
929
|
+
join: {
|
|
930
|
+
op,
|
|
931
|
+
leftTable: left.table,
|
|
932
|
+
rightTable: right.table,
|
|
933
|
+
leftMatch: left.match ? (row) => left.match(row, params, ctx) : undefined,
|
|
934
|
+
rightMatch: right.match ? (row) => right.match(row, params, ctx) : undefined
|
|
935
|
+
},
|
|
936
|
+
onDiff
|
|
937
|
+
};
|
|
938
|
+
set.add(subscription);
|
|
939
|
+
return {
|
|
940
|
+
initial: op.rows(),
|
|
941
|
+
version: atVersion,
|
|
942
|
+
unsubscribe: () => {
|
|
943
|
+
set.delete(subscription);
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
};
|
|
947
|
+
const subscribeGraph = async (collection, definition, params, ctx, onDiff, set) => {
|
|
948
|
+
if (definition.authorize !== undefined) {
|
|
949
|
+
const allowed = await definition.authorize(params, ctx);
|
|
950
|
+
if (!allowed) {
|
|
951
|
+
throw new UnauthorizedError(`subscribe to collection "${collection}"`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const instance = definition.query.instantiate(params, ctx);
|
|
955
|
+
const initial = await instance.hydrate();
|
|
956
|
+
const atVersion = version;
|
|
957
|
+
const subscription = {
|
|
958
|
+
kind: "graph",
|
|
959
|
+
collection,
|
|
960
|
+
instance,
|
|
961
|
+
onDiff
|
|
962
|
+
};
|
|
963
|
+
set.add(subscription);
|
|
964
|
+
return {
|
|
965
|
+
initial,
|
|
966
|
+
version: atVersion,
|
|
967
|
+
unsubscribe: () => {
|
|
968
|
+
set.delete(subscription);
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
};
|
|
972
|
+
return {
|
|
973
|
+
register: (collection) => {
|
|
974
|
+
registry.set(collection.name, collection);
|
|
975
|
+
for (const table of collection.tables ?? [collection.name]) {
|
|
976
|
+
addTableIndex(table, collection.name);
|
|
977
|
+
}
|
|
978
|
+
},
|
|
979
|
+
registerJoin: (collection) => {
|
|
980
|
+
registry.set(collection.name, collection);
|
|
981
|
+
addTableIndex(collection.left.table, collection.name);
|
|
982
|
+
addTableIndex(collection.right.table, collection.name);
|
|
983
|
+
},
|
|
984
|
+
registerGraph: (collection) => {
|
|
985
|
+
registry.set(collection.name, collection);
|
|
986
|
+
for (const table of collection.query.tables()) {
|
|
987
|
+
addTableIndex(table, collection.name);
|
|
988
|
+
}
|
|
989
|
+
},
|
|
990
|
+
subscribe: async ({ collection, params, ctx, onDiff, since }) => {
|
|
991
|
+
const registered = registry.get(collection);
|
|
992
|
+
if (registered === undefined) {
|
|
993
|
+
throw new Error(`Unknown collection "${collection}"`);
|
|
994
|
+
}
|
|
995
|
+
const typedOnDiff = onDiff;
|
|
996
|
+
const subscribeSet = subsFor(collection);
|
|
997
|
+
const registeredKind = registered.kind;
|
|
998
|
+
if (registeredKind === "join") {
|
|
999
|
+
const joined = await subscribeJoin(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
1000
|
+
return joined;
|
|
1001
|
+
}
|
|
1002
|
+
if (registeredKind === "graph") {
|
|
1003
|
+
const graphed = await subscribeGraph(collection, registered, params, ctx, typedOnDiff, subscribeSet);
|
|
1004
|
+
return graphed;
|
|
1005
|
+
}
|
|
1006
|
+
const definition = registered;
|
|
1007
|
+
if (definition.authorize !== undefined) {
|
|
1008
|
+
const allowed = await definition.authorize(params, ctx);
|
|
1009
|
+
if (!allowed) {
|
|
1010
|
+
throw new UnauthorizedError(`subscribe to collection "${collection}"`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
const key = definition.key ?? defaultKey;
|
|
1014
|
+
const rehydrate = async () => definition.hydrate(params, ctx);
|
|
1015
|
+
const match = definition.match;
|
|
1016
|
+
const tables = definition.tables ?? [collection];
|
|
1017
|
+
const incremental = match !== undefined && tables.length === 1;
|
|
1018
|
+
const boundMatch = incremental ? (row) => match(row, params, ctx) : () => true;
|
|
1019
|
+
const view = createMaterializedView({
|
|
1020
|
+
key,
|
|
1021
|
+
match: boundMatch
|
|
1022
|
+
});
|
|
1023
|
+
const resuming = since !== undefined && canResume(since, incremental);
|
|
1024
|
+
view.hydrate([...await rehydrate()]);
|
|
1025
|
+
const atVersion = version;
|
|
1026
|
+
const subscription = {
|
|
1027
|
+
kind: "view",
|
|
1028
|
+
collection,
|
|
1029
|
+
view,
|
|
1030
|
+
incremental,
|
|
1031
|
+
rehydrate,
|
|
1032
|
+
onDiff: typedOnDiff
|
|
1033
|
+
};
|
|
1034
|
+
subscribeSet.add(subscription);
|
|
1035
|
+
const unsubscribe = () => {
|
|
1036
|
+
subscribeSet.delete(subscription);
|
|
1037
|
+
};
|
|
1038
|
+
if (resuming) {
|
|
1039
|
+
return {
|
|
1040
|
+
initial: [],
|
|
1041
|
+
catchup: buildCatchup(since, tables, key, boundMatch),
|
|
1042
|
+
version: atVersion,
|
|
1043
|
+
unsubscribe
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
return {
|
|
1047
|
+
initial: view.rows(),
|
|
1048
|
+
version: atVersion,
|
|
1049
|
+
unsubscribe
|
|
1050
|
+
};
|
|
1051
|
+
},
|
|
1052
|
+
hydrate: async (collection, params, ctx) => {
|
|
1053
|
+
const definition = registry.get(collection);
|
|
1054
|
+
if (definition === undefined) {
|
|
1055
|
+
throw new Error(`Unknown collection "${collection}"`);
|
|
1056
|
+
}
|
|
1057
|
+
if (definition.authorize !== undefined) {
|
|
1058
|
+
const allowed = await definition.authorize(params, ctx);
|
|
1059
|
+
if (!allowed) {
|
|
1060
|
+
throw new UnauthorizedError(`hydrate collection "${collection}"`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return [...await definition.hydrate(params, ctx)];
|
|
1064
|
+
},
|
|
1065
|
+
applyChange: (table, change) => applyChange(table, change),
|
|
1066
|
+
connectSource: async (source) => {
|
|
1067
|
+
await source.start((table, change) => applyChange(table, change));
|
|
1068
|
+
return async () => {
|
|
1069
|
+
await source.stop();
|
|
1070
|
+
};
|
|
1071
|
+
},
|
|
1072
|
+
subscriptionCount: (collection) => {
|
|
1073
|
+
if (collection !== undefined) {
|
|
1074
|
+
return active.get(collection)?.size ?? 0;
|
|
1075
|
+
}
|
|
1076
|
+
let total = 0;
|
|
1077
|
+
for (const set of active.values()) {
|
|
1078
|
+
total += set.size;
|
|
1079
|
+
}
|
|
1080
|
+
return total;
|
|
1081
|
+
},
|
|
1082
|
+
registerMutation: (mutation) => {
|
|
1083
|
+
mutations.set(mutation.name, mutation);
|
|
1084
|
+
},
|
|
1085
|
+
runMutation: async (name, args, ctx) => {
|
|
1086
|
+
const mutation = mutations.get(name);
|
|
1087
|
+
if (mutation === undefined) {
|
|
1088
|
+
throw new Error(`Unknown mutation "${name}"`);
|
|
1089
|
+
}
|
|
1090
|
+
if (mutation.authorize !== undefined) {
|
|
1091
|
+
const allowed = await mutation.authorize(args, ctx);
|
|
1092
|
+
if (!allowed) {
|
|
1093
|
+
throw new UnauthorizedError(`run mutation "${name}"`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return mutation.handler(args, ctx, {
|
|
1097
|
+
change: (collection, change) => applyChange(collection, change)
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
};
|
|
1102
|
+
// src/engine/routes.ts
|
|
1103
|
+
var emptyContext = () => ({});
|
|
1104
|
+
var hydrateRoute = (engine, collection, resolveContext = emptyContext) => {
|
|
1105
|
+
return async (context) => {
|
|
1106
|
+
const rows = await engine.hydrate(collection.name, context.query, resolveContext(context));
|
|
1107
|
+
return rows;
|
|
1108
|
+
};
|
|
1109
|
+
};
|
|
1110
|
+
var mutateRoute = (engine, mutation, resolveContext = emptyContext) => {
|
|
1111
|
+
return async (context) => {
|
|
1112
|
+
const result = await engine.runMutation(mutation.name, context.body, resolveContext(context));
|
|
1113
|
+
return result;
|
|
1114
|
+
};
|
|
1115
|
+
};
|
|
1116
|
+
// src/engine/connection.ts
|
|
1117
|
+
var parseFrame = (raw) => {
|
|
1118
|
+
let value = raw;
|
|
1119
|
+
if (typeof value === "string") {
|
|
1120
|
+
try {
|
|
1121
|
+
value = JSON.parse(value);
|
|
1122
|
+
} catch {
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
if (typeof value !== "object" || value === null) {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const frame = value;
|
|
1130
|
+
if (frame.type === "subscribe") {
|
|
1131
|
+
return typeof frame.id === "string" && typeof frame.collection === "string" ? {
|
|
1132
|
+
type: "subscribe",
|
|
1133
|
+
id: frame.id,
|
|
1134
|
+
collection: frame.collection,
|
|
1135
|
+
params: frame.params,
|
|
1136
|
+
since: typeof frame.since === "number" ? frame.since : undefined
|
|
1137
|
+
} : undefined;
|
|
1138
|
+
}
|
|
1139
|
+
if (frame.type === "unsubscribe") {
|
|
1140
|
+
return typeof frame.id === "string" ? { type: "unsubscribe", id: frame.id } : undefined;
|
|
1141
|
+
}
|
|
1142
|
+
if (frame.type === "mutate") {
|
|
1143
|
+
return typeof frame.mutationId === "number" && typeof frame.name === "string" ? {
|
|
1144
|
+
type: "mutate",
|
|
1145
|
+
mutationId: frame.mutationId,
|
|
1146
|
+
name: frame.name,
|
|
1147
|
+
args: frame.args
|
|
1148
|
+
} : undefined;
|
|
1149
|
+
}
|
|
1150
|
+
return;
|
|
1151
|
+
};
|
|
1152
|
+
var createSyncConnection = ({
|
|
1153
|
+
engine,
|
|
1154
|
+
ctx,
|
|
1155
|
+
send
|
|
1156
|
+
}) => {
|
|
1157
|
+
const subscriptions = new Map;
|
|
1158
|
+
const handle = async (raw) => {
|
|
1159
|
+
const frame = parseFrame(raw);
|
|
1160
|
+
if (frame === undefined) {
|
|
1161
|
+
send({ type: "error", message: "Malformed sync frame" });
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (frame.type === "mutate") {
|
|
1165
|
+
try {
|
|
1166
|
+
const result = await engine.runMutation(frame.name, frame.args, ctx);
|
|
1167
|
+
send({ type: "ack", mutationId: frame.mutationId, result });
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
send({
|
|
1170
|
+
type: "reject",
|
|
1171
|
+
mutationId: frame.mutationId,
|
|
1172
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
if (frame.type === "unsubscribe") {
|
|
1178
|
+
subscriptions.get(frame.id)?.unsubscribe();
|
|
1179
|
+
subscriptions.delete(frame.id);
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
if (subscriptions.has(frame.id)) {
|
|
1183
|
+
send({
|
|
1184
|
+
type: "error",
|
|
1185
|
+
id: frame.id,
|
|
1186
|
+
message: `Subscription id "${frame.id}" already in use`
|
|
1187
|
+
});
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
try {
|
|
1191
|
+
const subscription = await engine.subscribe({
|
|
1192
|
+
collection: frame.collection,
|
|
1193
|
+
params: frame.params,
|
|
1194
|
+
ctx,
|
|
1195
|
+
since: frame.since,
|
|
1196
|
+
onDiff: (diff, diffVersion) => {
|
|
1197
|
+
send({
|
|
1198
|
+
type: "diff",
|
|
1199
|
+
id: frame.id,
|
|
1200
|
+
added: diff.added,
|
|
1201
|
+
removed: diff.removed,
|
|
1202
|
+
changed: diff.changed,
|
|
1203
|
+
version: diffVersion
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
subscriptions.set(frame.id, subscription);
|
|
1208
|
+
if (subscription.catchup !== undefined) {
|
|
1209
|
+
send({
|
|
1210
|
+
type: "diff",
|
|
1211
|
+
id: frame.id,
|
|
1212
|
+
added: subscription.catchup.added,
|
|
1213
|
+
removed: subscription.catchup.removed,
|
|
1214
|
+
changed: subscription.catchup.changed,
|
|
1215
|
+
version: subscription.version
|
|
1216
|
+
});
|
|
1217
|
+
} else {
|
|
1218
|
+
send({
|
|
1219
|
+
type: "snapshot",
|
|
1220
|
+
id: frame.id,
|
|
1221
|
+
rows: subscription.initial,
|
|
1222
|
+
version: subscription.version
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
send({
|
|
1227
|
+
type: "error",
|
|
1228
|
+
id: frame.id,
|
|
1229
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
const close = () => {
|
|
1234
|
+
for (const subscription of subscriptions.values()) {
|
|
1235
|
+
subscription.unsubscribe();
|
|
1236
|
+
}
|
|
1237
|
+
subscriptions.clear();
|
|
1238
|
+
};
|
|
1239
|
+
return { handle, close };
|
|
1240
|
+
};
|
|
1241
|
+
export {
|
|
1242
|
+
query,
|
|
1243
|
+
parseOutboxRow,
|
|
1244
|
+
orderByOp,
|
|
1245
|
+
mutateRoute,
|
|
1246
|
+
materialize,
|
|
1247
|
+
mapOp,
|
|
1248
|
+
joinNode,
|
|
1249
|
+
isEmptyViewDiff,
|
|
1250
|
+
hydrateRoute,
|
|
1251
|
+
fromRowChange,
|
|
1252
|
+
filterOp,
|
|
1253
|
+
defineMutation,
|
|
1254
|
+
defineJoinCollection,
|
|
1255
|
+
defineGraphCollection,
|
|
1256
|
+
defineCollection,
|
|
1257
|
+
createSyncEngine,
|
|
1258
|
+
createSyncConnection,
|
|
1259
|
+
createPollingChangeSource,
|
|
1260
|
+
createMaterializedView,
|
|
1261
|
+
createEquiJoin,
|
|
1262
|
+
createAggregate,
|
|
1263
|
+
chain,
|
|
1264
|
+
aggregateOp,
|
|
1265
|
+
UnauthorizedError
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
//# debugId=CDF0007CE81F93F364756E2164756E21
|
|
1269
|
+
//# sourceMappingURL=index.js.map
|