@signaldb/sync 2.0.0-beta.0 → 2.0.0-beta.10

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