@absolutejs/sync 1.7.9 → 1.8.1
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 +24 -0
- package/dist/adapters/tanstack-db/index.d.ts +40 -0
- package/dist/adapters/tanstack-db/index.js +523 -0
- package/dist/adapters/tanstack-db/index.js.map +11 -0
- package/dist/engine/index.js +38 -21
- package/dist/engine/index.js.map +3 -3
- package/dist/engine/sandbox.d.ts +20 -20
- package/dist/index.js +38 -21
- package/dist/index.js.map +3 -3
- package/package.json +15 -5
package/README.md
CHANGED
|
@@ -217,6 +217,30 @@ await orders.mutate({
|
|
|
217
217
|
});
|
|
218
218
|
```
|
|
219
219
|
|
|
220
|
+
TanStack DB can own the client-side collection graph while Absolute Sync handles
|
|
221
|
+
the live transport:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { createCollection } from '@tanstack/db';
|
|
225
|
+
import { createSyncTanStackCollectionOptions } from '@absolutejs/sync/tanstack-db';
|
|
226
|
+
|
|
227
|
+
type Order = { id: string; total: number; status: string };
|
|
228
|
+
|
|
229
|
+
const orders = createCollection(
|
|
230
|
+
createSyncTanStackCollectionOptions<Order>({
|
|
231
|
+
id: 'orders',
|
|
232
|
+
url: 'ws://localhost:3000/sync/ws',
|
|
233
|
+
collection: 'orders',
|
|
234
|
+
getKey: (order) => order.id,
|
|
235
|
+
mutations: {
|
|
236
|
+
insert: 'createOrder',
|
|
237
|
+
update: 'updateOrder',
|
|
238
|
+
delete: 'deleteOrder'
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
);
|
|
242
|
+
```
|
|
243
|
+
|
|
220
244
|
- **Incremental vs refetch.** A single-table filtered collection is matched
|
|
221
245
|
incrementally (only the changed rows move). Joins/aggregations and filters the
|
|
222
246
|
matcher can't evaluate fall back to a correct re-hydrate. `createAggregate`
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CollectionConfig, PendingMutation } from '@tanstack/db';
|
|
2
|
+
import { type CollectionCache, type MutationStorage, type SyncCollection } from '../../client/syncCollection';
|
|
3
|
+
import type { RowKey } from '../../engine/types';
|
|
4
|
+
export type TanStackRowKey = Extract<RowKey, string | number>;
|
|
5
|
+
export type TanStackMutationCall = {
|
|
6
|
+
name: string;
|
|
7
|
+
args?: unknown;
|
|
8
|
+
};
|
|
9
|
+
export type TanStackMutationMapper<T extends object, TOperation extends 'insert' | 'update' | 'delete'> = string | ((mutation: PendingMutation<T, TOperation>) => TanStackMutationCall | undefined);
|
|
10
|
+
export type SyncTanStackMutations<T extends object> = {
|
|
11
|
+
insert?: TanStackMutationMapper<T, 'insert'>;
|
|
12
|
+
update?: TanStackMutationMapper<T, 'update'>;
|
|
13
|
+
delete?: TanStackMutationMapper<T, 'delete'>;
|
|
14
|
+
};
|
|
15
|
+
export type SyncTanStackCollectionOptions<T extends object, TKey extends TanStackRowKey = TanStackRowKey> = Omit<CollectionConfig<T, TKey>, 'sync' | 'getKey' | 'onInsert' | 'onUpdate' | 'onDelete'> & {
|
|
16
|
+
/** WebSocket URL of the Absolute Sync endpoint. */
|
|
17
|
+
url: string;
|
|
18
|
+
/** Registered Absolute Sync collection name. */
|
|
19
|
+
collection: string;
|
|
20
|
+
/** Query params forwarded to the server collection hydrate/match/authorize hooks. */
|
|
21
|
+
params?: unknown;
|
|
22
|
+
/** Row identity shared by TanStack DB and Absolute Sync. */
|
|
23
|
+
getKey: (row: T) => TKey;
|
|
24
|
+
webSocketImpl?: typeof WebSocket;
|
|
25
|
+
reconnectMs?: number;
|
|
26
|
+
maxReconnectMs?: number;
|
|
27
|
+
storage?: MutationStorage;
|
|
28
|
+
cache?: CollectionCache<T>;
|
|
29
|
+
onError?: (error: unknown) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Optional mapping from TanStack mutations to registered Absolute Sync
|
|
32
|
+
* mutation names. TanStack already applies optimistic writes, so forwarded
|
|
33
|
+
* sync mutations intentionally do not add another optimistic overlay.
|
|
34
|
+
*/
|
|
35
|
+
mutations?: SyncTanStackMutations<T>;
|
|
36
|
+
/** Optional prebuilt Absolute Sync collection, useful when sharing lifecycle externally. */
|
|
37
|
+
syncCollection?: SyncCollection<T>;
|
|
38
|
+
};
|
|
39
|
+
export declare const createSyncTanStackCollectionOptions: <T extends object, TKey extends TanStackRowKey = TanStackRowKey>(options: SyncTanStackCollectionOptions<T, TKey>) => CollectionConfig<T, TKey>;
|
|
40
|
+
export { createSyncTanStackCollectionOptions as syncTanStackCollectionOptions };
|
|
@@ -0,0 +1,523 @@
|
|
|
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/syncCollection.ts
|
|
72
|
+
var localStorageMutationStorage = (key) => ({
|
|
73
|
+
load: () => {
|
|
74
|
+
const raw = globalThis.localStorage?.getItem(key);
|
|
75
|
+
return raw ? JSON.parse(raw) : [];
|
|
76
|
+
},
|
|
77
|
+
save: (records) => {
|
|
78
|
+
globalThis.localStorage?.setItem(key, JSON.stringify(records));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
var localStorageCollectionCache = (key) => ({
|
|
82
|
+
load: () => {
|
|
83
|
+
const raw = globalThis.localStorage?.getItem(key);
|
|
84
|
+
return raw ? JSON.parse(raw) : undefined;
|
|
85
|
+
},
|
|
86
|
+
save: (snapshot) => {
|
|
87
|
+
globalThis.localStorage?.setItem(key, JSON.stringify(snapshot));
|
|
88
|
+
},
|
|
89
|
+
clear: () => {
|
|
90
|
+
globalThis.localStorage?.removeItem(key);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
var openIndexedDb = (databaseName, storeName) => new Promise((resolve, reject) => {
|
|
94
|
+
const request = globalThis.indexedDB.open(databaseName, 1);
|
|
95
|
+
request.onupgradeneeded = () => {
|
|
96
|
+
request.result.createObjectStore(storeName);
|
|
97
|
+
};
|
|
98
|
+
request.onsuccess = () => resolve(request.result);
|
|
99
|
+
request.onerror = () => reject(request.error);
|
|
100
|
+
});
|
|
101
|
+
var indexedDbCollectionCache = ({
|
|
102
|
+
key,
|
|
103
|
+
databaseName = "absolutejs-sync",
|
|
104
|
+
storeName = "collections"
|
|
105
|
+
}) => {
|
|
106
|
+
let handle;
|
|
107
|
+
const database = () => {
|
|
108
|
+
handle ??= openIndexedDb(databaseName, storeName);
|
|
109
|
+
return handle;
|
|
110
|
+
};
|
|
111
|
+
const withStore = async (mode, run) => {
|
|
112
|
+
if (globalThis.indexedDB === undefined) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const db = await database();
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const request = run(db.transaction(storeName, mode).objectStore(storeName));
|
|
118
|
+
request.onsuccess = () => resolve(request.result);
|
|
119
|
+
request.onerror = () => reject(request.error);
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
load: () => withStore("readonly", (store) => store.get(key)),
|
|
124
|
+
save: async (snapshot) => {
|
|
125
|
+
await withStore("readwrite", (store) => store.put(snapshot, key));
|
|
126
|
+
},
|
|
127
|
+
clear: async () => {
|
|
128
|
+
await withStore("readwrite", (store) => store.delete(key));
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
var SUBSCRIPTION_ID = "s";
|
|
133
|
+
var createSyncCollection = (options) => {
|
|
134
|
+
const key = options.key ?? ((row) => row.id);
|
|
135
|
+
const reconnectMs = options.reconnectMs ?? 500;
|
|
136
|
+
const maxReconnectMs = options.maxReconnectMs ?? 1e4;
|
|
137
|
+
const Impl = options.webSocketImpl ?? globalThis.WebSocket;
|
|
138
|
+
if (!Impl) {
|
|
139
|
+
throw new Error("createSyncCollection requires WebSocket. Run in a browser or pass webSocketImpl.");
|
|
140
|
+
}
|
|
141
|
+
const confirmed = new Map;
|
|
142
|
+
const pending = [];
|
|
143
|
+
let mutationSeq = 0;
|
|
144
|
+
let state = {
|
|
145
|
+
data: [],
|
|
146
|
+
status: "connecting",
|
|
147
|
+
error: undefined
|
|
148
|
+
};
|
|
149
|
+
const listeners = new Set;
|
|
150
|
+
const setState = (patch) => {
|
|
151
|
+
state = { ...state, ...patch };
|
|
152
|
+
for (const listener of listeners) {
|
|
153
|
+
listener(state);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const recompute = (patch = {}) => {
|
|
157
|
+
const working = new Map(confirmed);
|
|
158
|
+
const draft = {
|
|
159
|
+
set: (row) => working.set(key(row), row),
|
|
160
|
+
delete: (rowKey) => working.delete(rowKey)
|
|
161
|
+
};
|
|
162
|
+
for (const mutation of pending) {
|
|
163
|
+
mutation.optimistic?.(draft);
|
|
164
|
+
}
|
|
165
|
+
setState({ ...patch, data: [...working.values()] });
|
|
166
|
+
};
|
|
167
|
+
let socket;
|
|
168
|
+
let connected = false;
|
|
169
|
+
let closed = false;
|
|
170
|
+
let attempt = 0;
|
|
171
|
+
let reconnectTimer;
|
|
172
|
+
let appliedVersion = 0;
|
|
173
|
+
const persist = () => {
|
|
174
|
+
options.storage?.save(pending.map((mutation) => ({
|
|
175
|
+
mutationId: mutation.mutationId,
|
|
176
|
+
name: mutation.name,
|
|
177
|
+
args: mutation.args
|
|
178
|
+
})));
|
|
179
|
+
};
|
|
180
|
+
let cacheScheduled = false;
|
|
181
|
+
const persistCache = () => {
|
|
182
|
+
if (options.cache === undefined || cacheScheduled) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
cacheScheduled = true;
|
|
186
|
+
queueMicrotask(() => {
|
|
187
|
+
cacheScheduled = false;
|
|
188
|
+
options.cache?.save({
|
|
189
|
+
rows: [...confirmed.values()],
|
|
190
|
+
version: appliedVersion
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
const settlePending = (mutationId) => {
|
|
195
|
+
const index = pending.findIndex((mutation2) => mutation2.mutationId === mutationId);
|
|
196
|
+
if (index === -1) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const [mutation] = pending.splice(index, 1);
|
|
200
|
+
persist();
|
|
201
|
+
return mutation;
|
|
202
|
+
};
|
|
203
|
+
const applyFrame = (frame) => {
|
|
204
|
+
if (frame.type === "snapshot") {
|
|
205
|
+
confirmed.clear();
|
|
206
|
+
for (const row of frame.rows) {
|
|
207
|
+
confirmed.set(key(row), row);
|
|
208
|
+
}
|
|
209
|
+
if (frame.version !== undefined) {
|
|
210
|
+
appliedVersion = frame.version;
|
|
211
|
+
}
|
|
212
|
+
persistCache();
|
|
213
|
+
recompute({ status: "ready", error: undefined });
|
|
214
|
+
} else if (frame.type === "diff") {
|
|
215
|
+
for (const row of frame.removed) {
|
|
216
|
+
confirmed.delete(key(row));
|
|
217
|
+
}
|
|
218
|
+
for (const row of frame.added) {
|
|
219
|
+
confirmed.set(key(row), row);
|
|
220
|
+
}
|
|
221
|
+
for (const row of frame.changed) {
|
|
222
|
+
confirmed.set(key(row), row);
|
|
223
|
+
}
|
|
224
|
+
if (frame.version !== undefined) {
|
|
225
|
+
appliedVersion = Math.max(appliedVersion, frame.version);
|
|
226
|
+
}
|
|
227
|
+
persistCache();
|
|
228
|
+
recompute({ status: "ready", error: undefined });
|
|
229
|
+
} else if (frame.type === "error") {
|
|
230
|
+
setState({ error: frame.message });
|
|
231
|
+
options.onError?.(frame.message);
|
|
232
|
+
} else if (frame.type === "ack") {
|
|
233
|
+
const mutation = settlePending(frame.mutationId);
|
|
234
|
+
if (mutation !== undefined) {
|
|
235
|
+
recompute();
|
|
236
|
+
mutation.resolve(frame.result);
|
|
237
|
+
}
|
|
238
|
+
} else if (frame.type === "reject") {
|
|
239
|
+
const mutation = settlePending(frame.mutationId);
|
|
240
|
+
if (mutation !== undefined) {
|
|
241
|
+
recompute();
|
|
242
|
+
mutation.reject(new Error(String(frame.message)));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
const sendMutate = (mutation) => {
|
|
247
|
+
if (connected) {
|
|
248
|
+
socket?.send(JSON.stringify({
|
|
249
|
+
type: "mutate",
|
|
250
|
+
mutationId: mutation.mutationId,
|
|
251
|
+
name: mutation.name,
|
|
252
|
+
args: mutation.args
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const connect = () => {
|
|
257
|
+
if (closed) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
setState({ status: "connecting" });
|
|
261
|
+
const ws = new Impl(options.url);
|
|
262
|
+
socket = ws;
|
|
263
|
+
ws.onopen = () => {
|
|
264
|
+
attempt = 0;
|
|
265
|
+
connected = true;
|
|
266
|
+
ws.send(JSON.stringify({
|
|
267
|
+
type: "subscribe",
|
|
268
|
+
id: SUBSCRIPTION_ID,
|
|
269
|
+
collection: options.collection,
|
|
270
|
+
params: options.params,
|
|
271
|
+
since: appliedVersion > 0 ? appliedVersion : undefined
|
|
272
|
+
}));
|
|
273
|
+
for (const mutation of pending) {
|
|
274
|
+
sendMutate(mutation);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
ws.onmessage = (event) => {
|
|
278
|
+
try {
|
|
279
|
+
applyFrame(JSON.parse(event.data));
|
|
280
|
+
} catch {}
|
|
281
|
+
};
|
|
282
|
+
ws.onclose = () => {
|
|
283
|
+
connected = false;
|
|
284
|
+
if (closed || reconnectMs <= 0) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const delay = Math.min(reconnectMs * 2 ** attempt, maxReconnectMs);
|
|
288
|
+
attempt += 1;
|
|
289
|
+
reconnectTimer = setTimeout(connect, delay);
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
const hydratePersisted = async () => {
|
|
293
|
+
if (options.storage === undefined) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const records = await options.storage.load();
|
|
297
|
+
for (const record of records) {
|
|
298
|
+
if (pending.some((m) => m.mutationId === record.mutationId)) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
pending.push({
|
|
302
|
+
mutationId: record.mutationId,
|
|
303
|
+
name: record.name,
|
|
304
|
+
args: record.args,
|
|
305
|
+
resolve: () => {},
|
|
306
|
+
reject: () => {}
|
|
307
|
+
});
|
|
308
|
+
mutationSeq = Math.max(mutationSeq, record.mutationId);
|
|
309
|
+
}
|
|
310
|
+
if (connected) {
|
|
311
|
+
for (const mutation of pending) {
|
|
312
|
+
sendMutate(mutation);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const hydrateCache = async () => {
|
|
317
|
+
if (options.cache === undefined) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
let snapshot;
|
|
321
|
+
try {
|
|
322
|
+
snapshot = await options.cache.load();
|
|
323
|
+
} catch {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (snapshot === undefined || appliedVersion > 0) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
for (const row of snapshot.rows) {
|
|
330
|
+
confirmed.set(key(row), row);
|
|
331
|
+
}
|
|
332
|
+
appliedVersion = snapshot.version;
|
|
333
|
+
recompute();
|
|
334
|
+
};
|
|
335
|
+
if (options.cache === undefined) {
|
|
336
|
+
connect();
|
|
337
|
+
hydratePersisted();
|
|
338
|
+
} else {
|
|
339
|
+
(async () => {
|
|
340
|
+
await hydrateCache();
|
|
341
|
+
await hydratePersisted();
|
|
342
|
+
connect();
|
|
343
|
+
})();
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
get: () => state,
|
|
347
|
+
subscribe: (listener) => {
|
|
348
|
+
listeners.add(listener);
|
|
349
|
+
return () => {
|
|
350
|
+
listeners.delete(listener);
|
|
351
|
+
};
|
|
352
|
+
},
|
|
353
|
+
mutate: (mutateOptions) => new Promise((resolve, reject) => {
|
|
354
|
+
const mutation = {
|
|
355
|
+
mutationId: mutationSeq += 1,
|
|
356
|
+
name: mutateOptions.name,
|
|
357
|
+
args: mutateOptions.args,
|
|
358
|
+
optimistic: mutateOptions.optimistic,
|
|
359
|
+
resolve: (result) => resolve(result),
|
|
360
|
+
reject
|
|
361
|
+
};
|
|
362
|
+
pending.push(mutation);
|
|
363
|
+
persist();
|
|
364
|
+
recompute();
|
|
365
|
+
sendMutate(mutation);
|
|
366
|
+
}),
|
|
367
|
+
disconnect: () => {
|
|
368
|
+
if (closed || socket === undefined) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
socket.close();
|
|
373
|
+
} catch {}
|
|
374
|
+
},
|
|
375
|
+
close: () => {
|
|
376
|
+
if (closed) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
closed = true;
|
|
380
|
+
connected = false;
|
|
381
|
+
if (reconnectTimer !== undefined) {
|
|
382
|
+
clearTimeout(reconnectTimer);
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
socket?.send(JSON.stringify({ type: "unsubscribe", id: SUBSCRIPTION_ID }));
|
|
386
|
+
socket?.close();
|
|
387
|
+
} catch {}
|
|
388
|
+
for (const mutation of pending.splice(0)) {
|
|
389
|
+
mutation.reject(new Error("sync collection closed"));
|
|
390
|
+
}
|
|
391
|
+
persist();
|
|
392
|
+
setState({ status: "closed" });
|
|
393
|
+
listeners.clear();
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// src/adapters/tanstack-db/index.ts
|
|
399
|
+
var toMutationCall = (mapper, mutation) => {
|
|
400
|
+
if (typeof mapper === "function") {
|
|
401
|
+
return mapper(mutation);
|
|
402
|
+
}
|
|
403
|
+
if (mutation.type === "insert") {
|
|
404
|
+
return {
|
|
405
|
+
name: mapper,
|
|
406
|
+
args: { row: mutation.modified, metadata: mutation.metadata }
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
if (mutation.type === "update") {
|
|
410
|
+
return {
|
|
411
|
+
name: mapper,
|
|
412
|
+
args: {
|
|
413
|
+
key: mutation.key,
|
|
414
|
+
row: mutation.modified,
|
|
415
|
+
changes: mutation.changes,
|
|
416
|
+
metadata: mutation.metadata
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
name: mapper,
|
|
422
|
+
args: {
|
|
423
|
+
key: mutation.key,
|
|
424
|
+
row: mutation.original,
|
|
425
|
+
metadata: mutation.metadata
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
var createMutationHandler = (sync, mapper) => async ({
|
|
430
|
+
transaction
|
|
431
|
+
}) => {
|
|
432
|
+
if (mapper === undefined) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
await Promise.all(transaction.mutations.map((mutation) => {
|
|
436
|
+
const call = toMutationCall(mapper, mutation);
|
|
437
|
+
return call === undefined ? Promise.resolve() : sync.mutate({ name: call.name, args: call.args });
|
|
438
|
+
}));
|
|
439
|
+
};
|
|
440
|
+
var createSyncConfig = (sync, getKey) => ({
|
|
441
|
+
rowUpdateMode: "full",
|
|
442
|
+
sync: ({ begin, write, commit, markReady }) => {
|
|
443
|
+
let previous = new Map;
|
|
444
|
+
let markedReady = false;
|
|
445
|
+
const flush = () => {
|
|
446
|
+
const state = sync.get();
|
|
447
|
+
const next = new Map;
|
|
448
|
+
for (const row of state.data) {
|
|
449
|
+
next.set(getKey(row), row);
|
|
450
|
+
}
|
|
451
|
+
begin();
|
|
452
|
+
for (const [key, row] of next) {
|
|
453
|
+
const old = previous.get(key);
|
|
454
|
+
if (old === undefined) {
|
|
455
|
+
write({ type: "insert", value: row });
|
|
456
|
+
} else if (!Object.is(old, row)) {
|
|
457
|
+
write({ type: "update", value: row, previousValue: old });
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
for (const key of previous.keys()) {
|
|
461
|
+
if (!next.has(key)) {
|
|
462
|
+
write({ type: "delete", key });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
previous = next;
|
|
466
|
+
commit();
|
|
467
|
+
if (state.status === "ready" && !markedReady) {
|
|
468
|
+
markedReady = true;
|
|
469
|
+
markReady();
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
flush();
|
|
473
|
+
const unsubscribe = sync.subscribe(flush);
|
|
474
|
+
return () => {
|
|
475
|
+
unsubscribe();
|
|
476
|
+
sync.close();
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
var createSyncTanStackCollectionOptions = (options) => {
|
|
481
|
+
const {
|
|
482
|
+
url,
|
|
483
|
+
collection,
|
|
484
|
+
params,
|
|
485
|
+
getKey,
|
|
486
|
+
webSocketImpl,
|
|
487
|
+
reconnectMs,
|
|
488
|
+
maxReconnectMs,
|
|
489
|
+
storage,
|
|
490
|
+
cache,
|
|
491
|
+
onError,
|
|
492
|
+
mutations,
|
|
493
|
+
syncCollection,
|
|
494
|
+
...collectionOptions
|
|
495
|
+
} = options;
|
|
496
|
+
const sync = syncCollection ?? createSyncCollection({
|
|
497
|
+
url,
|
|
498
|
+
collection,
|
|
499
|
+
params,
|
|
500
|
+
key: getKey,
|
|
501
|
+
webSocketImpl,
|
|
502
|
+
reconnectMs,
|
|
503
|
+
maxReconnectMs,
|
|
504
|
+
storage,
|
|
505
|
+
cache,
|
|
506
|
+
onError
|
|
507
|
+
});
|
|
508
|
+
return {
|
|
509
|
+
...collectionOptions,
|
|
510
|
+
getKey,
|
|
511
|
+
sync: createSyncConfig(sync, getKey),
|
|
512
|
+
onInsert: createMutationHandler(sync, mutations?.insert),
|
|
513
|
+
onUpdate: createMutationHandler(sync, mutations?.update),
|
|
514
|
+
onDelete: createMutationHandler(sync, mutations?.delete)
|
|
515
|
+
};
|
|
516
|
+
};
|
|
517
|
+
export {
|
|
518
|
+
createSyncTanStackCollectionOptions as syncTanStackCollectionOptions,
|
|
519
|
+
createSyncTanStackCollectionOptions
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
//# debugId=87BE1F94279A349864756E2164756E21
|
|
523
|
+
//# sourceMappingURL=index.js.map
|