@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
package/dist/client/index.js
CHANGED
|
@@ -1,4 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __name = (target, name) => {
|
|
5
|
+
Object.defineProperty(target, "name", {
|
|
6
|
+
value: name,
|
|
7
|
+
enumerable: false,
|
|
8
|
+
configurable: true
|
|
9
|
+
});
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
13
|
+
var __typeError = (msg) => {
|
|
14
|
+
throw TypeError(msg);
|
|
15
|
+
};
|
|
16
|
+
var __defNormalProp = (obj, key, value) => (key in obj) ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
17
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
18
|
+
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
|
|
19
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
20
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
21
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
22
|
+
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
23
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
24
|
+
var __expectFn = (fn) => fn !== undefined && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
25
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({
|
|
26
|
+
kind: __decoratorStrings[kind],
|
|
27
|
+
name,
|
|
28
|
+
metadata,
|
|
29
|
+
addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
|
|
30
|
+
});
|
|
31
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
32
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
33
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length;i < n; i++)
|
|
34
|
+
flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
|
|
35
|
+
return value;
|
|
36
|
+
};
|
|
37
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
38
|
+
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
|
|
39
|
+
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
|
|
40
|
+
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
|
|
41
|
+
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
|
|
42
|
+
get [name]() {
|
|
43
|
+
return __privateGet(this, extra);
|
|
44
|
+
},
|
|
45
|
+
set [name](x) {
|
|
46
|
+
__privateSet(this, extra, x);
|
|
47
|
+
}
|
|
48
|
+
}, name));
|
|
49
|
+
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
|
|
50
|
+
for (var i = decorators.length - 1;i >= 0; i--) {
|
|
51
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
52
|
+
if (k) {
|
|
53
|
+
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => (name in x) };
|
|
54
|
+
if (k ^ 3)
|
|
55
|
+
access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
|
|
56
|
+
if (k > 2)
|
|
57
|
+
access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
|
|
58
|
+
}
|
|
59
|
+
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? undefined : { get: desc.get, set: desc.set } : target, ctx);
|
|
60
|
+
done._ = 1;
|
|
61
|
+
if (k ^ 4 || it === undefined)
|
|
62
|
+
__expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
|
|
63
|
+
else if (typeof it !== "object" || it === null)
|
|
64
|
+
__typeError("Object expected");
|
|
65
|
+
else
|
|
66
|
+
__expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
|
|
67
|
+
}
|
|
68
|
+
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// src/client/subscriber.ts
|
|
2
72
|
var createSyncSubscriber = ({
|
|
3
73
|
topics,
|
|
4
74
|
onEvent,
|
|
@@ -33,9 +103,680 @@ var createSyncSubscriber = ({
|
|
|
33
103
|
source
|
|
34
104
|
};
|
|
35
105
|
};
|
|
106
|
+
// src/reactiveHub.ts
|
|
107
|
+
var SYNC_OPEN_TOPIC = "@absolutejs/sync:open";
|
|
108
|
+
|
|
109
|
+
// src/client/liveQuery.ts
|
|
110
|
+
var createLiveQuery = (options) => {
|
|
111
|
+
const hasSeed = options.initialData !== undefined;
|
|
112
|
+
let state = {
|
|
113
|
+
data: options.initialData,
|
|
114
|
+
error: undefined,
|
|
115
|
+
loading: !options.manual && !hasSeed,
|
|
116
|
+
fetching: false
|
|
117
|
+
};
|
|
118
|
+
const listeners = new Set;
|
|
119
|
+
const setState = (patch) => {
|
|
120
|
+
state = { ...state, ...patch };
|
|
121
|
+
for (const listener of listeners) {
|
|
122
|
+
listener(state);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
let requestSeq = 0;
|
|
126
|
+
let inFlight;
|
|
127
|
+
let closed = false;
|
|
128
|
+
const refetch = async () => {
|
|
129
|
+
if (closed) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const seq = requestSeq += 1;
|
|
133
|
+
inFlight?.abort();
|
|
134
|
+
const controller = new AbortController;
|
|
135
|
+
inFlight = controller;
|
|
136
|
+
setState({ fetching: true });
|
|
137
|
+
try {
|
|
138
|
+
const data = await options.fetcher(controller.signal);
|
|
139
|
+
if (seq !== requestSeq) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
setState({
|
|
143
|
+
data,
|
|
144
|
+
error: undefined,
|
|
145
|
+
loading: false,
|
|
146
|
+
fetching: false
|
|
147
|
+
});
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (controller.signal.aborted || seq !== requestSeq) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
setState({ error, loading: false, fetching: false });
|
|
153
|
+
options.onError?.(error);
|
|
154
|
+
} finally {
|
|
155
|
+
if (inFlight === controller) {
|
|
156
|
+
inFlight = undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
let debounceTimer;
|
|
161
|
+
const scheduleRefetch = () => {
|
|
162
|
+
if (closed) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (!options.debounceMs) {
|
|
166
|
+
refetch();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (debounceTimer !== undefined) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
debounceTimer = setTimeout(() => {
|
|
173
|
+
debounceTimer = undefined;
|
|
174
|
+
refetch();
|
|
175
|
+
}, options.debounceMs);
|
|
176
|
+
};
|
|
177
|
+
let opened = false;
|
|
178
|
+
const onEvent = (event) => {
|
|
179
|
+
if (event.topic === SYNC_OPEN_TOPIC) {
|
|
180
|
+
if (opened) {
|
|
181
|
+
scheduleRefetch();
|
|
182
|
+
}
|
|
183
|
+
opened = true;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
scheduleRefetch();
|
|
187
|
+
};
|
|
188
|
+
const subscriber = createSyncSubscriber({
|
|
189
|
+
topics: options.topics,
|
|
190
|
+
onEvent,
|
|
191
|
+
url: options.url,
|
|
192
|
+
withCredentials: options.withCredentials,
|
|
193
|
+
eventSourceImpl: options.eventSourceImpl
|
|
194
|
+
});
|
|
195
|
+
if (!options.manual && !hasSeed) {
|
|
196
|
+
refetch();
|
|
197
|
+
}
|
|
198
|
+
const close = () => {
|
|
199
|
+
if (closed) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
closed = true;
|
|
203
|
+
subscriber.close();
|
|
204
|
+
inFlight?.abort();
|
|
205
|
+
if (debounceTimer !== undefined) {
|
|
206
|
+
clearTimeout(debounceTimer);
|
|
207
|
+
debounceTimer = undefined;
|
|
208
|
+
}
|
|
209
|
+
listeners.clear();
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
get: () => state,
|
|
213
|
+
subscribe: (listener) => {
|
|
214
|
+
listeners.add(listener);
|
|
215
|
+
return () => {
|
|
216
|
+
listeners.delete(listener);
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
refetch,
|
|
220
|
+
close
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
var jsonFetcher = (url, init) => async (signal) => {
|
|
224
|
+
const response = await fetch(url, { ...init, signal });
|
|
225
|
+
if (!response.ok) {
|
|
226
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
227
|
+
}
|
|
228
|
+
return await response.json();
|
|
229
|
+
};
|
|
230
|
+
// src/client/syncCollection.ts
|
|
231
|
+
var localStorageMutationStorage = (key) => ({
|
|
232
|
+
load: () => {
|
|
233
|
+
const raw = globalThis.localStorage?.getItem(key);
|
|
234
|
+
return raw ? JSON.parse(raw) : [];
|
|
235
|
+
},
|
|
236
|
+
save: (records) => {
|
|
237
|
+
globalThis.localStorage?.setItem(key, JSON.stringify(records));
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
var SUBSCRIPTION_ID = "s";
|
|
241
|
+
var createSyncCollection = (options) => {
|
|
242
|
+
const key = options.key ?? ((row) => row.id);
|
|
243
|
+
const reconnectMs = options.reconnectMs ?? 500;
|
|
244
|
+
const maxReconnectMs = options.maxReconnectMs ?? 1e4;
|
|
245
|
+
const Impl = options.webSocketImpl ?? globalThis.WebSocket;
|
|
246
|
+
if (!Impl) {
|
|
247
|
+
throw new Error("createSyncCollection requires WebSocket. Run in a browser or pass webSocketImpl.");
|
|
248
|
+
}
|
|
249
|
+
const confirmed = new Map;
|
|
250
|
+
const pending = [];
|
|
251
|
+
let mutationSeq = 0;
|
|
252
|
+
let state = {
|
|
253
|
+
data: [],
|
|
254
|
+
status: "connecting",
|
|
255
|
+
error: undefined
|
|
256
|
+
};
|
|
257
|
+
const listeners = new Set;
|
|
258
|
+
const setState = (patch) => {
|
|
259
|
+
state = { ...state, ...patch };
|
|
260
|
+
for (const listener of listeners) {
|
|
261
|
+
listener(state);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
const recompute = (patch = {}) => {
|
|
265
|
+
const working = new Map(confirmed);
|
|
266
|
+
const draft = {
|
|
267
|
+
set: (row) => working.set(key(row), row),
|
|
268
|
+
delete: (rowKey) => working.delete(rowKey)
|
|
269
|
+
};
|
|
270
|
+
for (const mutation of pending) {
|
|
271
|
+
mutation.optimistic?.(draft);
|
|
272
|
+
}
|
|
273
|
+
setState({ ...patch, data: [...working.values()] });
|
|
274
|
+
};
|
|
275
|
+
let socket;
|
|
276
|
+
let connected = false;
|
|
277
|
+
let closed = false;
|
|
278
|
+
let attempt = 0;
|
|
279
|
+
let reconnectTimer;
|
|
280
|
+
let appliedVersion = 0;
|
|
281
|
+
const persist = () => {
|
|
282
|
+
options.storage?.save(pending.map((mutation) => ({
|
|
283
|
+
mutationId: mutation.mutationId,
|
|
284
|
+
name: mutation.name,
|
|
285
|
+
args: mutation.args
|
|
286
|
+
})));
|
|
287
|
+
};
|
|
288
|
+
const settlePending = (mutationId) => {
|
|
289
|
+
const index = pending.findIndex((mutation2) => mutation2.mutationId === mutationId);
|
|
290
|
+
if (index === -1) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const [mutation] = pending.splice(index, 1);
|
|
294
|
+
persist();
|
|
295
|
+
return mutation;
|
|
296
|
+
};
|
|
297
|
+
const applyFrame = (frame) => {
|
|
298
|
+
if (frame.type === "snapshot") {
|
|
299
|
+
confirmed.clear();
|
|
300
|
+
for (const row of frame.rows) {
|
|
301
|
+
confirmed.set(key(row), row);
|
|
302
|
+
}
|
|
303
|
+
if (frame.version !== undefined) {
|
|
304
|
+
appliedVersion = frame.version;
|
|
305
|
+
}
|
|
306
|
+
recompute({ status: "ready", error: undefined });
|
|
307
|
+
} else if (frame.type === "diff") {
|
|
308
|
+
for (const row of frame.removed) {
|
|
309
|
+
confirmed.delete(key(row));
|
|
310
|
+
}
|
|
311
|
+
for (const row of frame.added) {
|
|
312
|
+
confirmed.set(key(row), row);
|
|
313
|
+
}
|
|
314
|
+
for (const row of frame.changed) {
|
|
315
|
+
confirmed.set(key(row), row);
|
|
316
|
+
}
|
|
317
|
+
if (frame.version !== undefined) {
|
|
318
|
+
appliedVersion = Math.max(appliedVersion, frame.version);
|
|
319
|
+
}
|
|
320
|
+
recompute();
|
|
321
|
+
} else if (frame.type === "error") {
|
|
322
|
+
setState({ error: frame.message });
|
|
323
|
+
options.onError?.(frame.message);
|
|
324
|
+
} else if (frame.type === "ack") {
|
|
325
|
+
const mutation = settlePending(frame.mutationId);
|
|
326
|
+
if (mutation !== undefined) {
|
|
327
|
+
recompute();
|
|
328
|
+
mutation.resolve(frame.result);
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
const mutation = settlePending(frame.mutationId);
|
|
332
|
+
if (mutation !== undefined) {
|
|
333
|
+
recompute();
|
|
334
|
+
mutation.reject(new Error(String(frame.message)));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
const sendMutate = (mutation) => {
|
|
339
|
+
if (connected) {
|
|
340
|
+
socket?.send(JSON.stringify({
|
|
341
|
+
type: "mutate",
|
|
342
|
+
mutationId: mutation.mutationId,
|
|
343
|
+
name: mutation.name,
|
|
344
|
+
args: mutation.args
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
const connect = () => {
|
|
349
|
+
if (closed) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
setState({ status: "connecting" });
|
|
353
|
+
const ws = new Impl(options.url);
|
|
354
|
+
socket = ws;
|
|
355
|
+
ws.onopen = () => {
|
|
356
|
+
attempt = 0;
|
|
357
|
+
connected = true;
|
|
358
|
+
ws.send(JSON.stringify({
|
|
359
|
+
type: "subscribe",
|
|
360
|
+
id: SUBSCRIPTION_ID,
|
|
361
|
+
collection: options.collection,
|
|
362
|
+
params: options.params,
|
|
363
|
+
since: appliedVersion > 0 ? appliedVersion : undefined
|
|
364
|
+
}));
|
|
365
|
+
for (const mutation of pending) {
|
|
366
|
+
sendMutate(mutation);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
ws.onmessage = (event) => {
|
|
370
|
+
try {
|
|
371
|
+
applyFrame(JSON.parse(event.data));
|
|
372
|
+
} catch {}
|
|
373
|
+
};
|
|
374
|
+
ws.onclose = () => {
|
|
375
|
+
connected = false;
|
|
376
|
+
if (closed || reconnectMs <= 0) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const delay = Math.min(reconnectMs * 2 ** attempt, maxReconnectMs);
|
|
380
|
+
attempt += 1;
|
|
381
|
+
reconnectTimer = setTimeout(connect, delay);
|
|
382
|
+
};
|
|
383
|
+
};
|
|
384
|
+
connect();
|
|
385
|
+
const hydratePersisted = async () => {
|
|
386
|
+
if (options.storage === undefined) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const records = await options.storage.load();
|
|
390
|
+
for (const record of records) {
|
|
391
|
+
if (pending.some((m) => m.mutationId === record.mutationId)) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
pending.push({
|
|
395
|
+
mutationId: record.mutationId,
|
|
396
|
+
name: record.name,
|
|
397
|
+
args: record.args,
|
|
398
|
+
resolve: () => {},
|
|
399
|
+
reject: () => {}
|
|
400
|
+
});
|
|
401
|
+
mutationSeq = Math.max(mutationSeq, record.mutationId);
|
|
402
|
+
}
|
|
403
|
+
if (connected) {
|
|
404
|
+
for (const mutation of pending) {
|
|
405
|
+
sendMutate(mutation);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
hydratePersisted();
|
|
410
|
+
return {
|
|
411
|
+
get: () => state,
|
|
412
|
+
subscribe: (listener) => {
|
|
413
|
+
listeners.add(listener);
|
|
414
|
+
return () => {
|
|
415
|
+
listeners.delete(listener);
|
|
416
|
+
};
|
|
417
|
+
},
|
|
418
|
+
mutate: (mutateOptions) => new Promise((resolve, reject) => {
|
|
419
|
+
const mutation = {
|
|
420
|
+
mutationId: mutationSeq += 1,
|
|
421
|
+
name: mutateOptions.name,
|
|
422
|
+
args: mutateOptions.args,
|
|
423
|
+
optimistic: mutateOptions.optimistic,
|
|
424
|
+
resolve: (result) => resolve(result),
|
|
425
|
+
reject
|
|
426
|
+
};
|
|
427
|
+
pending.push(mutation);
|
|
428
|
+
persist();
|
|
429
|
+
recompute();
|
|
430
|
+
sendMutate(mutation);
|
|
431
|
+
}),
|
|
432
|
+
close: () => {
|
|
433
|
+
if (closed) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
closed = true;
|
|
437
|
+
connected = false;
|
|
438
|
+
if (reconnectTimer !== undefined) {
|
|
439
|
+
clearTimeout(reconnectTimer);
|
|
440
|
+
}
|
|
441
|
+
try {
|
|
442
|
+
socket?.send(JSON.stringify({ type: "unsubscribe", id: SUBSCRIPTION_ID }));
|
|
443
|
+
socket?.close();
|
|
444
|
+
} catch {}
|
|
445
|
+
for (const mutation of pending.splice(0)) {
|
|
446
|
+
mutation.reject(new Error("sync collection closed"));
|
|
447
|
+
}
|
|
448
|
+
persist();
|
|
449
|
+
setState({ status: "closed" });
|
|
450
|
+
listeners.clear();
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
// src/client/syncStore.ts
|
|
455
|
+
var SUBSCRIPTION_ID2 = "s";
|
|
456
|
+
var syncStore = (options) => {
|
|
457
|
+
const key = options.key ?? ((row) => row.id);
|
|
458
|
+
const reconnectMs = options.reconnectMs ?? 500;
|
|
459
|
+
const maxReconnectMs = options.maxReconnectMs ?? 1e4;
|
|
460
|
+
const reconcileGraceMs = options.reconcileGraceMs ?? 3000;
|
|
461
|
+
const mutations = options.mutations ?? {};
|
|
462
|
+
const Impl = options.webSocketImpl ?? globalThis.WebSocket;
|
|
463
|
+
if (!Impl) {
|
|
464
|
+
throw new Error("syncStore requires WebSocket. Run in a browser or pass webSocketImpl.");
|
|
465
|
+
}
|
|
466
|
+
const confirmed = new Map;
|
|
467
|
+
const pending = [];
|
|
468
|
+
let mutationSeq = 0;
|
|
469
|
+
let state = {
|
|
470
|
+
data: options.initialData ? [...options.initialData] : [],
|
|
471
|
+
status: "connecting",
|
|
472
|
+
error: undefined
|
|
473
|
+
};
|
|
474
|
+
if (options.initialData) {
|
|
475
|
+
for (const row of options.initialData) {
|
|
476
|
+
confirmed.set(key(row), row);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const listeners = new Set;
|
|
480
|
+
const setState = (patch) => {
|
|
481
|
+
state = { ...state, ...patch };
|
|
482
|
+
for (const listener of listeners) {
|
|
483
|
+
listener(state);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
const recompute = (patch = {}) => {
|
|
487
|
+
const working = new Map(confirmed);
|
|
488
|
+
const draft = {
|
|
489
|
+
set: (row) => working.set(key(row), row),
|
|
490
|
+
delete: (rowKey) => working.delete(rowKey)
|
|
491
|
+
};
|
|
492
|
+
for (const mutation of pending) {
|
|
493
|
+
mutation.optimistic?.(draft);
|
|
494
|
+
}
|
|
495
|
+
setState({ ...patch, data: [...working.values()] });
|
|
496
|
+
};
|
|
497
|
+
const persist = () => {
|
|
498
|
+
options.storage?.save(pending.map((mutation) => ({
|
|
499
|
+
mutationId: mutation.id,
|
|
500
|
+
name: mutation.name,
|
|
501
|
+
args: mutation.args
|
|
502
|
+
})));
|
|
503
|
+
};
|
|
504
|
+
const dropPending = (mutation) => {
|
|
505
|
+
const index = pending.indexOf(mutation);
|
|
506
|
+
if (index !== -1) {
|
|
507
|
+
pending.splice(index, 1);
|
|
508
|
+
}
|
|
509
|
+
if (mutation.graceTimer !== undefined) {
|
|
510
|
+
clearTimeout(mutation.graceTimer);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
const reconcileSettled = () => {
|
|
514
|
+
let changed = false;
|
|
515
|
+
for (const mutation of [...pending]) {
|
|
516
|
+
if (!mutation.settled) {
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
let reflected = true;
|
|
520
|
+
for (const [rowKey, kind] of mutation.touched) {
|
|
521
|
+
const present = confirmed.has(rowKey);
|
|
522
|
+
if (kind === "set" ? !present : present) {
|
|
523
|
+
reflected = false;
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (reflected) {
|
|
528
|
+
dropPending(mutation);
|
|
529
|
+
changed = true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (changed) {
|
|
533
|
+
recompute();
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
let socket;
|
|
537
|
+
let connected = false;
|
|
538
|
+
let closed = false;
|
|
539
|
+
let attempt = 0;
|
|
540
|
+
let reconnectTimer;
|
|
541
|
+
let appliedVersion = 0;
|
|
542
|
+
const applyFrame = (frame) => {
|
|
543
|
+
if (frame.type === "snapshot") {
|
|
544
|
+
confirmed.clear();
|
|
545
|
+
for (const row of frame.rows) {
|
|
546
|
+
confirmed.set(key(row), row);
|
|
547
|
+
}
|
|
548
|
+
if (frame.version !== undefined) {
|
|
549
|
+
appliedVersion = frame.version;
|
|
550
|
+
}
|
|
551
|
+
recompute({ status: "ready", error: undefined });
|
|
552
|
+
reconcileSettled();
|
|
553
|
+
} else if (frame.type === "diff") {
|
|
554
|
+
for (const row of frame.removed) {
|
|
555
|
+
confirmed.delete(key(row));
|
|
556
|
+
}
|
|
557
|
+
for (const row of frame.added) {
|
|
558
|
+
confirmed.set(key(row), row);
|
|
559
|
+
}
|
|
560
|
+
for (const row of frame.changed) {
|
|
561
|
+
confirmed.set(key(row), row);
|
|
562
|
+
}
|
|
563
|
+
if (frame.version !== undefined) {
|
|
564
|
+
appliedVersion = Math.max(appliedVersion, frame.version);
|
|
565
|
+
}
|
|
566
|
+
recompute();
|
|
567
|
+
reconcileSettled();
|
|
568
|
+
} else if (frame.type === "error") {
|
|
569
|
+
setState({ error: frame.message });
|
|
570
|
+
options.onError?.(frame.message);
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
const runMutation = async (mutation) => {
|
|
574
|
+
if (mutation.inFlight || mutation.settled) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const run = mutations[mutation.name];
|
|
578
|
+
if (run === undefined) {
|
|
579
|
+
dropPending(mutation);
|
|
580
|
+
recompute();
|
|
581
|
+
mutation.reject(new Error(`Unknown mutation "${mutation.name}"`));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
mutation.inFlight = true;
|
|
585
|
+
try {
|
|
586
|
+
const result = await run(mutation.args);
|
|
587
|
+
mutation.inFlight = false;
|
|
588
|
+
mutation.settled = true;
|
|
589
|
+
mutation.resolve(result);
|
|
590
|
+
persist();
|
|
591
|
+
reconcileSettled();
|
|
592
|
+
if (pending.includes(mutation)) {
|
|
593
|
+
mutation.graceTimer = setTimeout(() => {
|
|
594
|
+
dropPending(mutation);
|
|
595
|
+
recompute();
|
|
596
|
+
}, reconcileGraceMs);
|
|
597
|
+
}
|
|
598
|
+
} catch (error) {
|
|
599
|
+
mutation.inFlight = false;
|
|
600
|
+
if (connected) {
|
|
601
|
+
dropPending(mutation);
|
|
602
|
+
recompute();
|
|
603
|
+
persist();
|
|
604
|
+
mutation.reject(error);
|
|
605
|
+
} else {
|
|
606
|
+
options.onError?.(error);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
const connect = () => {
|
|
611
|
+
if (closed) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
setState({ status: "connecting" });
|
|
615
|
+
const ws = new Impl(options.url);
|
|
616
|
+
socket = ws;
|
|
617
|
+
ws.onopen = () => {
|
|
618
|
+
attempt = 0;
|
|
619
|
+
connected = true;
|
|
620
|
+
ws.send(JSON.stringify({
|
|
621
|
+
type: "subscribe",
|
|
622
|
+
id: SUBSCRIPTION_ID2,
|
|
623
|
+
collection: options.collection,
|
|
624
|
+
params: options.params,
|
|
625
|
+
since: appliedVersion > 0 ? appliedVersion : undefined
|
|
626
|
+
}));
|
|
627
|
+
for (const mutation of pending) {
|
|
628
|
+
if (!mutation.settled && !mutation.inFlight) {
|
|
629
|
+
runMutation(mutation);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
ws.onmessage = (event) => {
|
|
634
|
+
try {
|
|
635
|
+
applyFrame(JSON.parse(event.data));
|
|
636
|
+
} catch {}
|
|
637
|
+
};
|
|
638
|
+
ws.onclose = () => {
|
|
639
|
+
connected = false;
|
|
640
|
+
if (closed || reconnectMs <= 0) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const delay = Math.min(reconnectMs * 2 ** attempt, maxReconnectMs);
|
|
644
|
+
attempt += 1;
|
|
645
|
+
reconnectTimer = setTimeout(connect, delay);
|
|
646
|
+
};
|
|
647
|
+
};
|
|
648
|
+
const eagerHydrate = async () => {
|
|
649
|
+
if (options.hydrate === undefined || options.initialData !== undefined) {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
const rows = await options.hydrate();
|
|
654
|
+
if (state.status !== "ready") {
|
|
655
|
+
confirmed.clear();
|
|
656
|
+
for (const row of rows) {
|
|
657
|
+
confirmed.set(key(row), row);
|
|
658
|
+
}
|
|
659
|
+
recompute({ status: "ready" });
|
|
660
|
+
}
|
|
661
|
+
} catch (error) {
|
|
662
|
+
options.onError?.(error);
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
const hydratePersisted = async () => {
|
|
666
|
+
if (options.storage === undefined) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const records = await options.storage.load();
|
|
670
|
+
for (const record of records) {
|
|
671
|
+
if (pending.some((m) => m.id === record.mutationId)) {
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
pending.push({
|
|
675
|
+
id: record.mutationId,
|
|
676
|
+
name: record.name,
|
|
677
|
+
args: record.args,
|
|
678
|
+
touched: new Map,
|
|
679
|
+
settled: false,
|
|
680
|
+
inFlight: false,
|
|
681
|
+
resolve: () => {},
|
|
682
|
+
reject: () => {}
|
|
683
|
+
});
|
|
684
|
+
mutationSeq = Math.max(mutationSeq, record.mutationId);
|
|
685
|
+
}
|
|
686
|
+
if (connected) {
|
|
687
|
+
for (const mutation of pending) {
|
|
688
|
+
runMutation(mutation);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
connect();
|
|
693
|
+
eagerHydrate();
|
|
694
|
+
hydratePersisted();
|
|
695
|
+
const collectTouched = (optimistic) => {
|
|
696
|
+
const touched = new Map;
|
|
697
|
+
optimistic?.({
|
|
698
|
+
set: (row) => touched.set(key(row), "set"),
|
|
699
|
+
delete: (rowKey) => touched.set(rowKey, "delete")
|
|
700
|
+
});
|
|
701
|
+
return touched;
|
|
702
|
+
};
|
|
703
|
+
return {
|
|
704
|
+
get: () => state,
|
|
705
|
+
subscribe: (listener) => {
|
|
706
|
+
listeners.add(listener);
|
|
707
|
+
return () => {
|
|
708
|
+
listeners.delete(listener);
|
|
709
|
+
};
|
|
710
|
+
},
|
|
711
|
+
mutate: (name, args, mutateOptions) => new Promise((resolve, reject) => {
|
|
712
|
+
const mutation = {
|
|
713
|
+
id: mutationSeq += 1,
|
|
714
|
+
name,
|
|
715
|
+
args,
|
|
716
|
+
touched: collectTouched(mutateOptions?.optimistic),
|
|
717
|
+
optimistic: mutateOptions?.optimistic,
|
|
718
|
+
settled: false,
|
|
719
|
+
inFlight: false,
|
|
720
|
+
resolve,
|
|
721
|
+
reject
|
|
722
|
+
};
|
|
723
|
+
pending.push(mutation);
|
|
724
|
+
persist();
|
|
725
|
+
recompute();
|
|
726
|
+
runMutation(mutation);
|
|
727
|
+
}),
|
|
728
|
+
refetch: async () => {
|
|
729
|
+
if (options.hydrate === undefined) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const rows = await options.hydrate();
|
|
733
|
+
confirmed.clear();
|
|
734
|
+
for (const row of rows) {
|
|
735
|
+
confirmed.set(key(row), row);
|
|
736
|
+
}
|
|
737
|
+
recompute({ status: "ready" });
|
|
738
|
+
},
|
|
739
|
+
close: () => {
|
|
740
|
+
if (closed) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
closed = true;
|
|
744
|
+
connected = false;
|
|
745
|
+
if (reconnectTimer !== undefined) {
|
|
746
|
+
clearTimeout(reconnectTimer);
|
|
747
|
+
}
|
|
748
|
+
try {
|
|
749
|
+
socket?.send(JSON.stringify({ type: "unsubscribe", id: SUBSCRIPTION_ID2 }));
|
|
750
|
+
socket?.close();
|
|
751
|
+
} catch {}
|
|
752
|
+
for (const mutation of pending.splice(0)) {
|
|
753
|
+
if (mutation.graceTimer !== undefined) {
|
|
754
|
+
clearTimeout(mutation.graceTimer);
|
|
755
|
+
}
|
|
756
|
+
mutation.reject(new Error("sync store closed"));
|
|
757
|
+
}
|
|
758
|
+
persist();
|
|
759
|
+
setState({ status: "closed" });
|
|
760
|
+
listeners.clear();
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
};
|
|
764
|
+
var unwrapEden = async (response) => {
|
|
765
|
+
const { data, error } = await response;
|
|
766
|
+
if (error !== null && error !== undefined) {
|
|
767
|
+
throw error;
|
|
768
|
+
}
|
|
769
|
+
return data;
|
|
770
|
+
};
|
|
36
771
|
export {
|
|
37
|
-
|
|
772
|
+
unwrapEden,
|
|
773
|
+
syncStore,
|
|
774
|
+
localStorageMutationStorage,
|
|
775
|
+
jsonFetcher,
|
|
776
|
+
createSyncSubscriber,
|
|
777
|
+
createSyncCollection,
|
|
778
|
+
createLiveQuery
|
|
38
779
|
};
|
|
39
780
|
|
|
40
|
-
//# debugId=
|
|
781
|
+
//# debugId=5E158140B740DFD964756E2164756E21
|
|
41
782
|
//# sourceMappingURL=index.js.map
|