@signaldb/sync 2.0.0-beta.2 → 2.0.0-beta.21

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/dist/index.mjs CHANGED
@@ -1,606 +1,541 @@
1
- import { isEqual as N, modify as w, randomId as S, DefaultDataAdapter as E, Collection as y } from "@signaldb/core";
2
- function $(l, e, t = {}) {
3
- let s, n;
4
- const { leading: a = !1, trailing: o = !0 } = t;
5
- function r(...c) {
6
- const i = a && !s, h = o && !s;
7
- return s && clearTimeout(s), s = setTimeout(() => {
8
- s = null, o && !i && (n = l.apply(this, c));
9
- }, e), i ? n = l.apply(this, c) : h || (n = null), n;
10
- }
11
- return r;
1
+ import { Collection as e, DefaultDataAdapter as t, isEqual as n, modify as r, randomId as i } from "@signaldb/core";
2
+ //#region src/utils/debounce.ts
3
+ function a(e, t, n = {}) {
4
+ let r, i, { leading: a = !1, trailing: o = !0 } = n;
5
+ function s(...n) {
6
+ let s = a && !r, c = o && !r;
7
+ return r && clearTimeout(r), r = setTimeout(() => {
8
+ r = null, o && !s && (i = e.apply(this, n));
9
+ }, t), s ? i = e.apply(this, n) : c || (i = null), i;
10
+ }
11
+ return s;
12
12
  }
13
- class D {
14
- queue = [];
15
- pendingPromise = !1;
16
- /**
17
- * Method to add a new promise to the queue and returns a promise that resolves when this task is done
18
- * @param task Function that returns a promise that will be added to the queue
19
- * @returns Promise that resolves when the task is done
20
- */
21
- add(e) {
22
- return new Promise((t, s) => {
23
- this.queue.push(() => e().then(t).catch((n) => {
24
- throw s(n), n;
25
- })), this.dequeue();
26
- });
27
- }
28
- /**
29
- * Method to check if there is a pending promise in the queue
30
- * @returns True if there is a pending promise, false otherwise
31
- */
32
- hasPendingPromise() {
33
- return this.pendingPromise;
34
- }
35
- /**
36
- * Method to process the queue
37
- */
38
- dequeue() {
39
- if (this.pendingPromise || this.queue.length === 0)
40
- return;
41
- const e = this.queue.shift();
42
- e && (this.pendingPromise = !0, e().then(() => {
43
- this.pendingPromise = !1, this.dequeue();
44
- }).catch(() => {
45
- this.pendingPromise = !1, this.dequeue();
46
- }));
47
- }
48
- }
49
- function C(l, e) {
50
- const t = [], s = Object.keys(l), n = Object.keys(e), a = /* @__PURE__ */ new Set([...s, ...n]);
51
- for (const o of a)
52
- if (e[o] !== l[o])
53
- if (typeof e[o] == "object" && typeof l[o] == "object" && e[o] != null && l[o] != null) {
54
- const r = C(l[o], e[o]);
55
- for (const c of r)
56
- t.push(`${o}.${c}`);
57
- } else
58
- t.push(o);
59
- return t;
13
+ //#endregion
14
+ //#region src/utils/PromiseQueue.ts
15
+ var o = class {
16
+ queue = [];
17
+ pendingPromise = !1;
18
+ add(e) {
19
+ return new Promise((t, n) => {
20
+ this.queue.push(() => e().then(t).catch((e) => {
21
+ throw n(e), e;
22
+ })), this.dequeue();
23
+ });
24
+ }
25
+ hasPendingPromise() {
26
+ return this.pendingPromise;
27
+ }
28
+ dequeue() {
29
+ if (this.pendingPromise || this.queue.length === 0) return;
30
+ let e = this.queue.shift();
31
+ e && (this.pendingPromise = !0, e().then(() => {
32
+ this.pendingPromise = !1, this.dequeue();
33
+ }).catch(() => {
34
+ this.pendingPromise = !1, this.dequeue();
35
+ }));
36
+ }
37
+ };
38
+ //#endregion
39
+ //#region src/computeChanges.ts
40
+ function s(e, t) {
41
+ let n = [], r = Object.keys(e), i = Object.keys(t), a = new Set([...r, ...i]);
42
+ for (let r of a) if (t[r] !== e[r]) if (typeof t[r] == "object" && typeof e[r] == "object" && t[r] != null && e[r] != null) {
43
+ let i = s(e[r], t[r]);
44
+ for (let e of i) n.push(`${r}.${e}`);
45
+ } else n.push(r);
46
+ return n;
60
47
  }
61
- function f(l, e) {
62
- const t = [], s = [], n = /* @__PURE__ */ new Map(), a = [], o = new Map(l.map((c) => [c.id, c])), r = new Map(e.map((c) => [c.id, c]));
63
- for (const [c, i] of o) {
64
- const h = r.get(c);
65
- h ? N(h, i) || (n.set(h.id, C(i, h)), s.push(h)) : a.push(i);
66
- }
67
- for (const [c, i] of r)
68
- o.has(c) || t.push(i);
69
- return {
70
- added: t,
71
- modified: s,
72
- modifiedFields: n,
73
- removed: a
74
- };
48
+ function c(e, t) {
49
+ let r = [], i = [], a = /* @__PURE__ */ new Map(), o = [], c = new Map(e.map((e) => [e.id, e])), l = new Map(t.map((e) => [e.id, e]));
50
+ for (let [e, t] of c) {
51
+ let r = l.get(e);
52
+ r ? n(r, t) || (a.set(r.id, s(t, r)), i.push(r)) : o.push(t);
53
+ }
54
+ for (let [e, t] of l) c.has(e) || r.push(t);
55
+ return {
56
+ added: r,
57
+ modified: i,
58
+ modifiedFields: a,
59
+ removed: o
60
+ };
75
61
  }
76
- function g(l, e) {
77
- if (e.items != null)
78
- return e.items;
79
- const t = l || [];
80
- return e.changes.added.forEach((s) => {
81
- const n = t.findIndex((a) => a.id === s.id);
82
- n === -1 ? t.push(s) : t[n] = s;
83
- }), e.changes.modified.forEach((s) => {
84
- const n = t.findIndex((a) => a.id === s.id);
85
- n === -1 ? t.push(s) : t[n] = s;
86
- }), e.changes.removed.forEach((s) => {
87
- const n = t.findIndex((a) => a.id === s.id);
88
- n !== -1 && t.splice(n, 1);
89
- }), t;
62
+ //#endregion
63
+ //#region src/getSnapshot.ts
64
+ function l(e, t) {
65
+ if (t.items != null) return t.items;
66
+ let n = e || [];
67
+ return t.changes.added.forEach((e) => {
68
+ let t = n.findIndex((t) => t.id === e.id);
69
+ t === -1 ? n.push(e) : n[t] = e;
70
+ }), t.changes.modified.forEach((e) => {
71
+ let t = n.findIndex((t) => t.id === e.id);
72
+ t === -1 ? n.push(e) : n[t] = e;
73
+ }), t.changes.removed.forEach((e) => {
74
+ let t = n.findIndex((t) => t.id === e.id);
75
+ t !== -1 && n.splice(t, 1);
76
+ }), n;
90
77
  }
91
- function P(l, e) {
92
- const t = new Map(l.map((s) => [s.id, s]));
93
- return e.forEach((s) => {
94
- if (s.type === "remove")
95
- t.delete(s.data);
96
- else if (s.type === "insert") {
97
- const n = t.get(s.data.id);
98
- t.set(s.data.id, n ? { ...n, ...s.data } : s.data);
99
- } else {
100
- const n = t.get(s.data.id);
101
- t.set(s.data.id, n ? w(n, s.data.modifier) : w({ id: s.data.id }, s.data.modifier));
102
- }
103
- }), [...t.values()];
78
+ //#endregion
79
+ //#region src/applyChanges.ts
80
+ function u(e, t) {
81
+ let n = new Map(e.map((e) => [e.id, e]));
82
+ return t.forEach((e) => {
83
+ if (e.type === "remove") n.delete(e.data);
84
+ else if (e.type === "insert") {
85
+ let t = n.get(e.data.id);
86
+ n.set(e.data.id, t ? {
87
+ ...t,
88
+ ...e.data
89
+ } : e.data);
90
+ } else {
91
+ let t = n.get(e.data.id);
92
+ n.set(e.data.id, r(t || { id: e.data.id }, e.data.modifier));
93
+ }
94
+ }), [...n.values()];
104
95
  }
105
- function v(l) {
106
- return l.added.length > 0 || l.modified.length > 0 || l.removed.length > 0;
96
+ //#endregion
97
+ //#region src/sync.ts
98
+ function d(e) {
99
+ return e.added.length > 0 || e.modified.length > 0 || e.removed.length > 0;
107
100
  }
108
- function M(l, e) {
109
- return v(f(l, e));
101
+ function f(e, t) {
102
+ return d(c(e, t));
110
103
  }
111
- async function F({ changes: l, lastSnapshot: e, data: t, pull: s, push: n, insert: a, update: o, remove: r, batch: c }) {
112
- let i = t, h = e || [], p = g(e, i);
113
- if (l.length > 0) {
114
- const u = P(h, l);
115
- if (M(h, u)) {
116
- const O = P(p, l), m = f(p, O);
117
- v(m) && (await n(m), i = await s(), p = g(p, i)), h = u;
118
- }
119
- }
120
- const d = i.changes == null ? f(h, i.items) : i.changes;
121
- return await c(async () => {
122
- await Promise.all(d.added.map((u) => a(u))), await Promise.all(d.modified.map((u) => o(u.id, { $set: u }))), await Promise.all(d.removed.map((u) => r(u.id)));
123
- }), p;
104
+ async function p({ changes: e, lastSnapshot: t, data: n, pull: r, push: i, insert: a, update: o, remove: s, batch: p }) {
105
+ let m = n, h = t || [], g = l(t, m);
106
+ if (e.length > 0) {
107
+ let t = u(h, e);
108
+ if (f(h, t)) {
109
+ let n = u(g, e), a = c(g, n);
110
+ d(a) && (await i(a), m = await r(), g = l(g, m)), h = t;
111
+ }
112
+ }
113
+ let _ = m.changes == null ? c(h, m.items) : m.changes;
114
+ return await p(async () => {
115
+ await Promise.all(_.added.map((e) => a(e))), await Promise.all(_.modified.map((e) => o(e.id, { $set: e }))), await Promise.all(_.removed.map((e) => s(e.id)));
116
+ }), g;
124
117
  }
125
- class b {
126
- options;
127
- collections = /* @__PURE__ */ new Map();
128
- changes;
129
- snapshots;
130
- syncOperations;
131
- scheduledPushes = /* @__PURE__ */ new Set();
132
- remoteChanges = [];
133
- syncQueues = /* @__PURE__ */ new Map();
134
- collectionsReady;
135
- isDisposed = !1;
136
- instanceId = S();
137
- id;
138
- debouncedFlush;
139
- /**
140
- * @param options Collection options
141
- * @param options.pull Function to pull data from remote source.
142
- * @param options.push Function to push data to remote source.
143
- * @param [options.registerRemoteChange] Function to register a callback for remote changes.
144
- * @param [options.id] Unique identifier for this sync manager. Only nessesary if you have multiple sync managers.
145
- * @param [options.storageAdapter] Storage adapter to use for storing changes, snapshots and sync operations.
146
- * @param [options.reactivity] Reactivity adapter to use for reactivity.
147
- * @param [options.onError] Function to handle errors that occur async during syncing.
148
- * @param [options.autostart] Whether to automatically start syncing new collections.
149
- * @param [options.debounceTime] The time in milliseconds to debounce push operations.
150
- */
151
- constructor(e) {
152
- this.options = {
153
- autostart: !0,
154
- ...e
155
- }, this.id = this.options.id || "default-sync-manager";
156
- const { reactivity: t } = this.options, s = this.options.dataAdapter ?? new E();
157
- this.changes = new y(`${this.options.id}-changes`, s, {
158
- indices: ["collectionName"],
159
- reactivity: t
160
- }), this.snapshots = new y(`${this.options.id}-snapshots`, s, {
161
- indices: ["collectionName"],
162
- reactivity: t
163
- }), this.syncOperations = new y(`${this.options.id}-sync-operations`, s, {
164
- indices: ["collectionName", "status"],
165
- reactivity: t
166
- });
167
- const n = [
168
- Promise.resolve(this.syncOperations.isReady()),
169
- Promise.resolve(this.changes.isReady()),
170
- Promise.resolve(this.snapshots.isReady())
171
- ];
172
- this.collectionsReady = Promise.all(n).then(() => {
173
- }), this.changes.setMaxListeners(1e3), this.snapshots.setMaxListeners(1e3), this.syncOperations.setMaxListeners(1e3), this.debouncedFlush = $(this.flushScheduledPushes, this.options.debounceTime ?? 100);
174
- }
175
- getSyncQueue(e) {
176
- return this.syncQueues.get(e) == null && this.syncQueues.set(e, new D()), this.syncQueues.get(e);
177
- }
178
- /**
179
- * Clears all internal data structures
180
- */
181
- async dispose() {
182
- this.collections.clear(), this.syncQueues.clear(), this.remoteChanges.splice(0), await Promise.all([
183
- this.changes.dispose(),
184
- this.snapshots.dispose(),
185
- this.syncOperations.dispose()
186
- ]), this.isDisposed = !0;
187
- }
188
- /**
189
- * Gets a collection with it's options by name
190
- * @deprecated Use getCollectionProperties instead.
191
- * @param name Name of the collection
192
- * @throws {Error} Will throw an error if the name wasn't found
193
- * @returns Tuple of collection and options
194
- */
195
- getCollection(e) {
196
- const { collection: t, options: s } = this.getCollectionProperties(e);
197
- return [t, s];
198
- }
199
- /**
200
- * Gets collection options by name
201
- * @param name Name of the collection
202
- * @throws {Error} Will throw an error if the name wasn't found
203
- * @returns An object of all properties of the collection
204
- */
205
- getCollectionProperties(e) {
206
- const t = this.collections.get(e);
207
- if (t == null)
208
- throw new Error(`Collection with id '${e}' not found`);
209
- return t;
210
- }
211
- /**
212
- * Adds a collection to the sync manager.
213
- * @param collection Collection to add
214
- * @param options Options for the collection. The object needs at least a `name` property.
215
- * @param options.name Unique name of the collection
216
- */
217
- addCollection(e, t) {
218
- if (this.isDisposed)
219
- throw new Error("SyncManager is disposed");
220
- this.collections.set(t.name, {
221
- collection: e,
222
- options: t,
223
- readyPromise: e.ready(),
224
- syncPaused: !0
225
- // always start paused as the autostart will start it
226
- });
227
- const s = (a) => {
228
- for (const o of this.remoteChanges)
229
- if (o != null && o.collectionName === a.collectionName && o.type === a.type && !(a.type === "remove" && o.data !== a.data) && o.data.id === a.data.id)
230
- return !0;
231
- return !1;
232
- }, n = (a, o) => {
233
- const r = [...this.remoteChanges];
234
- for (let c = 0; c < r.length; c += 1) {
235
- const i = r[c];
236
- i != null && i.collectionName === a && (i.type === "remove" && i.data !== o || i.data.id === o && (r[c] = null));
237
- }
238
- this.remoteChanges = r.filter((c) => c != null);
239
- };
240
- e.on("added", (a) => {
241
- if (s({ collectionName: t.name, type: "insert", data: a })) {
242
- n(t.name, a.id);
243
- return;
244
- }
245
- this.changes.insert({
246
- collectionName: t.name,
247
- time: Date.now(),
248
- type: "insert",
249
- data: a
250
- }).then(() => {
251
- this.getCollectionProperties(t.name).syncPaused || this.schedulePush(t.name);
252
- }).catch((o) => {
253
- this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, o);
254
- });
255
- }), e.on("changed", ({ id: a }, o) => {
256
- const r = { id: a, modifier: o };
257
- if (s({ collectionName: t.name, type: "update", data: r })) {
258
- n(t.name, a);
259
- return;
260
- }
261
- this.changes.insert({
262
- collectionName: t.name,
263
- time: Date.now(),
264
- type: "update",
265
- data: r
266
- }).then(() => {
267
- this.getCollectionProperties(t.name).syncPaused || this.schedulePush(t.name);
268
- }).catch((c) => {
269
- this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, c);
270
- });
271
- }), e.on("removed", ({ id: a }) => {
272
- if (s({ collectionName: t.name, type: "remove", data: a })) {
273
- n(t.name, a);
274
- return;
275
- }
276
- this.changes.insert({
277
- collectionName: t.name,
278
- time: Date.now(),
279
- type: "remove",
280
- data: a
281
- }).then(() => {
282
- this.getCollectionProperties(t.name).syncPaused || this.schedulePush(t.name);
283
- }).catch((o) => {
284
- this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, o);
285
- });
286
- }), this.options.autostart && this.startSync(t.name).catch((a) => {
287
- this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, a);
288
- });
289
- }
290
- flushScheduledPushes() {
291
- this.scheduledPushes.forEach((e) => {
292
- this.pushChanges(e).catch(() => {
293
- });
294
- }), this.scheduledPushes.clear();
295
- }
296
- schedulePush(e) {
297
- this.scheduledPushes.add(e), this.debouncedFlush();
298
- }
299
- /**
300
- * Setup all collections to be synced with remote changes
301
- * and enable automatic pushing changes to the remote source.
302
- */
303
- async startAll() {
304
- await Promise.all([...this.collections.keys()].map((e) => this.startSync(e)));
305
- }
306
- /**
307
- * Setup a collection to be synced with remote changes
308
- * and enable automatic pushing changes to the remote source.
309
- * @param name Name of the collection
310
- */
311
- async startSync(e) {
312
- const t = this.getCollectionProperties(e);
313
- if (!t.syncPaused)
314
- return;
315
- this.schedulePush(e);
316
- const s = this.options.registerRemoteChange ? await this.options.registerRemoteChange(t.options, async (n) => {
317
- if (n == null)
318
- await this.sync(e);
319
- else {
320
- const a = Date.now(), o = await this.syncOperations.insert({
321
- start: a,
322
- collectionName: e,
323
- instanceId: this.instanceId,
324
- status: "active"
325
- });
326
- await this.syncWithData(e, n).then(async () => {
327
- await this.syncOperations.removeMany({
328
- id: { $ne: o },
329
- collectionName: e,
330
- $or: [
331
- { end: { $lte: a } },
332
- { status: "active" }
333
- ]
334
- }), await this.syncOperations.updateOne({ id: o }, {
335
- $set: { status: "done", end: Date.now() }
336
- });
337
- }).catch(async (r) => {
338
- throw this.options.onError && this.options.onError(this.getCollectionProperties(e).options, r), await this.syncOperations.updateOne({ id: o }, {
339
- $set: { status: "error", end: Date.now(), error: r.stack || r.message }
340
- }), r;
341
- });
342
- }
343
- }) : void 0;
344
- this.collections.set(e, {
345
- ...t,
346
- syncPaused: !1,
347
- cleanupFunction: s
348
- });
349
- }
350
- /**
351
- * Pauses the sync process for all collections.
352
- * This means that the collections will not be synced with remote changes
353
- * and changes will not automatically be pushed to the remote source.
354
- */
355
- async pauseAll() {
356
- await Promise.all([...this.collections.keys()].map((e) => this.pauseSync(e)));
357
- }
358
- /**
359
- * Pauses the sync process for a collection.
360
- * This means that the collection will not be synced with remote changes
361
- * and changes will not automatically be pushed to the remote source.
362
- * @param name Name of the collection
363
- */
364
- async pauseSync(e) {
365
- const t = this.getCollectionProperties(e);
366
- t.syncPaused || (t.cleanupFunction && await t.cleanupFunction(), this.collections.set(e, {
367
- ...t,
368
- cleanupFunction: void 0,
369
- syncPaused: !0
370
- }));
371
- }
372
- /**
373
- * Starts the sync process for all collections
374
- */
375
- async syncAll() {
376
- if (this.isDisposed)
377
- throw new Error("SyncManager is disposed");
378
- const e = [];
379
- if (await Promise.all([...this.collections.keys()].map((t) => this.sync(t).catch((s) => {
380
- e.push({ id: t, error: s });
381
- }))), e.length > 0)
382
- throw new Error(`Error while syncing collections:
383
- ${e.map((t) => `${t.id}: ${t.error.message}`).join(`
384
-
385
- `)}`);
386
- }
387
- /**
388
- * Checks if a collection is currently beeing synced
389
- * @param [name] Name of the collection. If not provided, it will check if any collection is currently beeing synced.
390
- * @param [async] If true, it will check for active syncs in the database. This is useful if you have multiple instances of the application running.
391
- * @returns True if the collection is currently beeing synced, false otherwise. If async is true, it will return a promise that resolves to true or false.
392
- */
393
- isSyncing(e, t) {
394
- const s = this.syncOperations.findOne({
395
- ...e ? { collectionName: e } : {},
396
- status: "active"
397
- }, { fields: { status: 1 }, async: t });
398
- return s instanceof Promise ? s.then((n) => n != null) : s != null;
399
- }
400
- /**
401
- * Checks if the sync manager is ready to sync.
402
- * @returns A promise that resolves when the sync manager is ready to sync.
403
- */
404
- async isReady() {
405
- await this.collectionsReady;
406
- }
407
- /**
408
- * Starts the sync process for a collection
409
- * @param name Name of the collection
410
- * @param options Options for the sync process.
411
- * @param options.force If true, the sync process will be started even if there are no changes and onlyWithChanges is true.
412
- * @param options.onlyWithChanges If true, the sync process will only be started if there are changes.
413
- */
414
- async sync(e, t = {}) {
415
- if (this.isDisposed)
416
- throw new Error("SyncManager is disposed");
417
- await this.isReady();
418
- const { options: s, readyPromise: n } = this.getCollectionProperties(e);
419
- await n;
420
- const a = await this.syncOperations.find({
421
- collectionName: e,
422
- instanceId: this.instanceId,
423
- status: "active"
424
- }, {
425
- reactive: !1,
426
- async: !0
427
- }).count() > 0, o = Date.now();
428
- let r = null;
429
- await new Promise((i) => {
430
- setTimeout(i, 0);
431
- });
432
- const c = async () => {
433
- const i = await this.syncOperations.findOne({
434
- collectionName: e,
435
- status: "done"
436
- }, {
437
- sort: { end: -1 },
438
- reactive: !1,
439
- async: !0
440
- });
441
- if (t?.onlyWithChanges && await this.changes.find({
442
- collectionName: e,
443
- time: { $lte: o }
444
- }, {
445
- sort: { time: 1 },
446
- reactive: !1,
447
- async: !0
448
- }).count() === 0)
449
- return;
450
- a || (r = await this.syncOperations.insert({
451
- start: o,
452
- collectionName: e,
453
- instanceId: this.instanceId,
454
- status: "active"
455
- }));
456
- const h = await this.options.pull(s, {
457
- lastFinishedSyncStart: i?.start,
458
- lastFinishedSyncEnd: i?.end
459
- });
460
- await this.syncWithData(e, h);
461
- };
462
- await (t?.force ? c() : this.getSyncQueue(e).add(c)).catch(async (i) => {
463
- throw r != null && (this.options.onError && this.options.onError(s, i), await this.syncOperations.updateOne({ id: r }, {
464
- $set: { status: "error", end: Date.now(), error: i.stack || i.message }
465
- })), i;
466
- }), r != null && (await this.syncOperations.removeMany({
467
- id: { $ne: r },
468
- collectionName: e,
469
- $or: [
470
- { end: { $lte: o } },
471
- { status: "active" }
472
- ]
473
- }), await this.syncOperations.updateOne({ id: r }, {
474
- $set: { status: "done", end: Date.now() }
475
- }));
476
- }
477
- /**
478
- * Starts the push process for a collection (sync process but only if there are changes)
479
- * @param name Name of the collection
480
- */
481
- async pushChanges(e) {
482
- await this.sync(e, {
483
- onlyWithChanges: !0
484
- });
485
- }
486
- async syncWithData(e, t) {
487
- const { collection: s, options: n } = this.getCollectionProperties(e), a = Date.now(), o = await this.syncOperations.findOne({
488
- collectionName: e,
489
- status: "done"
490
- }, {
491
- sort: { end: -1 },
492
- reactive: !1,
493
- async: !0
494
- }), r = await this.snapshots.findOne({
495
- collectionName: e
496
- }, {
497
- sort: { time: -1 },
498
- reactive: !1,
499
- async: !0
500
- }), c = await this.changes.find({
501
- collectionName: e,
502
- time: { $lte: a }
503
- }, {
504
- sort: { time: 1 },
505
- reactive: !1,
506
- async: !0
507
- }).fetch();
508
- await F({
509
- changes: c,
510
- lastSnapshot: r?.items,
511
- data: t,
512
- pull: () => this.options.pull(n, {
513
- lastFinishedSyncStart: o?.start,
514
- lastFinishedSyncEnd: o?.end
515
- }),
516
- push: (i) => this.options.push(n, {
517
- changes: i,
518
- rawChanges: c
519
- }),
520
- insert: async (i) => {
521
- this.remoteChanges.push({
522
- collectionName: e,
523
- type: "insert",
524
- data: i
525
- }, {
526
- collectionName: e,
527
- type: "update",
528
- data: { id: i.id, modifier: { $set: i } }
529
- }), await s.replaceOne({ id: i.id }, i, { upsert: !0 });
530
- },
531
- update: async (i, h) => {
532
- this.remoteChanges.push({
533
- collectionName: e,
534
- type: "insert",
535
- data: { id: i, ...h.$set }
536
- }, {
537
- collectionName: e,
538
- type: "update",
539
- data: { id: i, modifier: h }
540
- }), await s.updateOne({ id: i }, {
541
- ...h,
542
- $setOnInsert: { id: i }
543
- }, { upsert: !0 });
544
- },
545
- remove: async (i) => {
546
- await s.find({
547
- id: i
548
- }, { reactive: !1, async: !0 }).count() > 0 && (this.remoteChanges.push({
549
- collectionName: e,
550
- type: "remove",
551
- data: i
552
- }), await s.removeOne({ id: i }));
553
- },
554
- batch: async (i) => s.batch(async () => {
555
- await i();
556
- })
557
- }).then(async (i) => {
558
- if (await this.snapshots.removeMany({
559
- collectionName: e,
560
- time: { $lte: a }
561
- }), await this.changes.removeMany({
562
- collectionName: e,
563
- id: { $in: c.map((d) => d.id) }
564
- }), await this.snapshots.insert({
565
- time: a,
566
- collectionName: e,
567
- items: i
568
- }), await new Promise((d) => {
569
- setTimeout(d, 0);
570
- }), await this.changes.find({
571
- collectionName: e
572
- }, { reactive: !1, async: !0 }).count() > 0) {
573
- await this.sync(e, {
574
- force: !0,
575
- onlyWithChanges: !0
576
- });
577
- return;
578
- }
579
- const p = await s.find({
580
- id: { $nin: i.map((d) => d.id) }
581
- }, {
582
- reactive: !1,
583
- async: !0
584
- }).map((d) => d.id);
585
- await s.batch(async () => {
586
- await Promise.all(i.map(async (d) => {
587
- this.remoteChanges.push({
588
- collectionName: e,
589
- type: "insert",
590
- data: d
591
- }, {
592
- collectionName: e,
593
- type: "update",
594
- data: { id: d.id, modifier: { $set: d } }
595
- }), await s.replaceOne({ id: d.id }, d, { upsert: !0 });
596
- })), await Promise.all(p.map(async (d) => {
597
- await s.removeOne({ id: d });
598
- }));
599
- });
600
- });
601
- }
602
- }
603
- export {
604
- b as SyncManager
118
+ //#endregion
119
+ //#region src/SyncManager.ts
120
+ var m = class {
121
+ options;
122
+ collections = /* @__PURE__ */ new Map();
123
+ changes;
124
+ snapshots;
125
+ syncOperations;
126
+ scheduledPushes = /* @__PURE__ */ new Set();
127
+ remoteChanges = [];
128
+ syncQueues = /* @__PURE__ */ new Map();
129
+ collectionsReady;
130
+ isDisposed = !1;
131
+ instanceId = i();
132
+ id;
133
+ debouncedFlush;
134
+ constructor(n) {
135
+ this.options = {
136
+ autostart: !0,
137
+ ...n
138
+ }, this.id = this.options.id || "default-sync-manager";
139
+ let { reactivity: r } = this.options, i = this.options.dataAdapter ?? new t();
140
+ this.changes = new e(`${this.options.id}-changes`, i, {
141
+ indices: ["collectionName"],
142
+ reactivity: r
143
+ }), this.snapshots = new e(`${this.options.id}-snapshots`, i, {
144
+ indices: ["collectionName"],
145
+ reactivity: r
146
+ }), this.syncOperations = new e(`${this.options.id}-sync-operations`, i, {
147
+ indices: ["collectionName", "status"],
148
+ reactivity: r
149
+ });
150
+ let o = [
151
+ Promise.resolve(this.syncOperations.isReady()),
152
+ Promise.resolve(this.changes.isReady()),
153
+ Promise.resolve(this.snapshots.isReady())
154
+ ];
155
+ this.collectionsReady = Promise.all(o).then(() => {}), this.changes.setMaxListeners(1e3), this.snapshots.setMaxListeners(1e3), this.syncOperations.setMaxListeners(1e3), this.debouncedFlush = a(this.flushScheduledPushes, this.options.debounceTime ?? 100);
156
+ }
157
+ getSyncQueue(e) {
158
+ return this.syncQueues.get(e) ?? this.syncQueues.set(e, new o()), this.syncQueues.get(e);
159
+ }
160
+ async dispose() {
161
+ this.isDisposed = !0, this.collections.clear(), this.syncQueues.clear(), this.remoteChanges.splice(0), await Promise.all([
162
+ this.changes.dispose(),
163
+ this.snapshots.dispose(),
164
+ this.syncOperations.dispose()
165
+ ]);
166
+ }
167
+ getCollection(e) {
168
+ let { collection: t, options: n } = this.getCollectionProperties(e);
169
+ return [t, n];
170
+ }
171
+ getCollectionProperties(e) {
172
+ let t = this.collections.get(e);
173
+ if (t == null) throw Error(`Collection with id '${e}' not found`);
174
+ return t;
175
+ }
176
+ addCollection(e, t) {
177
+ if (this.isDisposed) throw Error("SyncManager is disposed");
178
+ this.collections.set(t.name, {
179
+ collection: e,
180
+ options: t,
181
+ readyPromise: e.ready(),
182
+ syncPaused: !0
183
+ });
184
+ let n = (e) => {
185
+ for (let t of this.remoteChanges) if (t != null && t.collectionName === e.collectionName && t.type === e.type && !(e.type === "remove" && t.data !== e.data) && t.data.id === e.data.id) return !0;
186
+ return !1;
187
+ }, r = (e, t) => {
188
+ let n = [...this.remoteChanges];
189
+ for (let r = 0; r < n.length; r += 1) {
190
+ let i = n[r];
191
+ i != null && i.collectionName === e && (i.type === "remove" && i.data !== t || i.data.id === t && (n[r] = null));
192
+ }
193
+ this.remoteChanges = n.filter((e) => e != null);
194
+ };
195
+ e.on("added", (e) => {
196
+ if (!this.isDisposed) {
197
+ if (n({
198
+ collectionName: t.name,
199
+ type: "insert",
200
+ data: e
201
+ })) {
202
+ r(t.name, e.id);
203
+ return;
204
+ }
205
+ this.changes.insert({
206
+ collectionName: t.name,
207
+ time: Date.now(),
208
+ type: "insert",
209
+ data: e
210
+ }).then(() => {
211
+ this.getCollectionProperties(t.name).syncPaused || this.schedulePush(t.name);
212
+ }).catch((e) => {
213
+ this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, e);
214
+ });
215
+ }
216
+ }), e.on("changed", ({ id: e }, i) => {
217
+ if (this.isDisposed) return;
218
+ let a = {
219
+ id: e,
220
+ modifier: i
221
+ };
222
+ if (n({
223
+ collectionName: t.name,
224
+ type: "update",
225
+ data: a
226
+ })) {
227
+ r(t.name, e);
228
+ return;
229
+ }
230
+ this.changes.insert({
231
+ collectionName: t.name,
232
+ time: Date.now(),
233
+ type: "update",
234
+ data: a
235
+ }).then(() => {
236
+ this.getCollectionProperties(t.name).syncPaused || this.schedulePush(t.name);
237
+ }).catch((e) => {
238
+ this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, e);
239
+ });
240
+ }), e.on("removed", ({ id: e }) => {
241
+ if (!this.isDisposed) {
242
+ if (n({
243
+ collectionName: t.name,
244
+ type: "remove",
245
+ data: e
246
+ })) {
247
+ r(t.name, e);
248
+ return;
249
+ }
250
+ this.changes.insert({
251
+ collectionName: t.name,
252
+ time: Date.now(),
253
+ type: "remove",
254
+ data: e
255
+ }).then(() => {
256
+ this.getCollectionProperties(t.name).syncPaused || this.schedulePush(t.name);
257
+ }).catch((e) => {
258
+ this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, e);
259
+ });
260
+ }
261
+ }), this.options.autostart && this.startSync(t.name).catch((e) => {
262
+ this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, e);
263
+ });
264
+ }
265
+ flushScheduledPushes() {
266
+ this.scheduledPushes.forEach((e) => {
267
+ this.pushChanges(e).catch(() => {});
268
+ }), this.scheduledPushes.clear();
269
+ }
270
+ schedulePush(e) {
271
+ this.scheduledPushes.add(e), this.debouncedFlush();
272
+ }
273
+ async startAll() {
274
+ await Promise.all([...this.collections.keys()].map((e) => this.startSync(e)));
275
+ }
276
+ async startSync(e) {
277
+ let t = this.getCollectionProperties(e);
278
+ if (!t.syncPaused) return;
279
+ this.schedulePush(e);
280
+ let n = this.options.registerRemoteChange ? await this.options.registerRemoteChange(t.options, async (t) => {
281
+ this.isDisposed || (t == null ? await this.sync(e) : await this.getSyncQueue(e).add(async () => {
282
+ let n = Date.now(), r = await this.syncOperations.insert({
283
+ start: n,
284
+ collectionName: e,
285
+ instanceId: this.instanceId,
286
+ status: "active"
287
+ });
288
+ await this.syncWithData(e, t).then(async () => {
289
+ this.isDisposed || (await this.syncOperations.removeMany({
290
+ id: { $ne: r },
291
+ collectionName: e,
292
+ $or: [{ end: { $lte: n } }, { status: "active" }]
293
+ }), await this.syncOperations.updateOne({ id: r }, { $set: {
294
+ status: "done",
295
+ end: Date.now()
296
+ } }));
297
+ }).catch(async (t) => {
298
+ throw this.options.onError && this.options.onError(this.getCollectionProperties(e).options, t), await this.syncOperations.updateOne({ id: r }, { $set: {
299
+ status: "error",
300
+ end: Date.now(),
301
+ error: t.stack || t.message
302
+ } }), t;
303
+ });
304
+ }));
305
+ }) : void 0;
306
+ this.collections.set(e, {
307
+ ...t,
308
+ syncPaused: !1,
309
+ cleanupFunction: n
310
+ });
311
+ }
312
+ async pauseAll() {
313
+ await Promise.all([...this.collections.keys()].map((e) => this.pauseSync(e)));
314
+ }
315
+ async pauseSync(e) {
316
+ let t = this.getCollectionProperties(e);
317
+ t.syncPaused || (t.cleanupFunction && await t.cleanupFunction(), this.collections.set(e, {
318
+ ...t,
319
+ cleanupFunction: void 0,
320
+ syncPaused: !0
321
+ }));
322
+ }
323
+ async syncAll() {
324
+ if (this.isDisposed) throw Error("SyncManager is disposed");
325
+ let e = [];
326
+ if (await Promise.all([...this.collections.keys()].map((t) => this.sync(t).catch((n) => {
327
+ e.push({
328
+ id: t,
329
+ error: n
330
+ });
331
+ }))), e.length > 0) throw Error(`Error while syncing collections:\n${e.map((e) => `${e.id}: ${e.error.message}`).join("\n\n")}`);
332
+ }
333
+ isSyncing(e, t) {
334
+ let n = this.syncOperations.findOne({
335
+ ...e ? { collectionName: e } : {},
336
+ status: "active"
337
+ }, {
338
+ fields: { status: 1 },
339
+ async: t
340
+ });
341
+ return n instanceof Promise ? n.then((e) => e != null) : n != null;
342
+ }
343
+ async isReady() {
344
+ await this.collectionsReady;
345
+ }
346
+ async sync(e, t = {}) {
347
+ if (this.isDisposed) throw Error("SyncManager is disposed");
348
+ await this.isReady();
349
+ let { options: n, readyPromise: r } = this.getCollectionProperties(e);
350
+ await r;
351
+ let i = await this.syncOperations.find({
352
+ collectionName: e,
353
+ instanceId: this.instanceId,
354
+ status: "active"
355
+ }, {
356
+ reactive: !1,
357
+ async: !0
358
+ }).count() > 0, a = Date.now(), o = null;
359
+ await new Promise((e) => {
360
+ setTimeout(e, 0);
361
+ });
362
+ let s = async () => {
363
+ let r = await this.syncOperations.findOne({
364
+ collectionName: e,
365
+ status: "done"
366
+ }, {
367
+ sort: { end: -1 },
368
+ reactive: !1,
369
+ async: !0
370
+ });
371
+ if (t?.onlyWithChanges && await this.changes.find({
372
+ collectionName: e,
373
+ time: { $lte: a }
374
+ }, {
375
+ sort: { time: 1 },
376
+ reactive: !1,
377
+ async: !0
378
+ }).count() === 0) return;
379
+ i || (o = await this.syncOperations.insert({
380
+ start: a,
381
+ collectionName: e,
382
+ instanceId: this.instanceId,
383
+ status: "active"
384
+ }));
385
+ let s = await this.options.pull(n, {
386
+ lastFinishedSyncStart: r?.start,
387
+ lastFinishedSyncEnd: r?.end
388
+ });
389
+ await this.syncWithData(e, s);
390
+ };
391
+ await (t?.force ? s() : this.getSyncQueue(e).add(s)).catch(async (e) => {
392
+ throw o != null && (this.options.onError && this.options.onError(n, e), await this.syncOperations.updateOne({ id: o }, { $set: {
393
+ status: "error",
394
+ end: Date.now(),
395
+ error: e.stack || e.message
396
+ } })), e;
397
+ }), o != null && (await this.syncOperations.removeMany({
398
+ id: { $ne: o },
399
+ collectionName: e,
400
+ $or: [{ end: { $lte: a } }, { status: "active" }]
401
+ }), await this.syncOperations.updateOne({ id: o }, { $set: {
402
+ status: "done",
403
+ end: Date.now()
404
+ } }));
405
+ }
406
+ async pushChanges(e) {
407
+ await this.sync(e, { onlyWithChanges: !0 });
408
+ }
409
+ async syncWithData(e, t) {
410
+ if (this.isDisposed) return;
411
+ let { collection: n, options: r } = this.getCollectionProperties(e), i = Date.now(), a = await this.syncOperations.findOne({
412
+ collectionName: e,
413
+ status: "done"
414
+ }, {
415
+ sort: { end: -1 },
416
+ reactive: !1,
417
+ async: !0
418
+ }), o = await this.snapshots.findOne({ collectionName: e }, {
419
+ sort: { time: -1 },
420
+ reactive: !1,
421
+ async: !0
422
+ }), s = await this.changes.find({
423
+ collectionName: e,
424
+ time: { $lte: i }
425
+ }, {
426
+ sort: { time: 1 },
427
+ reactive: !1,
428
+ async: !0
429
+ }).fetch();
430
+ await p({
431
+ changes: s,
432
+ lastSnapshot: o?.items,
433
+ data: t,
434
+ pull: () => this.options.pull(r, {
435
+ lastFinishedSyncStart: a?.start,
436
+ lastFinishedSyncEnd: a?.end
437
+ }),
438
+ push: (e) => this.options.push(r, {
439
+ changes: e,
440
+ rawChanges: s
441
+ }),
442
+ insert: async (t) => {
443
+ this.remoteChanges.push({
444
+ collectionName: e,
445
+ type: "insert",
446
+ data: t
447
+ }, {
448
+ collectionName: e,
449
+ type: "update",
450
+ data: {
451
+ id: t.id,
452
+ modifier: { $set: t }
453
+ }
454
+ }), await n.replaceOne({ id: t.id }, t, { upsert: !0 });
455
+ },
456
+ update: async (t, r) => {
457
+ this.remoteChanges.push({
458
+ collectionName: e,
459
+ type: "insert",
460
+ data: {
461
+ id: t,
462
+ ...r.$set
463
+ }
464
+ }, {
465
+ collectionName: e,
466
+ type: "update",
467
+ data: {
468
+ id: t,
469
+ modifier: r
470
+ }
471
+ }), await n.updateOne({ id: t }, {
472
+ ...r,
473
+ $setOnInsert: { id: t }
474
+ }, { upsert: !0 });
475
+ },
476
+ remove: async (t) => {
477
+ await n.find({ id: t }, {
478
+ reactive: !1,
479
+ async: !0
480
+ }).count() > 0 && (this.remoteChanges.push({
481
+ collectionName: e,
482
+ type: "remove",
483
+ data: t
484
+ }), await n.removeOne({ id: t }));
485
+ },
486
+ batch: async (e) => n.batch(async () => {
487
+ await e();
488
+ })
489
+ }).then(async (t) => {
490
+ if (this.isDisposed) return;
491
+ if (await this.snapshots.removeMany({
492
+ collectionName: e,
493
+ time: { $lte: i }
494
+ }), await this.changes.removeMany({
495
+ collectionName: e,
496
+ id: { $in: s.map((e) => e.id) }
497
+ }), await this.snapshots.insert({
498
+ time: i,
499
+ collectionName: e,
500
+ items: t
501
+ }), await new Promise((e) => {
502
+ setTimeout(e, 0);
503
+ }), await this.changes.find({ collectionName: e }, {
504
+ reactive: !1,
505
+ async: !0
506
+ }).count() > 0) {
507
+ await this.sync(e, {
508
+ force: !0,
509
+ onlyWithChanges: !0
510
+ });
511
+ return;
512
+ }
513
+ let r = await n.find({ id: { $nin: t.map((e) => e.id) } }, {
514
+ reactive: !1,
515
+ async: !0
516
+ }).map((e) => e.id);
517
+ await n.batch(async () => {
518
+ await Promise.all(t.map(async (t) => {
519
+ this.remoteChanges.push({
520
+ collectionName: e,
521
+ type: "insert",
522
+ data: t
523
+ }, {
524
+ collectionName: e,
525
+ type: "update",
526
+ data: {
527
+ id: t.id,
528
+ modifier: { $set: t }
529
+ }
530
+ }), await n.replaceOne({ id: t.id }, t, { upsert: !0 });
531
+ })), await Promise.all(r.map(async (e) => {
532
+ await n.removeOne({ id: e });
533
+ }));
534
+ });
535
+ });
536
+ }
605
537
  };
606
- //# sourceMappingURL=index.mjs.map
538
+ //#endregion
539
+ export { m as SyncManager };
540
+
541
+ //# sourceMappingURL=index.mjs.map