@sync-subscribe/client 0.3.2
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/LICENSE +21 -0
- package/README.md +430 -0
- package/dist/fetchTransport.d.ts +27 -0
- package/dist/fetchTransport.d.ts.map +1 -0
- package/dist/fetchTransport.js +88 -0
- package/dist/fetchTransport.js.map +1 -0
- package/dist/idbLocalStore.d.ts +45 -0
- package/dist/idbLocalStore.d.ts.map +1 -0
- package/dist/idbLocalStore.js +282 -0
- package/dist/idbLocalStore.js.map +1 -0
- package/dist/inMemoryStore.d.ts +36 -0
- package/dist/inMemoryStore.d.ts.map +1 -0
- package/dist/inMemoryStore.js +127 -0
- package/dist/inMemoryStore.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/syncClient.d.ts +111 -0
- package/dist/syncClient.d.ts.map +1 -0
- package/dist/syncClient.js +568 -0
- package/dist/syncClient.js.map +1 -0
- package/dist/types.d.ts +131 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import { filtersEqual, filterUnion, negateFilter, simplifyFilter, isAlwaysFalse, EMPTY_SYNC_TOKEN, } from "@sync-subscribe/core";
|
|
2
|
+
import { InMemoryStore } from "./inMemoryStore.js";
|
|
3
|
+
/**
|
|
4
|
+
* High-level client that manages subscriptions, local state, and sync cycles.
|
|
5
|
+
*
|
|
6
|
+
* Pass a custom `store` to use IndexedDB persistence:
|
|
7
|
+
* new SyncClient(transport, new IdbLocalStore("my-db"))
|
|
8
|
+
*
|
|
9
|
+
* Omit `store` to use the default in-memory store.
|
|
10
|
+
*/
|
|
11
|
+
export class SyncClient {
|
|
12
|
+
transport;
|
|
13
|
+
schema;
|
|
14
|
+
listeners = [];
|
|
15
|
+
activeSubs = new Map(); // keyed by subscriptionId
|
|
16
|
+
subActiveListeners = new Map(); // keyed by subscriptionId
|
|
17
|
+
_stopStream = null;
|
|
18
|
+
_streamTimer = null;
|
|
19
|
+
pendingPull = null;
|
|
20
|
+
store;
|
|
21
|
+
constructor(transport, store,
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
23
|
+
schema) {
|
|
24
|
+
this.transport = transport;
|
|
25
|
+
this.schema = schema;
|
|
26
|
+
this.store = store ?? new InMemoryStore();
|
|
27
|
+
}
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Subscriptions
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/** Serial queue — subscribe() calls run one at a time to prevent races. */
|
|
32
|
+
subscribeQueue = Promise.resolve();
|
|
33
|
+
subscribe(options) {
|
|
34
|
+
const next = this.subscribeQueue.then(() => this._subscribe(options));
|
|
35
|
+
// Swallow errors on the shared chain so a failure doesn't block future calls.
|
|
36
|
+
this.subscribeQueue = next.catch(() => { });
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
39
|
+
async _subscribe(options) {
|
|
40
|
+
const { name } = options;
|
|
41
|
+
const inputFilter = simplifyFilter(options.filter);
|
|
42
|
+
let previousSubscriptionId = options.previousSubscriptionId;
|
|
43
|
+
let storedSub;
|
|
44
|
+
// Restore from store if named and no explicit previousSubscriptionId
|
|
45
|
+
if (name && previousSubscriptionId === undefined) {
|
|
46
|
+
storedSub = await this.store.getSubscription(name);
|
|
47
|
+
if (storedSub) {
|
|
48
|
+
previousSubscriptionId = storedSub.subscriptionId;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (previousSubscriptionId !== undefined) {
|
|
52
|
+
storedSub = await this.store.getSubscriptionById(previousSubscriptionId);
|
|
53
|
+
}
|
|
54
|
+
// If filter is unchanged, reuse existing subscription (restores status too)
|
|
55
|
+
if (storedSub && filtersEqual(storedSub.filter, inputFilter)) {
|
|
56
|
+
const result = {
|
|
57
|
+
...storedSub,
|
|
58
|
+
...(name !== undefined && { name }),
|
|
59
|
+
};
|
|
60
|
+
this.activeSubs.set(result.subscriptionId, result);
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
// Generate a client-side UUID — no server call needed
|
|
64
|
+
const newSubscriptionId = crypto.randomUUID();
|
|
65
|
+
let newSub = {
|
|
66
|
+
subscriptionId: newSubscriptionId,
|
|
67
|
+
filter: inputFilter,
|
|
68
|
+
syncToken: EMPTY_SYNC_TOKEN,
|
|
69
|
+
...(name !== undefined && { name }),
|
|
70
|
+
status: "active",
|
|
71
|
+
};
|
|
72
|
+
const key = name ?? newSubscriptionId;
|
|
73
|
+
// Load all persisted subscriptions from the store (source of truth — activeSubs
|
|
74
|
+
// only reflects the current session). Exclude the old sub being replaced, if any.
|
|
75
|
+
const allPersistedSubs = await this.store.listSubscriptions();
|
|
76
|
+
const existingFilters = allPersistedSubs
|
|
77
|
+
.filter((s) => s.subscriptionId !== storedSub?.subscriptionId)
|
|
78
|
+
.filter((s) => s.syncToken !== EMPTY_SYNC_TOKEN)
|
|
79
|
+
.map((s) => s.filter);
|
|
80
|
+
// Check 1: is the new filter entirely covered by existing data?
|
|
81
|
+
// Check 2: does the old filter contain items that need eviction?
|
|
82
|
+
let newFilterPositive = existingFilters;
|
|
83
|
+
let evictionFilter;
|
|
84
|
+
if (storedSub) {
|
|
85
|
+
newFilterPositive = [
|
|
86
|
+
...existingFilters,
|
|
87
|
+
storedSub.filter,
|
|
88
|
+
];
|
|
89
|
+
evictionFilter = [
|
|
90
|
+
...existingFilters,
|
|
91
|
+
inputFilter,
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
if (newFilterPositive.length > 0) {
|
|
95
|
+
newSub = await this.checkForGap(newFilterPositive, inputFilter, newSub);
|
|
96
|
+
}
|
|
97
|
+
if (storedSub && evictionFilter && evictionFilter.length > 0) {
|
|
98
|
+
await this.checkForEviction(evictionFilter, storedSub.filter);
|
|
99
|
+
}
|
|
100
|
+
// Remove old subscription from store and in-memory map
|
|
101
|
+
if (storedSub) {
|
|
102
|
+
const oldKey = storedSub.name ?? storedSub.subscriptionId;
|
|
103
|
+
await this.store.removeSubscription(oldKey);
|
|
104
|
+
this.activeSubs.delete(storedSub.subscriptionId);
|
|
105
|
+
}
|
|
106
|
+
await this.store.setSubscription(key, newSub);
|
|
107
|
+
this.activeSubs.set(newSubscriptionId, newSub);
|
|
108
|
+
// Automatically kick off a pull and (re)start the stream for the new subscription.
|
|
109
|
+
void this.schedulePull();
|
|
110
|
+
this._syncStream();
|
|
111
|
+
return newSub;
|
|
112
|
+
}
|
|
113
|
+
async checkForEviction(existingFilters, oldFilter) {
|
|
114
|
+
const existingUnion = filterUnion(...existingFilters);
|
|
115
|
+
const negatedUnion = negateFilter(existingUnion);
|
|
116
|
+
const rawGap = {
|
|
117
|
+
$and: [oldFilter, negatedUnion],
|
|
118
|
+
};
|
|
119
|
+
if (isAlwaysFalse(rawGap)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const fGap = simplifyFilter(rawGap);
|
|
123
|
+
await this.store.evict(fGap);
|
|
124
|
+
}
|
|
125
|
+
/** Checks if the new inputFilter results in a gap we need to fill */
|
|
126
|
+
async checkForGap(existingFilters, inputFilter, newSub) {
|
|
127
|
+
const existingUnion = filterUnion(...existingFilters);
|
|
128
|
+
const negatedUnion = negateFilter(existingUnion);
|
|
129
|
+
const rawGap = {
|
|
130
|
+
$and: [inputFilter, negatedUnion],
|
|
131
|
+
};
|
|
132
|
+
if (isAlwaysFalse(rawGap)) {
|
|
133
|
+
// F_new is fully covered — every possible record matching F_new is already
|
|
134
|
+
// served by an existing subscription. Reconstruct token from local data.
|
|
135
|
+
const reconstructed = await this.store.reconstructSyncToken(inputFilter);
|
|
136
|
+
return {
|
|
137
|
+
...newSub,
|
|
138
|
+
status: "active",
|
|
139
|
+
syncToken: reconstructed !== EMPTY_SYNC_TOKEN ? reconstructed : newSub.syncToken,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// Check if F_new and existing subs are completely disjoint (no overlap at all).
|
|
143
|
+
// In this case there's no local data to reuse — use an empty token directly.
|
|
144
|
+
const rawIntersection = {
|
|
145
|
+
$and: [inputFilter, existingUnion],
|
|
146
|
+
};
|
|
147
|
+
if (isAlwaysFalse(rawIntersection)) {
|
|
148
|
+
return newSub;
|
|
149
|
+
}
|
|
150
|
+
// Gap exists — simplify the gap filter (safe after always-false check).
|
|
151
|
+
const fGap = simplifyFilter(rawGap);
|
|
152
|
+
const gapSubId = crypto.randomUUID();
|
|
153
|
+
const gapSub = {
|
|
154
|
+
subscriptionId: gapSubId,
|
|
155
|
+
filter: fGap,
|
|
156
|
+
syncToken: EMPTY_SYNC_TOKEN,
|
|
157
|
+
status: "active",
|
|
158
|
+
};
|
|
159
|
+
await this.store.setSubscription(gapSubId, gapSub);
|
|
160
|
+
return {
|
|
161
|
+
...newSub,
|
|
162
|
+
status: "pending_gap_fill",
|
|
163
|
+
gapSubscriptionId: gapSubId,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Update an existing subscription to use a new filter.
|
|
168
|
+
* Delegates to subscribe() with previousSubscriptionId set.
|
|
169
|
+
*/
|
|
170
|
+
async updateSubscription(subscriptionId, newFilter) {
|
|
171
|
+
const existing = this.activeSubs.get(subscriptionId) ??
|
|
172
|
+
(await this.store.getSubscriptionById(subscriptionId));
|
|
173
|
+
if (!existing)
|
|
174
|
+
throw new Error(`Unknown subscription: ${subscriptionId}`);
|
|
175
|
+
return this.subscribe({
|
|
176
|
+
filter: newFilter,
|
|
177
|
+
previousSubscriptionId: subscriptionId,
|
|
178
|
+
...(existing.name !== undefined && { name: existing.name }),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Sync
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
/**
|
|
185
|
+
* Pull patches for all subscriptions.
|
|
186
|
+
* - ACTIVE subscriptions are included directly.
|
|
187
|
+
* - PENDING subscriptions are excluded; their gap sub is included instead.
|
|
188
|
+
* When a gap sub's token appears in the response the gap is complete:
|
|
189
|
+
* the gap sub is removed, the parent subscription reconstructs its token
|
|
190
|
+
* and transitions to active.
|
|
191
|
+
*/
|
|
192
|
+
async pull() {
|
|
193
|
+
const subsForPull = [];
|
|
194
|
+
// Maps gapSubId → parentSubId so we can detect gap completion
|
|
195
|
+
const gapSubToParent = new Map();
|
|
196
|
+
for (const sub of this.activeSubs.values()) {
|
|
197
|
+
const status = sub.status ?? "active";
|
|
198
|
+
if (status === "active") {
|
|
199
|
+
subsForPull.push({
|
|
200
|
+
key: sub.subscriptionId,
|
|
201
|
+
filter: sub.filter,
|
|
202
|
+
syncToken: sub.syncToken,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else if (status === "pending_gap_fill" && sub.gapSubscriptionId) {
|
|
206
|
+
const gapSub = await this.store.getSubscriptionById(sub.gapSubscriptionId);
|
|
207
|
+
if (gapSub) {
|
|
208
|
+
subsForPull.push({
|
|
209
|
+
key: gapSub.subscriptionId,
|
|
210
|
+
filter: gapSub.filter,
|
|
211
|
+
syncToken: gapSub.syncToken,
|
|
212
|
+
});
|
|
213
|
+
gapSubToParent.set(gapSub.subscriptionId, sub.subscriptionId);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (subsForPull.length === 0)
|
|
218
|
+
return;
|
|
219
|
+
const { patches, syncTokens } = await this.transport.pull(subsForPull);
|
|
220
|
+
const applied = await this.store.applyPatches(patches);
|
|
221
|
+
for (const [id, syncToken] of Object.entries(syncTokens)) {
|
|
222
|
+
const parentSubId = gapSubToParent.get(id);
|
|
223
|
+
if (parentSubId) {
|
|
224
|
+
// Gap sub received a response — gap is filled.
|
|
225
|
+
const parentSub = this.activeSubs.get(parentSubId);
|
|
226
|
+
// Remove gap sub from local store only (server is stateless)
|
|
227
|
+
await this.store.removeSubscription(id);
|
|
228
|
+
// Reconstruct the parent's sync token from the now-complete local data
|
|
229
|
+
const reconstructed = await this.store.reconstructSyncToken(parentSub.filter);
|
|
230
|
+
const activeSub = {
|
|
231
|
+
...parentSub,
|
|
232
|
+
status: "active",
|
|
233
|
+
syncToken: reconstructed !== EMPTY_SYNC_TOKEN
|
|
234
|
+
? reconstructed
|
|
235
|
+
: parentSub.syncToken,
|
|
236
|
+
};
|
|
237
|
+
delete activeSub.gapSubscriptionId;
|
|
238
|
+
const key = parentSub.name ?? parentSub.subscriptionId;
|
|
239
|
+
await this.store.setSubscription(key, activeSub);
|
|
240
|
+
this.activeSubs.set(parentSubId, activeSub);
|
|
241
|
+
this._emitSubscriptionActive(parentSubId);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// Normal active sub — update its token
|
|
245
|
+
const sub = this.activeSubs.get(id);
|
|
246
|
+
if (sub) {
|
|
247
|
+
const updated = { ...sub, syncToken };
|
|
248
|
+
const key = sub.name ?? sub.subscriptionId;
|
|
249
|
+
await this.store.setSubscription(key, updated);
|
|
250
|
+
this.activeSubs.set(id, updated);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (applied.length > 0)
|
|
255
|
+
this.emit(applied);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Debounced pull — collapses multiple rapid calls (e.g. several hooks mounting
|
|
259
|
+
* at the same time) into a single transport request. All callers share the same
|
|
260
|
+
* promise and receive the result of the one batched pull.
|
|
261
|
+
*
|
|
262
|
+
* @param delayMs - How long to wait before issuing the pull (default 20 ms).
|
|
263
|
+
*/
|
|
264
|
+
schedulePull(delayMs = 20) {
|
|
265
|
+
if (this.pendingPull) {
|
|
266
|
+
clearTimeout(this.pendingPull.timer);
|
|
267
|
+
this.pendingPull.timer = setTimeout(() => this._flushPull(), delayMs);
|
|
268
|
+
return this.pendingPull.promise;
|
|
269
|
+
}
|
|
270
|
+
let resolve;
|
|
271
|
+
let reject;
|
|
272
|
+
const promise = new Promise((res, rej) => {
|
|
273
|
+
resolve = res;
|
|
274
|
+
reject = rej;
|
|
275
|
+
});
|
|
276
|
+
const timer = setTimeout(() => this._flushPull(), delayMs);
|
|
277
|
+
this.pendingPull = { promise, resolve, reject, timer };
|
|
278
|
+
return promise;
|
|
279
|
+
}
|
|
280
|
+
async _flushPull() {
|
|
281
|
+
const pending = this.pendingPull;
|
|
282
|
+
this.pendingPull = null;
|
|
283
|
+
if (!pending)
|
|
284
|
+
return;
|
|
285
|
+
try {
|
|
286
|
+
await this.pull();
|
|
287
|
+
pending.resolve();
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
pending.reject(err);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Write a record locally (read-your-own-writes) then push to server.
|
|
295
|
+
* Returns true on success, false if a conflict was detected (server wins).
|
|
296
|
+
*/
|
|
297
|
+
async mutate(record) {
|
|
298
|
+
const existing = await this.store.getById(record.recordId);
|
|
299
|
+
const stamped = {
|
|
300
|
+
...record,
|
|
301
|
+
updatedAt: Date.now(),
|
|
302
|
+
revisionCount: (existing?.revisionCount ?? 0) + 1,
|
|
303
|
+
};
|
|
304
|
+
await this.store.write(stamped);
|
|
305
|
+
// Optimistic update — notify listeners immediately so the UI reflects the change.
|
|
306
|
+
this.emit([{ op: "upsert", record: stamped }]);
|
|
307
|
+
const result = await this.transport.push([stamped]);
|
|
308
|
+
if ("conflict" in result && result.conflict) {
|
|
309
|
+
// Server wins: overwrite local record with server version.
|
|
310
|
+
const applied = await this.store.applyPatches([
|
|
311
|
+
{ op: "upsert", record: result.serverRecord },
|
|
312
|
+
]);
|
|
313
|
+
if (applied.length > 0)
|
|
314
|
+
this.emit(applied);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
// Stamp serverUpdatedAt on the local copy so reconstructSyncToken stays accurate.
|
|
318
|
+
// Emit again so listeners see the final record with serverUpdatedAt set.
|
|
319
|
+
const serverUpdatedAt = result
|
|
320
|
+
.serverUpdatedAt;
|
|
321
|
+
if (serverUpdatedAt !== undefined) {
|
|
322
|
+
await this.store.setServerUpdatedAt(stamped.recordId, serverUpdatedAt);
|
|
323
|
+
const updated = await this.store.getById(record.recordId);
|
|
324
|
+
if (updated)
|
|
325
|
+
this.emit([{ op: "upsert", record: updated }]);
|
|
326
|
+
}
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// Queries
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
/**
|
|
333
|
+
* Returns a reactive handle to a filtered view of the local store.
|
|
334
|
+
* Does NOT register a sync subscription — use this when data is already
|
|
335
|
+
* being synced via a separate `subscribe()` call (e.g. a background sync).
|
|
336
|
+
*
|
|
337
|
+
* Loading starts `true`, becomes `false` after the first local read.
|
|
338
|
+
* Re-runs whenever the store changes (pull patches, mutations).
|
|
339
|
+
*/
|
|
340
|
+
query(options) {
|
|
341
|
+
const { filter } = options;
|
|
342
|
+
return {
|
|
343
|
+
subscribe: (run, _invalidate) => {
|
|
344
|
+
let cancelled = false;
|
|
345
|
+
run({ data: [], loading: true });
|
|
346
|
+
this.store
|
|
347
|
+
.query(filter)
|
|
348
|
+
.then((data) => { if (!cancelled)
|
|
349
|
+
run({ data, loading: false }); })
|
|
350
|
+
.catch(console.error);
|
|
351
|
+
const offPatches = this.onPatches(async () => {
|
|
352
|
+
if (cancelled)
|
|
353
|
+
return;
|
|
354
|
+
const data = await this.store.query(filter);
|
|
355
|
+
if (!cancelled)
|
|
356
|
+
run({ data, loading: false });
|
|
357
|
+
});
|
|
358
|
+
return () => {
|
|
359
|
+
cancelled = true;
|
|
360
|
+
offPatches();
|
|
361
|
+
};
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Returns a reactive handle that also manages its own sync subscription.
|
|
367
|
+
* The sync subscription is registered when the first caller subscribes and
|
|
368
|
+
* removed when the last caller unsubscribes.
|
|
369
|
+
*
|
|
370
|
+
* Use this when the sync filter and the query filter are the same.
|
|
371
|
+
* For a narrower in-memory view of a broader background sync, use `query()`.
|
|
372
|
+
*/
|
|
373
|
+
liveQuery(options) {
|
|
374
|
+
const { filter, name } = options;
|
|
375
|
+
let refCount = 0;
|
|
376
|
+
let sessionCleanup;
|
|
377
|
+
const runs = new Set();
|
|
378
|
+
const refresh = async () => {
|
|
379
|
+
const data = await this.store.query(filter);
|
|
380
|
+
for (const run of runs)
|
|
381
|
+
run({ data, loading: false });
|
|
382
|
+
};
|
|
383
|
+
return {
|
|
384
|
+
subscribe: (run, _invalidate) => {
|
|
385
|
+
refCount++;
|
|
386
|
+
runs.add(run);
|
|
387
|
+
run({ data: [], loading: true });
|
|
388
|
+
if (refCount === 1) {
|
|
389
|
+
// Per-session state so that a React StrictMode unmount+remount
|
|
390
|
+
// (cleanup fires before the subscribe promise resolves) is handled safely.
|
|
391
|
+
let subId;
|
|
392
|
+
let cancelled = false;
|
|
393
|
+
const offPatches = this.onPatches(() => {
|
|
394
|
+
if (!cancelled)
|
|
395
|
+
refresh().catch(console.error);
|
|
396
|
+
});
|
|
397
|
+
this.subscribe({ filter, ...(name !== undefined && { name }) })
|
|
398
|
+
.then((sub) => {
|
|
399
|
+
if (cancelled) {
|
|
400
|
+
// Cleanup raced the subscribe — discard the subscription immediately.
|
|
401
|
+
this.unsubscribe(sub.subscriptionId).catch(console.error);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
subId = sub.subscriptionId;
|
|
405
|
+
return refresh();
|
|
406
|
+
})
|
|
407
|
+
.catch(console.error);
|
|
408
|
+
sessionCleanup = () => {
|
|
409
|
+
cancelled = true;
|
|
410
|
+
offPatches();
|
|
411
|
+
if (subId !== undefined) {
|
|
412
|
+
this.unsubscribe(subId).catch(console.error);
|
|
413
|
+
subId = undefined;
|
|
414
|
+
}
|
|
415
|
+
sessionCleanup = undefined;
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
refresh().catch(console.error);
|
|
420
|
+
}
|
|
421
|
+
return () => {
|
|
422
|
+
runs.delete(run);
|
|
423
|
+
refCount--;
|
|
424
|
+
if (refCount === 0)
|
|
425
|
+
sessionCleanup?.();
|
|
426
|
+
};
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
// Streaming (managed internally)
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
/**
|
|
434
|
+
* Debounced (re)start of the SSE stream. Collapses multiple rapid calls
|
|
435
|
+
* (e.g. several hooks subscribing at once) into a single connection open.
|
|
436
|
+
* Called automatically by subscribe(), unsubscribe(), and gap-fill completion.
|
|
437
|
+
*/
|
|
438
|
+
_syncStream(delayMs = 20) {
|
|
439
|
+
if (this._streamTimer !== null) {
|
|
440
|
+
clearTimeout(this._streamTimer);
|
|
441
|
+
}
|
|
442
|
+
this._streamTimer = setTimeout(() => {
|
|
443
|
+
this._streamTimer = null;
|
|
444
|
+
this._applyStreamState();
|
|
445
|
+
}, delayMs);
|
|
446
|
+
}
|
|
447
|
+
_applyStreamState() {
|
|
448
|
+
if (this._stopStream) {
|
|
449
|
+
this._stopStream();
|
|
450
|
+
this._stopStream = null;
|
|
451
|
+
}
|
|
452
|
+
if (!this.transport.stream)
|
|
453
|
+
return;
|
|
454
|
+
const subs = [...this.activeSubs.values()].filter((s) => (s.status ?? "active") === "active");
|
|
455
|
+
if (subs.length === 0)
|
|
456
|
+
return;
|
|
457
|
+
this._stopStream = this.transport.stream(subs.map((s) => ({
|
|
458
|
+
key: s.subscriptionId,
|
|
459
|
+
filter: s.filter,
|
|
460
|
+
syncToken: s.syncToken,
|
|
461
|
+
})), async ({ patches, syncTokens }) => {
|
|
462
|
+
const applied = await this.store.applyPatches(patches);
|
|
463
|
+
for (const [id, syncToken] of Object.entries(syncTokens)) {
|
|
464
|
+
const sub = this.activeSubs.get(id);
|
|
465
|
+
if (sub) {
|
|
466
|
+
const updated = { ...sub, syncToken };
|
|
467
|
+
const key = sub.name ?? sub.subscriptionId;
|
|
468
|
+
await this.store.setSubscription(key, updated);
|
|
469
|
+
this.activeSubs.set(id, updated);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (applied.length > 0)
|
|
473
|
+
this.emit(applied);
|
|
474
|
+
}, (err) => console.error("[SyncClient] SSE error:", err));
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Remove a subscription and restart the stream without it.
|
|
478
|
+
* If no active subscriptions remain the stream is stopped.
|
|
479
|
+
*/
|
|
480
|
+
async unsubscribe(subscriptionId) {
|
|
481
|
+
const sub = this.activeSubs.get(subscriptionId);
|
|
482
|
+
if (!sub)
|
|
483
|
+
return;
|
|
484
|
+
if (sub.gapSubscriptionId) {
|
|
485
|
+
await this.store.removeSubscription(sub.gapSubscriptionId);
|
|
486
|
+
this.subActiveListeners.delete(subscriptionId);
|
|
487
|
+
}
|
|
488
|
+
const key = sub.name ?? subscriptionId;
|
|
489
|
+
await this.store.removeSubscription(key);
|
|
490
|
+
this.activeSubs.delete(subscriptionId);
|
|
491
|
+
this._syncStream();
|
|
492
|
+
}
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
// Listeners
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
onPatches(listener) {
|
|
497
|
+
this.listeners.push(listener);
|
|
498
|
+
return () => {
|
|
499
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Register a one-shot listener that fires when the given subscription
|
|
504
|
+
* transitions from pending_gap_fill → active. Useful for restarting the
|
|
505
|
+
* SSE stream to include the newly-active subscription.
|
|
506
|
+
* Returns an unsubscribe function.
|
|
507
|
+
*/
|
|
508
|
+
onSubscriptionActive(subscriptionId, listener) {
|
|
509
|
+
if (!this.subActiveListeners.has(subscriptionId)) {
|
|
510
|
+
this.subActiveListeners.set(subscriptionId, new Set());
|
|
511
|
+
}
|
|
512
|
+
this.subActiveListeners.get(subscriptionId).add(listener);
|
|
513
|
+
return () => {
|
|
514
|
+
this.subActiveListeners.get(subscriptionId)?.delete(listener);
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
_emitSubscriptionActive(subscriptionId) {
|
|
518
|
+
const listeners = this.subActiveListeners.get(subscriptionId);
|
|
519
|
+
if (listeners) {
|
|
520
|
+
for (const l of listeners)
|
|
521
|
+
l();
|
|
522
|
+
this.subActiveListeners.delete(subscriptionId);
|
|
523
|
+
}
|
|
524
|
+
// Subscription just became active — restart stream to include it.
|
|
525
|
+
this._syncStream();
|
|
526
|
+
}
|
|
527
|
+
emit(patches) {
|
|
528
|
+
for (const l of this.listeners)
|
|
529
|
+
l(patches);
|
|
530
|
+
}
|
|
531
|
+
// ---------------------------------------------------------------------------
|
|
532
|
+
// Helpers
|
|
533
|
+
// ---------------------------------------------------------------------------
|
|
534
|
+
/**
|
|
535
|
+
* Look up a subscription by name (for named subs) or by subscriptionId
|
|
536
|
+
* (for unnamed subs, where the key IS the subscriptionId).
|
|
537
|
+
*/
|
|
538
|
+
getSubscription(key) {
|
|
539
|
+
// Try by subscriptionId first (covers unnamed subs)
|
|
540
|
+
const byId = this.activeSubs.get(key);
|
|
541
|
+
if (byId)
|
|
542
|
+
return byId;
|
|
543
|
+
// Then by name (covers named subs)
|
|
544
|
+
for (const sub of this.activeSubs.values()) {
|
|
545
|
+
if (sub.name === key)
|
|
546
|
+
return sub;
|
|
547
|
+
}
|
|
548
|
+
return undefined;
|
|
549
|
+
}
|
|
550
|
+
getSubscriptionById(id) {
|
|
551
|
+
return this.activeSubs.get(id);
|
|
552
|
+
}
|
|
553
|
+
/** Resets sync state (useful for logout / account switch). */
|
|
554
|
+
async reset() {
|
|
555
|
+
if (this._streamTimer !== null) {
|
|
556
|
+
clearTimeout(this._streamTimer);
|
|
557
|
+
this._streamTimer = null;
|
|
558
|
+
}
|
|
559
|
+
if (this._stopStream) {
|
|
560
|
+
this._stopStream();
|
|
561
|
+
this._stopStream = null;
|
|
562
|
+
}
|
|
563
|
+
this.activeSubs.clear();
|
|
564
|
+
await this.store.clear();
|
|
565
|
+
await this.store.clearSubscriptions();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
//# sourceMappingURL=syncClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncClient.js","sourceRoot":"","sources":["../src/syncClient.ts"],"names":[],"mappings":"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"}
|