@signaldb/sync 2.0.0-beta.20 → 2.0.0-beta.22

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.
@@ -1,10 +1,15 @@
1
- import { BaseItem, DataAdapter, ReactivityAdapter, Changeset, Collection } from '@signaldb/core';
1
+ import { BaseItem, DataAdapter, ReactivityAdapter, Changeset, Modifier, Collection } from '@signaldb/core';
2
2
  import { default as PromiseQueue } from './utils/PromiseQueue';
3
3
  import { Change, LoadResponse, Snapshot, SyncOperation } from './types';
4
4
  type SyncOptions<T extends Record<string, any>> = {
5
5
  name: string;
6
6
  } & T;
7
7
  type CleanupFunction = (() => void | Promise<void>) | void;
8
+ interface SyncListeners<ItemType extends BaseItem<IdType>, IdType = any> {
9
+ added: (item: ItemType) => void;
10
+ changed: (item: ItemType, modifier: Modifier<ItemType>) => void;
11
+ removed: (item: ItemType) => void;
12
+ }
8
13
  interface Options<CollectionOptions extends Record<string, any>, ItemType extends BaseItem<IdType> = BaseItem, IdType = any> {
9
14
  pull: (collectionOptions: SyncOptions<CollectionOptions>, pullParameters: {
10
15
  lastFinishedSyncStart?: number;
@@ -58,6 +63,7 @@ export default class SyncManager<CollectionOptions extends Record<string, any>,
58
63
  readyPromise: Promise<void>;
59
64
  syncPaused: boolean;
60
65
  cleanupFunction?: CleanupFunction;
66
+ syncListeners?: SyncListeners<ItemType, IdType>;
61
67
  }>;
62
68
  protected changes: Collection<Change<ItemType>, string>;
63
69
  protected snapshots: Collection<Snapshot<ItemType>, string>;
@@ -84,6 +90,19 @@ export default class SyncManager<CollectionOptions extends Record<string, any>,
84
90
  */
85
91
  constructor(options: Options<CollectionOptions, ItemType, IdType>);
86
92
  protected getSyncQueue(name: string): PromiseQueue;
93
+ /**
94
+ * Removes the event listeners this sync manager attached to a registered collection.
95
+ * The collection itself is left untouched, including listeners registered by the user.
96
+ * @param name Name of the collection
97
+ */
98
+ protected detachSyncListeners(name: string): void;
99
+ /**
100
+ * Removes a collection from the sync manager. Pauses the sync process for the collection
101
+ * and detaches all event listeners the sync manager attached to it. The collection itself
102
+ * won't be disposed and stays usable afterwards.
103
+ * @param name Name of the collection
104
+ */
105
+ removeCollection(name: string): Promise<void>;
87
106
  /**
88
107
  * Clears all internal data structures
89
108
  */
@@ -108,6 +127,7 @@ export default class SyncManager<CollectionOptions extends Record<string, any>,
108
127
  readyPromise: Promise<void>;
109
128
  syncPaused: boolean;
110
129
  cleanupFunction?: CleanupFunction | undefined;
130
+ syncListeners?: SyncListeners<ItemType, IdType>;
111
131
  };
112
132
  /**
113
133
  * Adds a collection to the sync manager.
@@ -4,6 +4,10 @@ import { Change } from './types';
4
4
  * applies changes to a collection of items
5
5
  * @param items The items to apply the changes to
6
6
  * @param changes The changes to apply to the items
7
+ * @param [fallbackItems] Items that are used as a base for update changes that
8
+ * target an item which isn't part of `items` anymore (e.g. because it was removed
9
+ * remotely). Without them, the resulting item would only contain the fields of the
10
+ * modifier and all other fields would be lost.
7
11
  * @returns The new items after applying the changes
8
12
  */
9
- export default function applyChanges<ItemType extends BaseItem<IdType>, IdType>(items: ItemType[], changes: Change<ItemType, IdType>[]): ItemType[];
13
+ export default function applyChanges<ItemType extends BaseItem<IdType>, IdType>(items: ItemType[], changes: Change<ItemType, IdType>[], fallbackItems?: ItemType[]): ItemType[];
package/dist/index.mjs CHANGED
@@ -38,11 +38,13 @@ var o = class {
38
38
  //#endregion
39
39
  //#region src/computeChanges.ts
40
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);
41
+ let n = [], r = Object.keys(e), i = Object.keys(t), a = /* @__PURE__ */ new Set([...r, ...i]);
42
+ for (let r of a) if (t[r] !== e[r]) {
43
+ if (typeof t[r] == "object" && typeof e[r] == "object" && t[r] != null && e[r] != null) {
44
+ let i = s(e[r], t[r]);
45
+ for (let e of i) n.push(`${r}.${e}`);
46
+ } else n.push(r);
47
+ }
46
48
  return n;
47
49
  }
48
50
  function c(e, t) {
@@ -63,7 +65,7 @@ function c(e, t) {
63
65
  //#region src/getSnapshot.ts
64
66
  function l(e, t) {
65
67
  if (t.items != null) return t.items;
66
- let n = e || [];
68
+ let n = [...e || []];
67
69
  return t.changes.added.forEach((e) => {
68
70
  let t = n.findIndex((t) => t.id === e.id);
69
71
  t === -1 ? n.push(e) : n[t] = e;
@@ -77,21 +79,21 @@ function l(e, t) {
77
79
  }
78
80
  //#endregion
79
81
  //#region src/applyChanges.ts
80
- function u(e, t) {
81
- let n = new Map(e.map((e) => [e.id, e]));
82
+ function u(e, t, n) {
83
+ let i = new Map(e.map((e) => [e.id, e])), a = n == null ? void 0 : new Map(n.map((e) => [e.id, e]));
82
84
  return t.forEach((e) => {
83
- if (e.type === "remove") n.delete(e.data);
85
+ if (e.type === "remove") i.delete(e.data);
84
86
  else if (e.type === "insert") {
85
- let t = n.get(e.data.id);
86
- n.set(e.data.id, t ? {
87
+ let t = i.get(e.data.id);
88
+ i.set(e.data.id, t ? {
87
89
  ...t,
88
90
  ...e.data
89
91
  } : e.data);
90
92
  } 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
+ let t = i.get(e.data.id) ?? a?.get(e.data.id);
94
+ i.set(e.data.id, r(t || { id: e.data.id }, e.data.modifier));
93
95
  }
94
- }), [...n.values()];
96
+ }), [...i.values()];
95
97
  }
96
98
  //#endregion
97
99
  //#region src/sync.ts
@@ -106,7 +108,7 @@ async function p({ changes: e, lastSnapshot: t, data: n, pull: r, push: i, inser
106
108
  if (e.length > 0) {
107
109
  let t = u(h, e);
108
110
  if (f(h, t)) {
109
- let n = u(g, e), a = c(g, n);
111
+ let n = u(g, e, h), a = c(g, n);
110
112
  d(a) && (await i(a), m = await r(), g = l(g, m)), h = t;
111
113
  }
112
114
  }
@@ -157,8 +159,19 @@ var m = class {
157
159
  getSyncQueue(e) {
158
160
  return this.syncQueues.get(e) ?? this.syncQueues.set(e, new o()), this.syncQueues.get(e);
159
161
  }
162
+ detachSyncListeners(e) {
163
+ let t = this.collections.get(e);
164
+ if (t == null) return;
165
+ let { collection: n, syncListeners: r } = t;
166
+ r != null && (n.off("added", r.added), n.off("changed", r.changed), n.off("removed", r.removed));
167
+ }
168
+ async removeCollection(e) {
169
+ this.collections.has(e) && (await this.pauseSync(e), this.detachSyncListeners(e), this.collections.delete(e), this.syncQueues.delete(e), this.scheduledPushes.delete(e));
170
+ }
160
171
  async dispose() {
161
- this.isDisposed = !0, this.collections.clear(), this.syncQueues.clear(), this.remoteChanges.splice(0), await Promise.all([
172
+ this.isDisposed = !0;
173
+ for (let e of this.collections.keys()) this.detachSyncListeners(e);
174
+ this.collections.clear(), this.syncQueues.clear(), this.scheduledPushes.clear(), this.remoteChanges.splice(0), await Promise.all([
162
175
  this.changes.dispose(),
163
176
  this.snapshots.dispose(),
164
177
  this.syncOperations.dispose()
@@ -175,24 +188,18 @@ var m = class {
175
188
  }
176
189
  addCollection(e, t) {
177
190
  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
- });
191
+ this.detachSyncListeners(t.name);
184
192
  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;
193
+ 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
194
  return !1;
187
195
  }, r = (e, t) => {
188
196
  let n = [...this.remoteChanges];
189
197
  for (let r = 0; r < n.length; r += 1) {
190
198
  let i = n[r];
191
- i != null && i.collectionName === e && (i.type === "remove" && i.data !== t || i.data.id === t && (n[r] = null));
199
+ i != null && i.collectionName === e && (i.type !== "remove" || i.data === t) && i.data.id === t && (n[r] = null);
192
200
  }
193
201
  this.remoteChanges = n.filter((e) => e != null);
194
- };
195
- e.on("added", (e) => {
202
+ }, i = (e) => {
196
203
  if (!this.isDisposed) {
197
204
  if (n({
198
205
  collectionName: t.name,
@@ -213,7 +220,7 @@ var m = class {
213
220
  this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, e);
214
221
  });
215
222
  }
216
- }), e.on("changed", ({ id: e }, i) => {
223
+ }, a = ({ id: e }, i) => {
217
224
  if (this.isDisposed) return;
218
225
  let a = {
219
226
  id: e,
@@ -237,7 +244,7 @@ var m = class {
237
244
  }).catch((e) => {
238
245
  this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, e);
239
246
  });
240
- }), e.on("removed", ({ id: e }) => {
247
+ }, o = ({ id: e }) => {
241
248
  if (!this.isDisposed) {
242
249
  if (n({
243
250
  collectionName: t.name,
@@ -258,6 +265,19 @@ var m = class {
258
265
  this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, e);
259
266
  });
260
267
  }
268
+ };
269
+ e.on("added", i), e.on("changed", a), e.on("removed", o);
270
+ let s = {
271
+ added: i,
272
+ changed: a,
273
+ removed: o
274
+ };
275
+ this.collections.set(t.name, {
276
+ collection: e,
277
+ options: t,
278
+ readyPromise: e.ready(),
279
+ syncPaused: !0,
280
+ syncListeners: s
261
281
  }), this.options.autostart && this.startSync(t.name).catch((e) => {
262
282
  this.options.onError && this.options.onError(this.getCollectionProperties(t.name).options, e);
263
283
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/utils/debounce.ts","../src/utils/PromiseQueue.ts","../src/computeChanges.ts","../src/getSnapshot.ts","../src/applyChanges.ts","../src/sync.ts","../src/SyncManager.ts"],"sourcesContent":["type DebounceOptions = {\n leading?: boolean,\n trailing?: boolean,\n}\n\n/**\n * Debounces a function.\n * @param fn Function to debounce\n * @param wait Time to wait before calling the function.\n * @param [options] Debounce options\n * @param [options.leading] Whether to call the function on the leading edge of the wait interval.\n * @param [options.trailing] Whether to call the function on the trailing edge of the wait interval.\n * @returns The debounced function.\n */\nexport default function debounce<Arguments extends any[], T, ThisType>(\n fn: (this: ThisType, ...args: Arguments) => T,\n wait: number,\n options: DebounceOptions = {},\n): (...args: Arguments) => T {\n let timeout: ReturnType<typeof setTimeout> | null\n let result: T | null\n\n const { leading = false, trailing = true } = options\n\n /**\n * The debounced function that will be returned.\n * @param this The context to bind the function to.\n * @param args The arguments to pass to the function.\n * @returns The result of the debounced function.\n */\n function debounced(this: ThisType, ...args: Arguments) {\n const shouldCallImmediately = leading && !timeout\n const shouldCallTrailing = trailing && !timeout\n\n if (timeout) {\n clearTimeout(timeout)\n }\n\n timeout = setTimeout(() => {\n timeout = null\n if (trailing && !shouldCallImmediately) {\n result = fn.apply(this, args)\n }\n }, wait)\n\n if (shouldCallImmediately) {\n result = fn.apply(this, args)\n } else if (!shouldCallTrailing) {\n result = null\n }\n\n return result as T\n }\n return debounced\n}\n","/**\n * Class for queuing promises to be executed one after the other.\n * This is useful for tasks that should not be executed in parallel.\n * @example\n * const queue = new PromiseQueue();\n * queue.add(() => fetch('https://example.com/api/endpoint1'));\n * queue.add(() => fetch('https://example.com/api/endpoint2'));\n * // The second fetch will only be executed after the first one is done.\n */\nexport default class PromiseQueue {\n private queue: (() => Promise<any>)[] = []\n private pendingPromise: boolean = false\n\n /**\n * Method to add a new promise to the queue and returns a promise that resolves when this task is done\n * @param task Function that returns a promise that will be added to the queue\n * @returns Promise that resolves when the task is done\n */\n add(task: () => Promise<any>): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n // Wrap the task with the resolve and reject to control its completion from the outside\n this.queue.push(() => task()\n .then(resolve)\n .catch((error: Error) => {\n reject(error)\n throw error\n }))\n this.dequeue()\n })\n }\n\n /**\n * Method to check if there is a pending promise in the queue\n * @returns True if there is a pending promise, false otherwise\n */\n public hasPendingPromise(): boolean {\n return this.pendingPromise\n }\n\n /**\n * Method to process the queue\n */\n private dequeue() {\n if (this.pendingPromise || this.queue.length === 0) {\n return\n }\n const task = this.queue.shift()\n if (!task) return\n this.pendingPromise = true\n task()\n .then(() => {\n this.pendingPromise = false\n this.dequeue()\n })\n .catch(() => {\n this.pendingPromise = false\n this.dequeue()\n })\n }\n}\n","import type { BaseItem } from '@signaldb/core'\nimport { isEqual } from '@signaldb/core'\n\n/**\n * Computes the modified fields between two items recursively.\n * @param oldItem The old item\n * @param newItem The new item\n * @returns The modified fields\n */\nexport function computeModifiedFields<T extends Record<string, any>>(\n oldItem: T,\n newItem: T,\n): string[] {\n const modifiedFields: string[] = []\n\n const oldKeys = Object.keys(oldItem)\n const newKeys = Object.keys(newItem)\n const allKeys = new Set([...oldKeys, ...newKeys])\n\n for (const key of allKeys) {\n if (newItem[key] !== oldItem[key]) {\n if (typeof newItem[key] === 'object' && typeof oldItem[key] === 'object' && newItem[key] != null && oldItem[key] != null) {\n const nestedModifiedFields = computeModifiedFields(oldItem[key], newItem[key])\n for (const nestedField of nestedModifiedFields) {\n modifiedFields.push(`${key}.${nestedField}`)\n }\n } else {\n modifiedFields.push(key)\n }\n }\n }\n\n return modifiedFields\n}\n\n/**\n * Compute changes between two arrays of items.\n * @param oldItems Array of the old items\n * @param newItems Array of the new items\n * @returns The changeset\n */\nexport default function computeChanges<ItemType extends BaseItem<IdType>, IdType>(\n oldItems: ItemType[],\n newItems: ItemType[],\n) {\n const added: ItemType[] = []\n const modified: ItemType[] = []\n const modifiedFields: Map<IdType, string[]> = new Map()\n const removed: ItemType[] = []\n\n const oldItemsMap = new Map(oldItems.map(item => [item.id, item]))\n const newItemsMap = new Map(newItems.map(item => [item.id, item]))\n\n for (const [id, oldItem] of oldItemsMap) {\n const newItem = newItemsMap.get(id)\n if (!newItem) {\n removed.push(oldItem)\n } else if (!isEqual(newItem, oldItem)) {\n modifiedFields.set(newItem.id, computeModifiedFields(oldItem, newItem))\n modified.push(newItem)\n }\n }\n\n for (const [id, newItem] of newItemsMap) {\n if (!oldItemsMap.has(id)) {\n added.push(newItem)\n }\n }\n\n return {\n added,\n modified,\n modifiedFields,\n removed,\n }\n}\n","import type { BaseItem } from '@signaldb/core'\nimport type { LoadResponse } from './types'\n\n/**\n * Gets the snapshot of items from the last snapshot and the changes.\n * @param lastSnapshot The last snapshot of items\n * @param data The changes to apply to the last snapshot\n * @returns The new snapshot of items\n */\nexport default function getSnapshot<ItemType extends BaseItem<IdType>, IdType>(\n lastSnapshot: ItemType[] | undefined,\n data: LoadResponse<ItemType>,\n) {\n if (data.items != null) return data.items\n\n const items = lastSnapshot || []\n data.changes.added.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index === -1) {\n items.push(item)\n } else {\n items[index] = item\n }\n })\n data.changes.modified.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index === -1) {\n items.push(item)\n } else {\n items[index] = item\n }\n })\n data.changes.removed.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index !== -1) items.splice(index, 1)\n })\n return items\n}\n","import type { BaseItem } from '@signaldb/core'\nimport { modify } from '@signaldb/core'\nimport type { Change } from './types'\n\n/**\n * applies changes to a collection of items\n * @param items The items to apply the changes to\n * @param changes The changes to apply to the items\n * @returns The new items after applying the changes\n */\nexport default function applyChanges<ItemType extends BaseItem<IdType>, IdType>(\n items: ItemType[],\n changes: Change<ItemType, IdType>[],\n): ItemType[] {\n // Create initial map of items by ID\n const itemMap = new Map(items.map(item => [item.id, item]))\n\n changes.forEach((change) => {\n if (change.type === 'remove') {\n itemMap.delete(change.data)\n } else if (change.type === 'insert') {\n const existingItem = itemMap.get(change.data.id)\n itemMap.set(\n change.data.id,\n existingItem ? { ...existingItem, ...change.data } : change.data,\n )\n } else { // change.type === 'update'\n const existingItem = itemMap.get(change.data.id)\n itemMap.set(\n change.data.id,\n existingItem\n ? modify(existingItem, change.data.modifier)\n : modify({ id: change.data.id } as ItemType, change.data.modifier),\n )\n }\n })\n\n // Convert map back to array\n return [...itemMap.values()]\n}\n","import type { BaseItem, Modifier, Changeset } from '@signaldb/core'\nimport computeChanges from './computeChanges'\nimport type { Change, LoadResponse } from './types'\nimport getSnapshot from './getSnapshot'\nimport applyChanges from './applyChanges'\n\n/**\n * Checks if there are any changes in the given changeset.\n * @param changes The changeset to check.\n * @returns True if there are changes, false otherwise.\n */\nfunction hasChanges<T>(\n changes: Changeset<T>,\n) {\n return changes.added.length > 0\n || changes.modified.length > 0\n || changes.removed.length > 0\n}\n/**\n * Checks if there is a difference between the old items and the new items.\n * @param oldItems The old items.\n * @param newItems The new items.\n * @returns True if there is a difference, false otherwise.\n */\nfunction hasDifference<ItemType extends BaseItem<IdType>, IdType>(\n oldItems: ItemType[],\n newItems: ItemType[],\n) {\n return hasChanges(computeChanges(oldItems, newItems))\n}\n\ninterface Options<ItemType extends BaseItem<IdType>, IdType> {\n changes: Change<ItemType, IdType>[],\n lastSnapshot?: ItemType[],\n data: LoadResponse<ItemType>,\n pull: () => Promise<LoadResponse<ItemType>>,\n push: (changes: Changeset<ItemType> & {\n modifiedFields: Map<IdType, string[]>,\n }) => Promise<void>,\n insert: (item: ItemType) => Promise<void>,\n update: (id: IdType, modifier: Modifier<ItemType>) => Promise<void>,\n remove: (id: IdType) => Promise<void>,\n batch: (fn: () => Promise<void>) => Promise<void>,\n}\n\n/**\n * Does a sync operation based on the provided options. If changes are supplied, these will be rebased on the new data.\n * Afterwards the push method will be called with the remaining changes. A new snapshot will be created and returned.\n * @param options Sync options\n * @param options.changes Changes to call the push method with\n * @param [options.lastSnapshot] The last snapshot\n * @param options.data The new data\n * @param options.pull Method to pull new data\n * @param options.push Method to push changes\n * @param options.insert Method to insert an item\n * @param options.update Method to update an item\n * @param options.remove Method to remove an item\n * @param options.batch Method to batch multiple operations\n * @returns The new snapshot\n */\nexport default async function sync<ItemType extends BaseItem<IdType>, IdType>({\n changes,\n lastSnapshot,\n data,\n pull,\n push,\n insert,\n update,\n remove,\n batch,\n}: Options<ItemType, IdType>): Promise<ItemType[]> {\n let newData = data\n let previousSnapshot = lastSnapshot || []\n let newSnapshot = getSnapshot(lastSnapshot, newData)\n if (changes.length > 0) {\n // apply changes on last snapshot and check if there is a difference\n const lastSnapshotWithChanges = applyChanges(previousSnapshot, changes)\n if (hasDifference(previousSnapshot, lastSnapshotWithChanges)) {\n // if yes, apply the changes on the newSnapshot and check if there is a difference\n const newSnapshotWithChanges = applyChanges(newSnapshot, changes)\n const changesToPush = computeChanges<ItemType, IdType>(newSnapshot, newSnapshotWithChanges)\n if (hasChanges(changesToPush)) {\n // if yes, push the changes to the server\n await push(changesToPush)\n\n // pull new data afterwards to ensure that all server changes are applied\n newData = await pull()\n newSnapshot = getSnapshot(newSnapshot, newData)\n }\n previousSnapshot = lastSnapshotWithChanges\n }\n }\n\n // apply the new changes on the collection\n const newChanges = newData.changes == null\n ? computeChanges(previousSnapshot, newData.items)\n : newData.changes\n await batch(async () => {\n await Promise.all(newChanges.added.map(item => insert(item)))\n await Promise.all(newChanges.modified.map(item => update(item.id, { $set: item })))\n await Promise.all(newChanges.removed.map(item => remove(item.id)))\n })\n\n return newSnapshot\n}\n","import type {\n BaseItem,\n DataAdapter,\n ReactivityAdapter,\n Changeset,\n Selector,\n} from '@signaldb/core'\nimport { DefaultDataAdapter, Collection, randomId } from '@signaldb/core'\nimport debounce from './utils/debounce'\nimport PromiseQueue from './utils/PromiseQueue'\nimport sync from './sync'\nimport type { Change, LoadResponse, Snapshot, SyncOperation } from './types'\n\ntype SyncOptions<T extends Record<string, any>> = {\n name: string,\n} & T\n\ntype CleanupFunction = (() => void | Promise<void>) | void\n\ninterface Options<\n CollectionOptions extends Record<string, any>,\n ItemType extends BaseItem<IdType> = BaseItem,\n IdType = any,\n> {\n pull: (\n collectionOptions: SyncOptions<CollectionOptions>,\n pullParameters: {\n lastFinishedSyncStart?: number,\n lastFinishedSyncEnd?: number,\n },\n ) => Promise<LoadResponse<ItemType>>,\n push: (\n collectionOptions: SyncOptions<CollectionOptions>,\n pushParameters: {\n rawChanges: Omit<Change, 'id' | 'collectionName'>[],\n changes: Changeset<ItemType> & {\n modifiedFields: Map<IdType, string[]>,\n },\n },\n ) => Promise<void>,\n registerRemoteChange?: (\n collectionOptions: SyncOptions<CollectionOptions>,\n onChange: (data?: LoadResponse<ItemType>) => Promise<void>,\n ) => CleanupFunction | Promise<CleanupFunction>,\n\n id?: string,\n dataAdapter?: DataAdapter,\n reactivity?: ReactivityAdapter,\n onError?: (collectionOptions: SyncOptions<CollectionOptions>, error: Error) => void,\n\n autostart?: boolean,\n debounceTime?: number,\n}\n\n/**\n * Class to manage syncing of collections.\n * @template CollectionOptions\n * @template ItemType\n * @template IdType\n * @example\n * const syncManager = new SyncManager({\n * pull: async (collectionOptions) => {\n * const response = await fetch(`/api/collections/${collectionOptions.name}`)\n * return await response.json()\n * },\n * push: async (collectionOptions, { changes }) => {\n * await fetch(`/api/collections/${collectionOptions.name}`, {\n * method: 'POST',\n * body: JSON.stringify(changes),\n * })\n * },\n * })\n *\n * const collection = new Collection()\n * syncManager.addCollection(collection, {\n * name: 'todos',\n * })\n *\n * syncManager.sync('todos')\n */\nexport default class SyncManager<\n CollectionOptions extends Record<string, any>,\n ItemType extends BaseItem<IdType> = BaseItem,\n IdType = any,\n> {\n protected options: Options<CollectionOptions, ItemType, IdType>\n protected collections: Map<string, {\n collection: Collection<ItemType, IdType, any>,\n options: SyncOptions<CollectionOptions>,\n readyPromise: Promise<void>,\n syncPaused: boolean,\n cleanupFunction?: CleanupFunction,\n }> = new Map()\n\n protected changes: Collection<Change<ItemType>, string>\n protected snapshots: Collection<Snapshot<ItemType>, string>\n protected syncOperations: Collection<SyncOperation, string>\n protected scheduledPushes: Set<string> = new Set()\n protected remoteChanges: Omit<Change, 'id' | 'time'>[] = []\n protected syncQueues: Map<string, PromiseQueue> = new Map()\n protected collectionsReady: Promise<void>\n protected isDisposed = false\n protected instanceId = randomId()\n protected id: string\n protected debouncedFlush: () => void\n\n /**\n * @param options Collection options\n * @param options.pull Function to pull data from remote source.\n * @param options.push Function to push data to remote source.\n * @param [options.registerRemoteChange] Function to register a callback for remote changes.\n * @param [options.id] Unique identifier for this sync manager. Only nessesary if you have multiple sync managers.\n * @param [options.storageAdapter] Storage adapter to use for storing changes, snapshots and sync operations.\n * @param [options.reactivity] Reactivity adapter to use for reactivity.\n * @param [options.onError] Function to handle errors that occur async during syncing.\n * @param [options.autostart] Whether to automatically start syncing new collections.\n * @param [options.debounceTime] The time in milliseconds to debounce push operations.\n */\n constructor(options: Options<CollectionOptions, ItemType, IdType>) {\n this.options = {\n autostart: true,\n ...options,\n }\n this.id = this.options.id || 'default-sync-manager'\n const { reactivity } = this.options\n const dataAdapter = this.options.dataAdapter ?? new DefaultDataAdapter()\n this.changes = new Collection(`${this.options.id}-changes`, dataAdapter, {\n indices: ['collectionName'],\n reactivity,\n })\n this.snapshots = new Collection(`${this.options.id}-snapshots`, dataAdapter, {\n indices: ['collectionName'],\n reactivity,\n })\n this.syncOperations = new Collection(`${this.options.id}-sync-operations`, dataAdapter, {\n indices: ['collectionName', 'status'],\n reactivity,\n })\n\n const readiness = [\n Promise.resolve(this.syncOperations.isReady()),\n Promise.resolve(this.changes.isReady()),\n Promise.resolve(this.snapshots.isReady()),\n ]\n this.collectionsReady = Promise.all(readiness).then(() => { /* noop */ })\n\n this.changes.setMaxListeners(1000)\n this.snapshots.setMaxListeners(1000)\n this.syncOperations.setMaxListeners(1000)\n\n this.debouncedFlush = debounce(this.flushScheduledPushes, this.options.debounceTime ?? 100)\n }\n\n protected getSyncQueue(name: string) {\n if (this.syncQueues.get(name) == null) {\n this.syncQueues.set(name, new PromiseQueue())\n }\n return this.syncQueues.get(name) as PromiseQueue\n }\n\n /**\n * Clears all internal data structures\n */\n public async dispose() {\n this.isDisposed = true\n this.collections.clear()\n this.syncQueues.clear()\n this.remoteChanges.splice(0)\n await Promise.all([\n this.changes.dispose(),\n this.snapshots.dispose(),\n this.syncOperations.dispose(),\n ])\n }\n\n /**\n * Gets a collection with it's options by name\n * @deprecated Use getCollectionProperties instead.\n * @param name Name of the collection\n * @throws {Error} Will throw an error if the name wasn't found\n * @returns Tuple of collection and options\n */\n public getCollection(name: string) {\n const { collection, options } = this.getCollectionProperties(name)\n return [collection, options]\n }\n\n /**\n * Gets collection options by name\n * @param name Name of the collection\n * @throws {Error} Will throw an error if the name wasn't found\n * @returns An object of all properties of the collection\n */\n public getCollectionProperties(name: string) {\n const collectionParameters = this.collections.get(name)\n if (collectionParameters == null) throw new Error(`Collection with id '${name}' not found`)\n return collectionParameters\n }\n\n /**\n * Adds a collection to the sync manager.\n * @param collection Collection to add\n * @param options Options for the collection. The object needs at least a `name` property.\n * @param options.name Unique name of the collection\n */\n public addCollection<\n CollectionItem extends ItemType = ItemType,\n >(\n collection: Collection<CollectionItem, IdType, any>,\n options: SyncOptions<CollectionOptions>,\n ) {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n\n this.collections.set(options.name, {\n collection: collection as unknown as Collection<ItemType, IdType, any>,\n options,\n readyPromise: collection.ready(),\n syncPaused: true, // always start paused as the autostart will start it\n })\n\n const hasRemoteChange = (change: Omit<Change, 'id' | 'time'>) => {\n for (const remoteChange of this.remoteChanges) {\n if (remoteChange == null) continue\n if (remoteChange.collectionName !== change.collectionName) continue\n if (remoteChange.type !== change.type) continue\n\n if (change.type === 'remove' && remoteChange.data !== change.data) continue\n if (remoteChange.data.id !== change.data.id) continue\n return true\n }\n return false\n }\n const removeRemoteChanges = (collectionName: string, id: IdType) => {\n const newRemoteChanges: (Omit<Change, 'id' | 'time'> | null)[] = [...this.remoteChanges]\n for (let i = 0; i < newRemoteChanges.length; i += 1) {\n const item = newRemoteChanges[i]\n if (item == null) continue\n if (item.collectionName !== collectionName) continue\n if (item.type === 'remove' && item.data !== id) continue\n if (item.data.id !== id) continue\n newRemoteChanges[i] = null\n }\n this.remoteChanges = newRemoteChanges.filter(item => item != null)\n }\n\n collection.on('added', (item) => {\n if (this.isDisposed) return\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'insert', data: item })) {\n removeRemoteChanges(options.name, item.id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'insert',\n data: item,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n })\n collection.on('changed', ({ id }, modifier) => {\n if (this.isDisposed) return\n const data = { id, modifier }\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'update', data })) {\n removeRemoteChanges(options.name, id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'update',\n data,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n })\n collection.on('removed', ({ id }) => {\n if (this.isDisposed) return\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'remove', data: id })) {\n removeRemoteChanges(options.name, id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'remove',\n data: id,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n })\n\n if (this.options.autostart) {\n this.startSync(options.name)\n .catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n }\n }\n\n protected flushScheduledPushes() {\n this.scheduledPushes.forEach((name) => {\n this.pushChanges(name).catch(() => { /* error handler is called in sync */ })\n })\n this.scheduledPushes.clear()\n }\n\n protected schedulePush(name: string) {\n this.scheduledPushes.add(name)\n this.debouncedFlush()\n }\n\n /**\n * Setup all collections to be synced with remote changes\n * and enable automatic pushing changes to the remote source.\n */\n public async startAll() {\n await Promise.all([...this.collections.keys()].map(id =>\n this.startSync(id)))\n }\n\n /**\n * Setup a collection to be synced with remote changes\n * and enable automatic pushing changes to the remote source.\n * @param name Name of the collection\n */\n public async startSync(name: string) {\n const collectionParameters = this.getCollectionProperties(name)\n if (!collectionParameters.syncPaused) return // already started\n\n this.schedulePush(name) // push changes that were made while paused\n\n const cleanupFunction = this.options.registerRemoteChange\n ? await this.options.registerRemoteChange(\n collectionParameters.options,\n async (data) => {\n if (this.isDisposed) return\n // eslint-disable-next-line unicorn/prefer-ternary -- this is easier to read than a ternary operator\n if (data == null) {\n // if no data is provided, we will sync the collection with the remote source\n await this.sync(name)\n } else {\n // if data is provided, we will sync the collection with the provided data\n await this.getSyncQueue(name).add(async () => {\n const syncTime = Date.now()\n const syncId = await this.syncOperations.insert({\n start: syncTime,\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n })\n await this.syncWithData(name, data)\n .then(async () => {\n if (this.isDisposed) return\n // clean up old sync operations\n await this.syncOperations.removeMany({\n id: { $ne: syncId },\n collectionName: name,\n $or: [\n { end: { $lte: syncTime } },\n { status: 'active' },\n ],\n })\n\n // update sync operation status to done after everthing was finished\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'done', end: Date.now() },\n })\n })\n .catch(async (error: Error) => {\n if (this.options.onError) {\n this.options.onError(this.getCollectionProperties(name).options, error)\n }\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'error', end: Date.now(), error: error.stack || error.message },\n })\n throw error\n })\n })\n }\n },\n )\n : undefined\n this.collections.set(name, {\n ...collectionParameters,\n syncPaused: false,\n cleanupFunction,\n })\n }\n\n /**\n * Pauses the sync process for all collections.\n * This means that the collections will not be synced with remote changes\n * and changes will not automatically be pushed to the remote source.\n */\n public async pauseAll() {\n await Promise.all([...this.collections.keys()].map(id =>\n this.pauseSync(id)))\n }\n\n /**\n * Pauses the sync process for a collection.\n * This means that the collection will not be synced with remote changes\n * and changes will not automatically be pushed to the remote source.\n * @param name Name of the collection\n */\n public async pauseSync(name: string) {\n const collectionParameters = this.getCollectionProperties(name)\n if (collectionParameters.syncPaused) return // already paused\n if (collectionParameters.cleanupFunction) await collectionParameters.cleanupFunction()\n this.collections.set(name, {\n ...collectionParameters,\n cleanupFunction: undefined,\n syncPaused: true,\n })\n }\n\n /**\n * Starts the sync process for all collections\n */\n public async syncAll() {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n const errors: { id: string, error: Error }[] = []\n await Promise.all([...this.collections.keys()].map(id =>\n this.sync(id).catch((error: Error) => {\n errors.push({ id, error })\n })))\n if (errors.length > 0) throw new Error(`Error while syncing collections:\\n${errors.map(error => `${error.id}: ${error.error.message}`).join('\\n\\n')}`)\n }\n\n /**\n * Checks if a collection is currently beeing synced\n * @param [name] Name of the collection. If not provided, it will check if any collection is currently beeing synced.\n * @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.\n * @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.\n */\n public isSyncing(\n name: string | undefined,\n async: true,\n ): Promise<boolean>\n\n public isSyncing(\n name?: string,\n async?: false,\n ): boolean\n\n public isSyncing<Async extends boolean = false>(\n name?: string,\n async?: Async,\n ): Promise<boolean> | boolean {\n const itemOrPromise = this.syncOperations.findOne({\n ...name ? { collectionName: name } : {},\n status: 'active',\n }, { fields: { status: 1 }, async })\n if (itemOrPromise instanceof Promise) {\n return itemOrPromise\n .then(item => item != null) as Async extends true ? Promise<boolean> : boolean\n }\n return (itemOrPromise != null) as Async extends true ? Promise<boolean> : boolean\n }\n\n /**\n * Checks if the sync manager is ready to sync.\n * @returns A promise that resolves when the sync manager is ready to sync.\n */\n public async isReady() {\n await this.collectionsReady\n }\n\n /**\n * Starts the sync process for a collection\n * @param name Name of the collection\n * @param options Options for the sync process.\n * @param options.force If true, the sync process will be started even if there are no changes and onlyWithChanges is true.\n * @param options.onlyWithChanges If true, the sync process will only be started if there are changes.\n */\n public async sync(name: string, options: { force?: boolean, onlyWithChanges?: boolean } = {}) {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n await this.isReady()\n const { options: collectionOptions, readyPromise } = this.getCollectionProperties(name)\n await readyPromise\n\n const hasActiveSyncs = await this.syncOperations.find({\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n }, {\n reactive: false,\n async: true,\n }).count() > 0\n const syncTime = Date.now()\n let syncId: string | null = null\n\n // schedule for next tick to allow other tasks to run first\n await new Promise((resolve) => {\n setTimeout(resolve, 0)\n })\n const doSync = async () => {\n const lastFinishedSync = await this.syncOperations.findOne({\n collectionName: name,\n status: 'done',\n }, {\n sort: { end: -1 },\n reactive: false,\n async: true,\n })\n if (options?.onlyWithChanges) {\n const currentChanges = await this.changes.find({\n collectionName: name,\n time: { $lte: syncTime },\n }, {\n sort: { time: 1 },\n reactive: false,\n async: true,\n }).count()\n if (currentChanges === 0) return\n }\n\n if (!hasActiveSyncs) {\n syncId = await this.syncOperations.insert({\n start: syncTime,\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n })\n }\n const data = await this.options.pull(collectionOptions, {\n lastFinishedSyncStart: lastFinishedSync?.start,\n lastFinishedSyncEnd: lastFinishedSync?.end,\n })\n\n await this.syncWithData(name, data)\n }\n\n await (options?.force ? doSync() : this.getSyncQueue(name).add(doSync))\n .catch(async (error: Error) => {\n if (syncId != null) {\n if (this.options.onError) this.options.onError(collectionOptions, error)\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'error', end: Date.now(), error: error.stack || error.message },\n })\n }\n throw error\n })\n\n if (syncId != null) {\n // clean up old sync operations\n await this.syncOperations.removeMany({\n id: { $ne: syncId },\n collectionName: name,\n $or: [\n { end: { $lte: syncTime } },\n { status: 'active' },\n ],\n })\n\n // update sync operation status to done after everthing was finished\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'done', end: Date.now() },\n })\n }\n }\n\n /**\n * Starts the push process for a collection (sync process but only if there are changes)\n * @param name Name of the collection\n */\n public async pushChanges(name: string) {\n await this.sync(name, {\n onlyWithChanges: true,\n })\n }\n\n protected async syncWithData(\n name: string,\n data: LoadResponse<ItemType>,\n ) {\n if (this.isDisposed) return\n const { collection, options: collectionOptions } = this.getCollectionProperties(name)\n\n const syncTime = Date.now()\n\n const lastFinishedSync = await this.syncOperations.findOne({\n collectionName: name,\n status: 'done',\n }, {\n sort: { end: -1 },\n reactive: false,\n async: true,\n })\n const lastSnapshot = await this.snapshots.findOne({\n collectionName: name,\n } as Selector<Snapshot<any>>, {\n sort: { time: -1 },\n reactive: false,\n async: true,\n })\n const currentChanges = await this.changes.find({\n collectionName: name,\n time: { $lte: syncTime },\n }, {\n sort: { time: 1 },\n reactive: false,\n async: true,\n }).fetch()\n\n await sync<ItemType, ItemType['id']>({\n changes: currentChanges,\n lastSnapshot: lastSnapshot?.items,\n data,\n pull: () => this.options.pull(collectionOptions, {\n lastFinishedSyncStart: lastFinishedSync?.start,\n lastFinishedSyncEnd: lastFinishedSync?.end,\n }),\n push: changes => this.options.push(collectionOptions, {\n changes,\n rawChanges: currentChanges,\n }),\n insert: async (item) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: item,\n }, {\n collectionName: name,\n type: 'update',\n data: { id: item.id, modifier: { $set: item } },\n })\n\n // replace the item\n await collection.replaceOne({ id: item.id } as Selector<any>, item, { upsert: true })\n },\n update: async (itemId, modifier) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: { id: itemId, ...modifier.$set },\n }, {\n collectionName: name,\n type: 'update',\n data: { id: itemId, modifier },\n })\n\n await collection.updateOne({ id: itemId } as Selector<any>, {\n ...modifier,\n $setOnInsert: { id: itemId } as Partial<ItemType>,\n }, { upsert: true })\n },\n remove: async (itemId) => {\n const itemExists = await collection.find({\n id: itemId,\n } as Selector<any>, { reactive: false, async: true }).count() > 0\n if (!itemExists) return\n this.remoteChanges.push({\n collectionName: name,\n type: 'remove',\n data: itemId,\n })\n await collection.removeOne({ id: itemId } as Selector<any>)\n },\n batch: async (fn) => {\n return collection.batch(async () => {\n await fn()\n })\n },\n })\n .then(async (snapshot) => {\n if (this.isDisposed) return\n\n // clean up old snapshots\n await this.snapshots.removeMany({\n collectionName: name,\n time: { $lte: syncTime },\n } as Selector<any>)\n\n // clean up processed changes\n await this.changes.removeMany({\n collectionName: name,\n id: { $in: currentChanges.map(c => c.id) },\n })\n\n // insert new snapshot\n await this.snapshots.insert({\n time: syncTime,\n collectionName: name,\n items: snapshot,\n })\n\n // delay sync operation update to next tick to allow other tasks to run first\n await new Promise((resolve) => {\n setTimeout(resolve, 0)\n })\n\n const hasChanges = await this.changes.find({\n collectionName: name,\n }, { reactive: false, async: true }).count() > 0\n\n if (hasChanges) {\n // check if there are unsynced changes to push\n // and sync again if there are any\n await this.sync(name, {\n force: true,\n onlyWithChanges: true,\n })\n return\n }\n\n // if there are no unsynced changes apply the last snapshot\n // to make sure that collection and snapshot are in sync\n\n // find all items that are not in the snapshot\n const nonExistingItemIds = await collection.find({\n id: { $nin: snapshot.map(item => item.id) },\n } as Selector<any>, {\n reactive: false,\n async: true,\n }).map(item => item.id) as IdType[]\n\n await collection.batch(async () => {\n // update all items that are in the snapshot\n await Promise.all(snapshot.map(async (item) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: item,\n }, {\n collectionName: name,\n type: 'update',\n data: { id: item.id, modifier: { $set: item } },\n })\n\n // replace the item\n await collection.replaceOne({ id: item.id } as Selector<any>, item, { upsert: true })\n }))\n\n // remove all items that are not in the snapshot\n await Promise.all(nonExistingItemIds.map(async (id) => {\n await collection.removeOne({ id } as Selector<any>)\n }))\n })\n })\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/utils/debounce.ts","../src/utils/PromiseQueue.ts","../src/computeChanges.ts","../src/getSnapshot.ts","../src/applyChanges.ts","../src/sync.ts","../src/SyncManager.ts"],"sourcesContent":["type DebounceOptions = {\n leading?: boolean,\n trailing?: boolean,\n}\n\n/**\n * Debounces a function.\n * @param fn Function to debounce\n * @param wait Time to wait before calling the function.\n * @param [options] Debounce options\n * @param [options.leading] Whether to call the function on the leading edge of the wait interval.\n * @param [options.trailing] Whether to call the function on the trailing edge of the wait interval.\n * @returns The debounced function.\n */\nexport default function debounce<Arguments extends any[], T, ThisType>(\n fn: (this: ThisType, ...args: Arguments) => T,\n wait: number,\n options: DebounceOptions = {},\n): (...args: Arguments) => T {\n let timeout: ReturnType<typeof setTimeout> | null\n let result: T | null\n\n const { leading = false, trailing = true } = options\n\n /**\n * The debounced function that will be returned.\n * @param this The context to bind the function to.\n * @param args The arguments to pass to the function.\n * @returns The result of the debounced function.\n */\n function debounced(this: ThisType, ...args: Arguments) {\n const shouldCallImmediately = leading && !timeout\n const shouldCallTrailing = trailing && !timeout\n\n if (timeout) {\n clearTimeout(timeout)\n }\n\n timeout = setTimeout(() => {\n timeout = null\n if (trailing && !shouldCallImmediately) {\n result = fn.apply(this, args)\n }\n }, wait)\n\n if (shouldCallImmediately) {\n result = fn.apply(this, args)\n } else if (!shouldCallTrailing) {\n result = null\n }\n\n return result as T\n }\n return debounced\n}\n","/**\n * Class for queuing promises to be executed one after the other.\n * This is useful for tasks that should not be executed in parallel.\n * @example\n * const queue = new PromiseQueue();\n * queue.add(() => fetch('https://example.com/api/endpoint1'));\n * queue.add(() => fetch('https://example.com/api/endpoint2'));\n * // The second fetch will only be executed after the first one is done.\n */\nexport default class PromiseQueue {\n private queue: (() => Promise<any>)[] = []\n private pendingPromise: boolean = false\n\n /**\n * Method to add a new promise to the queue and returns a promise that resolves when this task is done\n * @param task Function that returns a promise that will be added to the queue\n * @returns Promise that resolves when the task is done\n */\n add(task: () => Promise<any>): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n // Wrap the task with the resolve and reject to control its completion from the outside\n this.queue.push(() => task()\n .then(resolve)\n .catch((error: Error) => {\n reject(error)\n throw error\n }))\n this.dequeue()\n })\n }\n\n /**\n * Method to check if there is a pending promise in the queue\n * @returns True if there is a pending promise, false otherwise\n */\n public hasPendingPromise(): boolean {\n return this.pendingPromise\n }\n\n /**\n * Method to process the queue\n */\n private dequeue() {\n if (this.pendingPromise || this.queue.length === 0) {\n return\n }\n const task = this.queue.shift()\n if (!task) return\n this.pendingPromise = true\n task()\n .then(() => {\n this.pendingPromise = false\n this.dequeue()\n })\n .catch(() => {\n this.pendingPromise = false\n this.dequeue()\n })\n }\n}\n","import type { BaseItem } from '@signaldb/core'\nimport { isEqual } from '@signaldb/core'\n\n/**\n * Computes the modified fields between two items recursively.\n * @param oldItem The old item\n * @param newItem The new item\n * @returns The modified fields\n */\nexport function computeModifiedFields<T extends Record<string, any>>(\n oldItem: T,\n newItem: T,\n): string[] {\n const modifiedFields: string[] = []\n\n const oldKeys = Object.keys(oldItem)\n const newKeys = Object.keys(newItem)\n const allKeys = new Set([...oldKeys, ...newKeys])\n\n for (const key of allKeys) {\n if (newItem[key] !== oldItem[key]) {\n if (typeof newItem[key] === 'object' && typeof oldItem[key] === 'object' && newItem[key] != null && oldItem[key] != null) {\n const nestedModifiedFields = computeModifiedFields(oldItem[key], newItem[key])\n for (const nestedField of nestedModifiedFields) {\n modifiedFields.push(`${key}.${nestedField}`)\n }\n } else {\n modifiedFields.push(key)\n }\n }\n }\n\n return modifiedFields\n}\n\n/**\n * Compute changes between two arrays of items.\n * @param oldItems Array of the old items\n * @param newItems Array of the new items\n * @returns The changeset\n */\nexport default function computeChanges<ItemType extends BaseItem<IdType>, IdType>(\n oldItems: ItemType[],\n newItems: ItemType[],\n) {\n const added: ItemType[] = []\n const modified: ItemType[] = []\n const modifiedFields: Map<IdType, string[]> = new Map()\n const removed: ItemType[] = []\n\n const oldItemsMap = new Map(oldItems.map(item => [item.id, item]))\n const newItemsMap = new Map(newItems.map(item => [item.id, item]))\n\n for (const [id, oldItem] of oldItemsMap) {\n const newItem = newItemsMap.get(id)\n if (!newItem) {\n removed.push(oldItem)\n } else if (!isEqual(newItem, oldItem)) {\n modifiedFields.set(newItem.id, computeModifiedFields(oldItem, newItem))\n modified.push(newItem)\n }\n }\n\n for (const [id, newItem] of newItemsMap) {\n if (!oldItemsMap.has(id)) {\n added.push(newItem)\n }\n }\n\n return {\n added,\n modified,\n modifiedFields,\n removed,\n }\n}\n","import type { BaseItem } from '@signaldb/core'\nimport type { LoadResponse } from './types'\n\n/**\n * Gets the snapshot of items from the last snapshot and the changes.\n * @param lastSnapshot The last snapshot of items\n * @param data The changes to apply to the last snapshot\n * @returns The new snapshot of items\n */\nexport default function getSnapshot<ItemType extends BaseItem<IdType>, IdType>(\n lastSnapshot: ItemType[] | undefined,\n data: LoadResponse<ItemType>,\n) {\n if (data.items != null) return data.items\n\n // copy the array to not mutate the last snapshot\n const items = [...lastSnapshot || []]\n data.changes.added.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index === -1) {\n items.push(item)\n } else {\n items[index] = item\n }\n })\n data.changes.modified.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index === -1) {\n items.push(item)\n } else {\n items[index] = item\n }\n })\n data.changes.removed.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index !== -1) items.splice(index, 1)\n })\n return items\n}\n","import type { BaseItem } from '@signaldb/core'\nimport { modify } from '@signaldb/core'\nimport type { Change } from './types'\n\n/**\n * applies changes to a collection of items\n * @param items The items to apply the changes to\n * @param changes The changes to apply to the items\n * @param [fallbackItems] Items that are used as a base for update changes that\n * target an item which isn't part of `items` anymore (e.g. because it was removed\n * remotely). Without them, the resulting item would only contain the fields of the\n * modifier and all other fields would be lost.\n * @returns The new items after applying the changes\n */\nexport default function applyChanges<ItemType extends BaseItem<IdType>, IdType>(\n items: ItemType[],\n changes: Change<ItemType, IdType>[],\n fallbackItems?: ItemType[],\n): ItemType[] {\n // Create initial map of items by ID\n const itemMap = new Map(items.map(item => [item.id, item]))\n const fallbackItemMap = fallbackItems == null\n ? undefined\n : new Map(fallbackItems.map(item => [item.id, item]))\n\n changes.forEach((change) => {\n if (change.type === 'remove') {\n itemMap.delete(change.data)\n } else if (change.type === 'insert') {\n const existingItem = itemMap.get(change.data.id)\n itemMap.set(\n change.data.id,\n existingItem ? { ...existingItem, ...change.data } : change.data,\n )\n } else { // change.type === 'update'\n // fall back to the last known state of the item to preserve all fields\n // that aren't part of the modifier\n const existingItem = itemMap.get(change.data.id)\n ?? fallbackItemMap?.get(change.data.id)\n itemMap.set(\n change.data.id,\n existingItem\n ? modify(existingItem, change.data.modifier)\n : modify({ id: change.data.id } as ItemType, change.data.modifier),\n )\n }\n })\n\n // Convert map back to array\n return [...itemMap.values()]\n}\n","import type { BaseItem, Modifier, Changeset } from '@signaldb/core'\nimport computeChanges from './computeChanges'\nimport type { Change, LoadResponse } from './types'\nimport getSnapshot from './getSnapshot'\nimport applyChanges from './applyChanges'\n\n/**\n * Checks if there are any changes in the given changeset.\n * @param changes The changeset to check.\n * @returns True if there are changes, false otherwise.\n */\nfunction hasChanges<T>(\n changes: Changeset<T>,\n) {\n return changes.added.length > 0\n || changes.modified.length > 0\n || changes.removed.length > 0\n}\n/**\n * Checks if there is a difference between the old items and the new items.\n * @param oldItems The old items.\n * @param newItems The new items.\n * @returns True if there is a difference, false otherwise.\n */\nfunction hasDifference<ItemType extends BaseItem<IdType>, IdType>(\n oldItems: ItemType[],\n newItems: ItemType[],\n) {\n return hasChanges(computeChanges(oldItems, newItems))\n}\n\ninterface Options<ItemType extends BaseItem<IdType>, IdType> {\n changes: Change<ItemType, IdType>[],\n lastSnapshot?: ItemType[],\n data: LoadResponse<ItemType>,\n pull: () => Promise<LoadResponse<ItemType>>,\n push: (changes: Changeset<ItemType> & {\n modifiedFields: Map<IdType, string[]>,\n }) => Promise<void>,\n insert: (item: ItemType) => Promise<void>,\n update: (id: IdType, modifier: Modifier<ItemType>) => Promise<void>,\n remove: (id: IdType) => Promise<void>,\n batch: (fn: () => Promise<void>) => Promise<void>,\n}\n\n/**\n * Does a sync operation based on the provided options. If changes are supplied, these will be rebased on the new data.\n * Afterwards the push method will be called with the remaining changes. A new snapshot will be created and returned.\n * @param options Sync options\n * @param options.changes Changes to call the push method with\n * @param [options.lastSnapshot] The last snapshot\n * @param options.data The new data\n * @param options.pull Method to pull new data\n * @param options.push Method to push changes\n * @param options.insert Method to insert an item\n * @param options.update Method to update an item\n * @param options.remove Method to remove an item\n * @param options.batch Method to batch multiple operations\n * @returns The new snapshot\n */\nexport default async function sync<ItemType extends BaseItem<IdType>, IdType>({\n changes,\n lastSnapshot,\n data,\n pull,\n push,\n insert,\n update,\n remove,\n batch,\n}: Options<ItemType, IdType>): Promise<ItemType[]> {\n let newData = data\n let previousSnapshot = lastSnapshot || []\n let newSnapshot = getSnapshot(lastSnapshot, newData)\n if (changes.length > 0) {\n // apply changes on last snapshot and check if there is a difference\n const lastSnapshotWithChanges = applyChanges(previousSnapshot, changes)\n if (hasDifference(previousSnapshot, lastSnapshotWithChanges)) {\n // if yes, apply the changes on the newSnapshot and check if there is a difference\n // pass the previous snapshot as fallback to preserve all fields of items that\n // were removed remotely, but were updated locally\n const newSnapshotWithChanges = applyChanges(newSnapshot, changes, previousSnapshot)\n const changesToPush = computeChanges<ItemType, IdType>(newSnapshot, newSnapshotWithChanges)\n if (hasChanges(changesToPush)) {\n // if yes, push the changes to the server\n await push(changesToPush)\n\n // pull new data afterwards to ensure that all server changes are applied\n newData = await pull()\n newSnapshot = getSnapshot(newSnapshot, newData)\n }\n previousSnapshot = lastSnapshotWithChanges\n }\n }\n\n // apply the new changes on the collection\n const newChanges = newData.changes == null\n ? computeChanges(previousSnapshot, newData.items)\n : newData.changes\n await batch(async () => {\n await Promise.all(newChanges.added.map(item => insert(item)))\n await Promise.all(newChanges.modified.map(item => update(item.id, { $set: item })))\n await Promise.all(newChanges.removed.map(item => remove(item.id)))\n })\n\n return newSnapshot\n}\n","import type {\n BaseItem,\n DataAdapter,\n ReactivityAdapter,\n Changeset,\n Modifier,\n Selector,\n} from '@signaldb/core'\nimport { DefaultDataAdapter, Collection, randomId } from '@signaldb/core'\nimport debounce from './utils/debounce'\nimport PromiseQueue from './utils/PromiseQueue'\nimport sync from './sync'\nimport type { Change, LoadResponse, Snapshot, SyncOperation } from './types'\n\ntype SyncOptions<T extends Record<string, any>> = {\n name: string,\n} & T\n\ntype CleanupFunction = (() => void | Promise<void>) | void\n\ninterface SyncListeners<ItemType extends BaseItem<IdType>, IdType = any> {\n added: (item: ItemType) => void,\n changed: (item: ItemType, modifier: Modifier<ItemType>) => void,\n removed: (item: ItemType) => void,\n}\n\ninterface Options<\n CollectionOptions extends Record<string, any>,\n ItemType extends BaseItem<IdType> = BaseItem,\n IdType = any,\n> {\n pull: (\n collectionOptions: SyncOptions<CollectionOptions>,\n pullParameters: {\n lastFinishedSyncStart?: number,\n lastFinishedSyncEnd?: number,\n },\n ) => Promise<LoadResponse<ItemType>>,\n push: (\n collectionOptions: SyncOptions<CollectionOptions>,\n pushParameters: {\n rawChanges: Omit<Change, 'id' | 'collectionName'>[],\n changes: Changeset<ItemType> & {\n modifiedFields: Map<IdType, string[]>,\n },\n },\n ) => Promise<void>,\n registerRemoteChange?: (\n collectionOptions: SyncOptions<CollectionOptions>,\n onChange: (data?: LoadResponse<ItemType>) => Promise<void>,\n ) => CleanupFunction | Promise<CleanupFunction>,\n\n id?: string,\n dataAdapter?: DataAdapter,\n reactivity?: ReactivityAdapter,\n onError?: (collectionOptions: SyncOptions<CollectionOptions>, error: Error) => void,\n\n autostart?: boolean,\n debounceTime?: number,\n}\n\n/**\n * Class to manage syncing of collections.\n * @template CollectionOptions\n * @template ItemType\n * @template IdType\n * @example\n * const syncManager = new SyncManager({\n * pull: async (collectionOptions) => {\n * const response = await fetch(`/api/collections/${collectionOptions.name}`)\n * return await response.json()\n * },\n * push: async (collectionOptions, { changes }) => {\n * await fetch(`/api/collections/${collectionOptions.name}`, {\n * method: 'POST',\n * body: JSON.stringify(changes),\n * })\n * },\n * })\n *\n * const collection = new Collection()\n * syncManager.addCollection(collection, {\n * name: 'todos',\n * })\n *\n * syncManager.sync('todos')\n */\nexport default class SyncManager<\n CollectionOptions extends Record<string, any>,\n ItemType extends BaseItem<IdType> = BaseItem,\n IdType = any,\n> {\n protected options: Options<CollectionOptions, ItemType, IdType>\n protected collections: Map<string, {\n collection: Collection<ItemType, IdType, any>,\n options: SyncOptions<CollectionOptions>,\n readyPromise: Promise<void>,\n syncPaused: boolean,\n cleanupFunction?: CleanupFunction,\n syncListeners?: SyncListeners<ItemType, IdType>,\n }> = new Map()\n\n protected changes: Collection<Change<ItemType>, string>\n protected snapshots: Collection<Snapshot<ItemType>, string>\n protected syncOperations: Collection<SyncOperation, string>\n protected scheduledPushes: Set<string> = new Set()\n protected remoteChanges: Omit<Change, 'id' | 'time'>[] = []\n protected syncQueues: Map<string, PromiseQueue> = new Map()\n protected collectionsReady: Promise<void>\n protected isDisposed = false\n protected instanceId = randomId()\n protected id: string\n protected debouncedFlush: () => void\n\n /**\n * @param options Collection options\n * @param options.pull Function to pull data from remote source.\n * @param options.push Function to push data to remote source.\n * @param [options.registerRemoteChange] Function to register a callback for remote changes.\n * @param [options.id] Unique identifier for this sync manager. Only nessesary if you have multiple sync managers.\n * @param [options.storageAdapter] Storage adapter to use for storing changes, snapshots and sync operations.\n * @param [options.reactivity] Reactivity adapter to use for reactivity.\n * @param [options.onError] Function to handle errors that occur async during syncing.\n * @param [options.autostart] Whether to automatically start syncing new collections.\n * @param [options.debounceTime] The time in milliseconds to debounce push operations.\n */\n constructor(options: Options<CollectionOptions, ItemType, IdType>) {\n this.options = {\n autostart: true,\n ...options,\n }\n this.id = this.options.id || 'default-sync-manager'\n const { reactivity } = this.options\n const dataAdapter = this.options.dataAdapter ?? new DefaultDataAdapter()\n this.changes = new Collection(`${this.options.id}-changes`, dataAdapter, {\n indices: ['collectionName'],\n reactivity,\n })\n this.snapshots = new Collection(`${this.options.id}-snapshots`, dataAdapter, {\n indices: ['collectionName'],\n reactivity,\n })\n this.syncOperations = new Collection(`${this.options.id}-sync-operations`, dataAdapter, {\n indices: ['collectionName', 'status'],\n reactivity,\n })\n\n const readiness = [\n Promise.resolve(this.syncOperations.isReady()),\n Promise.resolve(this.changes.isReady()),\n Promise.resolve(this.snapshots.isReady()),\n ]\n this.collectionsReady = Promise.all(readiness).then(() => { /* noop */ })\n\n this.changes.setMaxListeners(1000)\n this.snapshots.setMaxListeners(1000)\n this.syncOperations.setMaxListeners(1000)\n\n this.debouncedFlush = debounce(this.flushScheduledPushes, this.options.debounceTime ?? 100)\n }\n\n protected getSyncQueue(name: string) {\n if (this.syncQueues.get(name) == null) {\n this.syncQueues.set(name, new PromiseQueue())\n }\n return this.syncQueues.get(name) as PromiseQueue\n }\n\n /**\n * Removes the event listeners this sync manager attached to a registered collection.\n * The collection itself is left untouched, including listeners registered by the user.\n * @param name Name of the collection\n */\n protected detachSyncListeners(name: string) {\n const collectionParameters = this.collections.get(name)\n if (collectionParameters == null) return\n const { collection, syncListeners } = collectionParameters\n if (syncListeners == null) return\n collection.off('added', syncListeners.added)\n collection.off('changed', syncListeners.changed)\n collection.off('removed', syncListeners.removed)\n }\n\n /**\n * Removes a collection from the sync manager. Pauses the sync process for the collection\n * and detaches all event listeners the sync manager attached to it. The collection itself\n * won't be disposed and stays usable afterwards.\n * @param name Name of the collection\n */\n public async removeCollection(name: string) {\n if (!this.collections.has(name)) return\n await this.pauseSync(name)\n this.detachSyncListeners(name)\n this.collections.delete(name)\n this.syncQueues.delete(name)\n this.scheduledPushes.delete(name)\n }\n\n /**\n * Clears all internal data structures\n */\n public async dispose() {\n this.isDisposed = true\n // detach the listeners from the user provided collections before disposing the\n // internal collections. Otherwise the listeners would stay attached and reference\n // the disposed internal collections of this sync manager forever.\n for (const name of this.collections.keys()) {\n this.detachSyncListeners(name)\n }\n this.collections.clear()\n this.syncQueues.clear()\n this.scheduledPushes.clear()\n this.remoteChanges.splice(0)\n await Promise.all([\n this.changes.dispose(),\n this.snapshots.dispose(),\n this.syncOperations.dispose(),\n ])\n }\n\n /**\n * Gets a collection with it's options by name\n * @deprecated Use getCollectionProperties instead.\n * @param name Name of the collection\n * @throws {Error} Will throw an error if the name wasn't found\n * @returns Tuple of collection and options\n */\n public getCollection(name: string) {\n const { collection, options } = this.getCollectionProperties(name)\n return [collection, options]\n }\n\n /**\n * Gets collection options by name\n * @param name Name of the collection\n * @throws {Error} Will throw an error if the name wasn't found\n * @returns An object of all properties of the collection\n */\n public getCollectionProperties(name: string) {\n const collectionParameters = this.collections.get(name)\n if (collectionParameters == null) throw new Error(`Collection with id '${name}' not found`)\n return collectionParameters\n }\n\n /**\n * Adds a collection to the sync manager.\n * @param collection Collection to add\n * @param options Options for the collection. The object needs at least a `name` property.\n * @param options.name Unique name of the collection\n */\n public addCollection<\n CollectionItem extends ItemType = ItemType,\n >(\n collection: Collection<CollectionItem, IdType, any>,\n options: SyncOptions<CollectionOptions>,\n ) {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n\n // detach listeners of a previous registration under the same name,\n // otherwise every re-registration would add another set of listeners\n this.detachSyncListeners(options.name)\n\n const hasRemoteChange = (change: Omit<Change, 'id' | 'time'>) => {\n for (const remoteChange of this.remoteChanges) {\n if (remoteChange == null) continue\n if (remoteChange.collectionName !== change.collectionName) continue\n if (remoteChange.type !== change.type) continue\n\n if (change.type === 'remove' && remoteChange.data !== change.data) continue\n if (remoteChange.data.id !== change.data.id) continue\n return true\n }\n return false\n }\n const removeRemoteChanges = (collectionName: string, id: IdType) => {\n const newRemoteChanges: (Omit<Change, 'id' | 'time'> | null)[] = [...this.remoteChanges]\n for (let i = 0; i < newRemoteChanges.length; i += 1) {\n const item = newRemoteChanges[i]\n if (item == null) continue\n if (item.collectionName !== collectionName) continue\n if (item.type === 'remove' && item.data !== id) continue\n if (item.data.id !== id) continue\n newRemoteChanges[i] = null\n }\n this.remoteChanges = newRemoteChanges.filter(item => item != null)\n }\n\n const onAdded: SyncListeners<CollectionItem, IdType>['added'] = (item) => {\n if (this.isDisposed) return\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'insert', data: item })) {\n removeRemoteChanges(options.name, item.id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'insert',\n data: item,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n }\n const onChanged: SyncListeners<CollectionItem, IdType>['changed'] = ({ id }, modifier) => {\n if (this.isDisposed) return\n const data = { id, modifier }\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'update', data })) {\n removeRemoteChanges(options.name, id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'update',\n data,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n }\n const onRemoved: SyncListeners<CollectionItem, IdType>['removed'] = ({ id }) => {\n if (this.isDisposed) return\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'remove', data: id })) {\n removeRemoteChanges(options.name, id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'remove',\n data: id,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n }\n\n collection.on('added', onAdded)\n collection.on('changed', onChanged)\n collection.on('removed', onRemoved)\n\n const syncListeners = {\n added: onAdded,\n changed: onChanged,\n removed: onRemoved,\n } as unknown as SyncListeners<ItemType, IdType>\n\n this.collections.set(options.name, {\n collection: collection as unknown as Collection<ItemType, IdType, any>,\n options,\n readyPromise: collection.ready(),\n syncPaused: true, // always start paused as the autostart will start it\n syncListeners,\n })\n\n if (this.options.autostart) {\n this.startSync(options.name)\n .catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n }\n }\n\n protected flushScheduledPushes() {\n this.scheduledPushes.forEach((name) => {\n this.pushChanges(name).catch(() => { /* error handler is called in sync */ })\n })\n this.scheduledPushes.clear()\n }\n\n protected schedulePush(name: string) {\n this.scheduledPushes.add(name)\n this.debouncedFlush()\n }\n\n /**\n * Setup all collections to be synced with remote changes\n * and enable automatic pushing changes to the remote source.\n */\n public async startAll() {\n await Promise.all([...this.collections.keys()].map(id =>\n this.startSync(id)))\n }\n\n /**\n * Setup a collection to be synced with remote changes\n * and enable automatic pushing changes to the remote source.\n * @param name Name of the collection\n */\n public async startSync(name: string) {\n const collectionParameters = this.getCollectionProperties(name)\n if (!collectionParameters.syncPaused) return // already started\n\n this.schedulePush(name) // push changes that were made while paused\n\n const cleanupFunction = this.options.registerRemoteChange\n ? await this.options.registerRemoteChange(\n collectionParameters.options,\n async (data) => {\n if (this.isDisposed) return\n // eslint-disable-next-line unicorn/prefer-ternary -- this is easier to read than a ternary operator\n if (data == null) {\n // if no data is provided, we will sync the collection with the remote source\n await this.sync(name)\n } else {\n // if data is provided, we will sync the collection with the provided data\n await this.getSyncQueue(name).add(async () => {\n const syncTime = Date.now()\n const syncId = await this.syncOperations.insert({\n start: syncTime,\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n })\n await this.syncWithData(name, data)\n .then(async () => {\n if (this.isDisposed) return\n // clean up old sync operations\n await this.syncOperations.removeMany({\n id: { $ne: syncId },\n collectionName: name,\n $or: [\n { end: { $lte: syncTime } },\n { status: 'active' },\n ],\n })\n\n // update sync operation status to done after everthing was finished\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'done', end: Date.now() },\n })\n })\n .catch(async (error: Error) => {\n if (this.options.onError) {\n this.options.onError(this.getCollectionProperties(name).options, error)\n }\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'error', end: Date.now(), error: error.stack || error.message },\n })\n throw error\n })\n })\n }\n },\n )\n : undefined\n this.collections.set(name, {\n ...collectionParameters,\n syncPaused: false,\n cleanupFunction,\n })\n }\n\n /**\n * Pauses the sync process for all collections.\n * This means that the collections will not be synced with remote changes\n * and changes will not automatically be pushed to the remote source.\n */\n public async pauseAll() {\n await Promise.all([...this.collections.keys()].map(id =>\n this.pauseSync(id)))\n }\n\n /**\n * Pauses the sync process for a collection.\n * This means that the collection will not be synced with remote changes\n * and changes will not automatically be pushed to the remote source.\n * @param name Name of the collection\n */\n public async pauseSync(name: string) {\n const collectionParameters = this.getCollectionProperties(name)\n if (collectionParameters.syncPaused) return // already paused\n if (collectionParameters.cleanupFunction) await collectionParameters.cleanupFunction()\n this.collections.set(name, {\n ...collectionParameters,\n cleanupFunction: undefined,\n syncPaused: true,\n })\n }\n\n /**\n * Starts the sync process for all collections\n */\n public async syncAll() {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n const errors: { id: string, error: Error }[] = []\n await Promise.all([...this.collections.keys()].map(id =>\n this.sync(id).catch((error: Error) => {\n errors.push({ id, error })\n })))\n if (errors.length > 0) throw new Error(`Error while syncing collections:\\n${errors.map(error => `${error.id}: ${error.error.message}`).join('\\n\\n')}`)\n }\n\n /**\n * Checks if a collection is currently beeing synced\n * @param [name] Name of the collection. If not provided, it will check if any collection is currently beeing synced.\n * @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.\n * @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.\n */\n public isSyncing(\n name: string | undefined,\n async: true,\n ): Promise<boolean>\n\n public isSyncing(\n name?: string,\n async?: false,\n ): boolean\n\n public isSyncing<Async extends boolean = false>(\n name?: string,\n async?: Async,\n ): Promise<boolean> | boolean {\n const itemOrPromise = this.syncOperations.findOne({\n ...name ? { collectionName: name } : {},\n status: 'active',\n }, { fields: { status: 1 }, async })\n if (itemOrPromise instanceof Promise) {\n return itemOrPromise\n .then(item => item != null)\n }\n return (itemOrPromise != null)\n }\n\n /**\n * Checks if the sync manager is ready to sync.\n * @returns A promise that resolves when the sync manager is ready to sync.\n */\n public async isReady() {\n await this.collectionsReady\n }\n\n /**\n * Starts the sync process for a collection\n * @param name Name of the collection\n * @param options Options for the sync process.\n * @param options.force If true, the sync process will be started even if there are no changes and onlyWithChanges is true.\n * @param options.onlyWithChanges If true, the sync process will only be started if there are changes.\n */\n public async sync(name: string, options: { force?: boolean, onlyWithChanges?: boolean } = {}) {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n await this.isReady()\n const { options: collectionOptions, readyPromise } = this.getCollectionProperties(name)\n await readyPromise\n\n const hasActiveSyncs = await this.syncOperations.find({\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n }, {\n reactive: false,\n async: true,\n }).count() > 0\n const syncTime = Date.now()\n let syncId: string | null = null\n\n // schedule for next tick to allow other tasks to run first\n await new Promise((resolve) => {\n setTimeout(resolve, 0)\n })\n const doSync = async () => {\n const lastFinishedSync = await this.syncOperations.findOne({\n collectionName: name,\n status: 'done',\n }, {\n sort: { end: -1 },\n reactive: false,\n async: true,\n })\n if (options?.onlyWithChanges) {\n const currentChanges = await this.changes.find({\n collectionName: name,\n time: { $lte: syncTime },\n }, {\n sort: { time: 1 },\n reactive: false,\n async: true,\n }).count()\n if (currentChanges === 0) return\n }\n\n if (!hasActiveSyncs) {\n syncId = await this.syncOperations.insert({\n start: syncTime,\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n })\n }\n const data = await this.options.pull(collectionOptions, {\n lastFinishedSyncStart: lastFinishedSync?.start,\n lastFinishedSyncEnd: lastFinishedSync?.end,\n })\n\n await this.syncWithData(name, data)\n }\n\n await (options?.force ? doSync() : this.getSyncQueue(name).add(doSync))\n .catch(async (error: Error) => {\n if (syncId != null) {\n if (this.options.onError) this.options.onError(collectionOptions, error)\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'error', end: Date.now(), error: error.stack || error.message },\n })\n }\n throw error\n })\n\n if (syncId != null) {\n // clean up old sync operations\n await this.syncOperations.removeMany({\n id: { $ne: syncId },\n collectionName: name,\n $or: [\n { end: { $lte: syncTime } },\n { status: 'active' },\n ],\n })\n\n // update sync operation status to done after everthing was finished\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'done', end: Date.now() },\n })\n }\n }\n\n /**\n * Starts the push process for a collection (sync process but only if there are changes)\n * @param name Name of the collection\n */\n public async pushChanges(name: string) {\n await this.sync(name, {\n onlyWithChanges: true,\n })\n }\n\n protected async syncWithData(\n name: string,\n data: LoadResponse<ItemType>,\n ) {\n if (this.isDisposed) return\n const { collection, options: collectionOptions } = this.getCollectionProperties(name)\n\n const syncTime = Date.now()\n\n const lastFinishedSync = await this.syncOperations.findOne({\n collectionName: name,\n status: 'done',\n }, {\n sort: { end: -1 },\n reactive: false,\n async: true,\n })\n const lastSnapshot = await this.snapshots.findOne({\n collectionName: name,\n } as Selector<Snapshot<any>>, {\n sort: { time: -1 },\n reactive: false,\n async: true,\n })\n const currentChanges = await this.changes.find({\n collectionName: name,\n time: { $lte: syncTime },\n }, {\n sort: { time: 1 },\n reactive: false,\n async: true,\n }).fetch()\n\n await sync<ItemType, ItemType['id']>({\n changes: currentChanges,\n lastSnapshot: lastSnapshot?.items,\n data,\n pull: () => this.options.pull(collectionOptions, {\n lastFinishedSyncStart: lastFinishedSync?.start,\n lastFinishedSyncEnd: lastFinishedSync?.end,\n }),\n push: changes => this.options.push(collectionOptions, {\n changes,\n rawChanges: currentChanges,\n }),\n insert: async (item) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: item,\n }, {\n collectionName: name,\n type: 'update',\n data: { id: item.id, modifier: { $set: item } },\n })\n\n // replace the item\n await collection.replaceOne({ id: item.id } as Selector<any>, item, { upsert: true })\n },\n update: async (itemId, modifier) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: { id: itemId, ...modifier.$set },\n }, {\n collectionName: name,\n type: 'update',\n data: { id: itemId, modifier },\n })\n\n await collection.updateOne({ id: itemId } as Selector<any>, {\n ...modifier,\n $setOnInsert: { id: itemId } as Partial<ItemType>,\n }, { upsert: true })\n },\n remove: async (itemId) => {\n const itemExists = await collection.find({\n id: itemId,\n } as Selector<any>, { reactive: false, async: true }).count() > 0\n if (!itemExists) return\n this.remoteChanges.push({\n collectionName: name,\n type: 'remove',\n data: itemId,\n })\n await collection.removeOne({ id: itemId } as Selector<any>)\n },\n batch: async (fn) => {\n return collection.batch(async () => {\n await fn()\n })\n },\n })\n .then(async (snapshot) => {\n if (this.isDisposed) return\n\n // clean up old snapshots\n await this.snapshots.removeMany({\n collectionName: name,\n time: { $lte: syncTime },\n } as Selector<any>)\n\n // clean up processed changes\n await this.changes.removeMany({\n collectionName: name,\n id: { $in: currentChanges.map(c => c.id) },\n })\n\n // insert new snapshot\n await this.snapshots.insert({\n time: syncTime,\n collectionName: name,\n items: snapshot,\n })\n\n // delay sync operation update to next tick to allow other tasks to run first\n await new Promise((resolve) => {\n setTimeout(resolve, 0)\n })\n\n const hasChanges = await this.changes.find({\n collectionName: name,\n }, { reactive: false, async: true }).count() > 0\n\n if (hasChanges) {\n // check if there are unsynced changes to push\n // and sync again if there are any\n await this.sync(name, {\n force: true,\n onlyWithChanges: true,\n })\n return\n }\n\n // if there are no unsynced changes apply the last snapshot\n // to make sure that collection and snapshot are in sync\n\n // find all items that are not in the snapshot\n const nonExistingItemIds = await collection.find({\n id: { $nin: snapshot.map(item => item.id) },\n } as Selector<any>, {\n reactive: false,\n async: true,\n }).map(item => item.id) as IdType[]\n\n await collection.batch(async () => {\n // update all items that are in the snapshot\n await Promise.all(snapshot.map(async (item) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: item,\n }, {\n collectionName: name,\n type: 'update',\n data: { id: item.id, modifier: { $set: item } },\n })\n\n // replace the item\n await collection.replaceOne({ id: item.id } as Selector<any>, item, { upsert: true })\n }))\n\n // remove all items that are not in the snapshot\n await Promise.all(nonExistingItemIds.map(async (id) => {\n await collection.removeOne({ id } as Selector<any>)\n }))\n })\n })\n }\n}\n"],"mappings":"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"}
package/dist/index.umd.js CHANGED
@@ -1,4 +1,4 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@signaldb/core")):typeof define==`function`&&define.amd?define([`exports`,`@signaldb/core`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.SignalDB={},e._signaldb_core))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function n(e,t,n={}){let r,i,{leading:a=!1,trailing:o=!0}=n;function s(...n){let s=a&&!r,c=o&&!r;return r&&clearTimeout(r),r=setTimeout(()=>{r=null,o&&!s&&(i=e.apply(this,n))},t),s?i=e.apply(this,n):c||(i=null),i}return s}var r=class{queue=[];pendingPromise=!1;add(e){return new Promise((t,n)=>{this.queue.push(()=>e().then(t).catch(e=>{throw n(e),e})),this.dequeue()})}hasPendingPromise(){return this.pendingPromise}dequeue(){if(this.pendingPromise||this.queue.length===0)return;let e=this.queue.shift();e&&(this.pendingPromise=!0,e().then(()=>{this.pendingPromise=!1,this.dequeue()}).catch(()=>{this.pendingPromise=!1,this.dequeue()}))}};function i(e,t){let n=[],r=Object.keys(e),a=Object.keys(t),o=new Set([...r,...a]);for(let r of o)if(t[r]!==e[r])if(typeof t[r]==`object`&&typeof e[r]==`object`&&t[r]!=null&&e[r]!=null){let a=i(e[r],t[r]);for(let e of a)n.push(`${r}.${e}`)}else n.push(r);return n}function a(e,n){let r=[],a=[],o=new Map,s=[],c=new Map(e.map(e=>[e.id,e])),l=new Map(n.map(e=>[e.id,e]));for(let[e,n]of c){let r=l.get(e);r?(0,t.isEqual)(r,n)||(o.set(r.id,i(n,r)),a.push(r)):s.push(n)}for(let[e,t]of l)c.has(e)||r.push(t);return{added:r,modified:a,modifiedFields:o,removed:s}}function o(e,t){if(t.items!=null)return t.items;let n=e||[];return t.changes.added.forEach(e=>{let t=n.findIndex(t=>t.id===e.id);t===-1?n.push(e):n[t]=e}),t.changes.modified.forEach(e=>{let t=n.findIndex(t=>t.id===e.id);t===-1?n.push(e):n[t]=e}),t.changes.removed.forEach(e=>{let t=n.findIndex(t=>t.id===e.id);t!==-1&&n.splice(t,1)}),n}function s(e,n){let r=new Map(e.map(e=>[e.id,e]));return n.forEach(e=>{if(e.type===`remove`)r.delete(e.data);else if(e.type===`insert`){let t=r.get(e.data.id);r.set(e.data.id,t?{...t,...e.data}:e.data)}else{let n=r.get(e.data.id);r.set(e.data.id,n?(0,t.modify)(n,e.data.modifier):(0,t.modify)({id:e.data.id},e.data.modifier))}}),[...r.values()]}function c(e){return e.added.length>0||e.modified.length>0||e.removed.length>0}function l(e,t){return c(a(e,t))}async function u({changes:e,lastSnapshot:t,data:n,pull:r,push:i,insert:u,update:d,remove:f,batch:p}){let m=n,h=t||[],g=o(t,m);if(e.length>0){let t=s(h,e);if(l(h,t)){let n=s(g,e),l=a(g,n);c(l)&&(await i(l),m=await r(),g=o(g,m)),h=t}}let _=m.changes==null?a(h,m.items):m.changes;return await p(async()=>{await Promise.all(_.added.map(e=>u(e))),await Promise.all(_.modified.map(e=>d(e.id,{$set:e}))),await Promise.all(_.removed.map(e=>f(e.id)))}),g}e.SyncManager=class{options;collections=new Map;changes;snapshots;syncOperations;scheduledPushes=new Set;remoteChanges=[];syncQueues=new Map;collectionsReady;isDisposed=!1;instanceId=(0,t.randomId)();id;debouncedFlush;constructor(e){this.options={autostart:!0,...e},this.id=this.options.id||`default-sync-manager`;let{reactivity:r}=this.options,i=this.options.dataAdapter??new t.DefaultDataAdapter;this.changes=new t.Collection(`${this.options.id}-changes`,i,{indices:[`collectionName`],reactivity:r}),this.snapshots=new t.Collection(`${this.options.id}-snapshots`,i,{indices:[`collectionName`],reactivity:r}),this.syncOperations=new t.Collection(`${this.options.id}-sync-operations`,i,{indices:[`collectionName`,`status`],reactivity:r});let a=[Promise.resolve(this.syncOperations.isReady()),Promise.resolve(this.changes.isReady()),Promise.resolve(this.snapshots.isReady())];this.collectionsReady=Promise.all(a).then(()=>{}),this.changes.setMaxListeners(1e3),this.snapshots.setMaxListeners(1e3),this.syncOperations.setMaxListeners(1e3),this.debouncedFlush=n(this.flushScheduledPushes,this.options.debounceTime??100)}getSyncQueue(e){return this.syncQueues.get(e)??this.syncQueues.set(e,new r),this.syncQueues.get(e)}async dispose(){this.isDisposed=!0,this.collections.clear(),this.syncQueues.clear(),this.remoteChanges.splice(0),await Promise.all([this.changes.dispose(),this.snapshots.dispose(),this.syncOperations.dispose()])}getCollection(e){let{collection:t,options:n}=this.getCollectionProperties(e);return[t,n]}getCollectionProperties(e){let t=this.collections.get(e);if(t==null)throw Error(`Collection with id '${e}' not found`);return t}addCollection(e,t){if(this.isDisposed)throw Error(`SyncManager is disposed`);this.collections.set(t.name,{collection:e,options:t,readyPromise:e.ready(),syncPaused:!0});let n=e=>{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;return!1},r=(e,t)=>{let n=[...this.remoteChanges];for(let r=0;r<n.length;r+=1){let i=n[r];i!=null&&i.collectionName===e&&(i.type===`remove`&&i.data!==t||i.data.id===t&&(n[r]=null))}this.remoteChanges=n.filter(e=>e!=null)};e.on(`added`,e=>{if(!this.isDisposed){if(n({collectionName:t.name,type:`insert`,data:e})){r(t.name,e.id);return}this.changes.insert({collectionName:t.name,time:Date.now(),type:`insert`,data:e}).then(()=>{this.getCollectionProperties(t.name).syncPaused||this.schedulePush(t.name)}).catch(e=>{this.options.onError&&this.options.onError(this.getCollectionProperties(t.name).options,e)})}}),e.on(`changed`,({id:e},i)=>{if(this.isDisposed)return;let a={id:e,modifier:i};if(n({collectionName:t.name,type:`update`,data:a})){r(t.name,e);return}this.changes.insert({collectionName:t.name,time:Date.now(),type:`update`,data:a}).then(()=>{this.getCollectionProperties(t.name).syncPaused||this.schedulePush(t.name)}).catch(e=>{this.options.onError&&this.options.onError(this.getCollectionProperties(t.name).options,e)})}),e.on(`removed`,({id:e})=>{if(!this.isDisposed){if(n({collectionName:t.name,type:`remove`,data:e})){r(t.name,e);return}this.changes.insert({collectionName:t.name,time:Date.now(),type:`remove`,data:e}).then(()=>{this.getCollectionProperties(t.name).syncPaused||this.schedulePush(t.name)}).catch(e=>{this.options.onError&&this.options.onError(this.getCollectionProperties(t.name).options,e)})}}),this.options.autostart&&this.startSync(t.name).catch(e=>{this.options.onError&&this.options.onError(this.getCollectionProperties(t.name).options,e)})}flushScheduledPushes(){this.scheduledPushes.forEach(e=>{this.pushChanges(e).catch(()=>{})}),this.scheduledPushes.clear()}schedulePush(e){this.scheduledPushes.add(e),this.debouncedFlush()}async startAll(){await Promise.all([...this.collections.keys()].map(e=>this.startSync(e)))}async startSync(e){let t=this.getCollectionProperties(e);if(!t.syncPaused)return;this.schedulePush(e);let n=this.options.registerRemoteChange?await this.options.registerRemoteChange(t.options,async t=>{this.isDisposed||(t==null?await this.sync(e):await this.getSyncQueue(e).add(async()=>{let n=Date.now(),r=await this.syncOperations.insert({start:n,collectionName:e,instanceId:this.instanceId,status:`active`});await this.syncWithData(e,t).then(async()=>{this.isDisposed||(await this.syncOperations.removeMany({id:{$ne:r},collectionName:e,$or:[{end:{$lte:n}},{status:`active`}]}),await this.syncOperations.updateOne({id:r},{$set:{status:`done`,end:Date.now()}}))}).catch(async t=>{throw this.options.onError&&this.options.onError(this.getCollectionProperties(e).options,t),await this.syncOperations.updateOne({id:r},{$set:{status:`error`,end:Date.now(),error:t.stack||t.message}}),t})}))}):void 0;this.collections.set(e,{...t,syncPaused:!1,cleanupFunction:n})}async pauseAll(){await Promise.all([...this.collections.keys()].map(e=>this.pauseSync(e)))}async pauseSync(e){let t=this.getCollectionProperties(e);t.syncPaused||(t.cleanupFunction&&await t.cleanupFunction(),this.collections.set(e,{...t,cleanupFunction:void 0,syncPaused:!0}))}async syncAll(){if(this.isDisposed)throw Error(`SyncManager is disposed`);let e=[];if(await Promise.all([...this.collections.keys()].map(t=>this.sync(t).catch(n=>{e.push({id:t,error:n})}))),e.length>0)throw Error(`Error while syncing collections:\n${e.map(e=>`${e.id}: ${e.error.message}`).join(`
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@signaldb/core")):typeof define==`function`&&define.amd?define([`exports`,`@signaldb/core`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.SignalDB={},e._signaldb_core))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function n(e,t,n={}){let r,i,{leading:a=!1,trailing:o=!0}=n;function s(...n){let s=a&&!r,c=o&&!r;return r&&clearTimeout(r),r=setTimeout(()=>{r=null,o&&!s&&(i=e.apply(this,n))},t),s?i=e.apply(this,n):c||(i=null),i}return s}var r=class{queue=[];pendingPromise=!1;add(e){return new Promise((t,n)=>{this.queue.push(()=>e().then(t).catch(e=>{throw n(e),e})),this.dequeue()})}hasPendingPromise(){return this.pendingPromise}dequeue(){if(this.pendingPromise||this.queue.length===0)return;let e=this.queue.shift();e&&(this.pendingPromise=!0,e().then(()=>{this.pendingPromise=!1,this.dequeue()}).catch(()=>{this.pendingPromise=!1,this.dequeue()}))}};function i(e,t){let n=[],r=Object.keys(e),a=Object.keys(t),o=new Set([...r,...a]);for(let r of o)if(t[r]!==e[r]){if(typeof t[r]==`object`&&typeof e[r]==`object`&&t[r]!=null&&e[r]!=null){let a=i(e[r],t[r]);for(let e of a)n.push(`${r}.${e}`)}else n.push(r)}return n}function a(e,n){let r=[],a=[],o=new Map,s=[],c=new Map(e.map(e=>[e.id,e])),l=new Map(n.map(e=>[e.id,e]));for(let[e,n]of c){let r=l.get(e);r?(0,t.isEqual)(r,n)||(o.set(r.id,i(n,r)),a.push(r)):s.push(n)}for(let[e,t]of l)c.has(e)||r.push(t);return{added:r,modified:a,modifiedFields:o,removed:s}}function o(e,t){if(t.items!=null)return t.items;let n=[...e||[]];return t.changes.added.forEach(e=>{let t=n.findIndex(t=>t.id===e.id);t===-1?n.push(e):n[t]=e}),t.changes.modified.forEach(e=>{let t=n.findIndex(t=>t.id===e.id);t===-1?n.push(e):n[t]=e}),t.changes.removed.forEach(e=>{let t=n.findIndex(t=>t.id===e.id);t!==-1&&n.splice(t,1)}),n}function s(e,n,r){let i=new Map(e.map(e=>[e.id,e])),a=r==null?void 0:new Map(r.map(e=>[e.id,e]));return n.forEach(e=>{if(e.type===`remove`)i.delete(e.data);else if(e.type===`insert`){let t=i.get(e.data.id);i.set(e.data.id,t?{...t,...e.data}:e.data)}else{let n=i.get(e.data.id)??a?.get(e.data.id);i.set(e.data.id,n?(0,t.modify)(n,e.data.modifier):(0,t.modify)({id:e.data.id},e.data.modifier))}}),[...i.values()]}function c(e){return e.added.length>0||e.modified.length>0||e.removed.length>0}function l(e,t){return c(a(e,t))}async function u({changes:e,lastSnapshot:t,data:n,pull:r,push:i,insert:u,update:d,remove:f,batch:p}){let m=n,h=t||[],g=o(t,m);if(e.length>0){let t=s(h,e);if(l(h,t)){let n=s(g,e,h),l=a(g,n);c(l)&&(await i(l),m=await r(),g=o(g,m)),h=t}}let _=m.changes==null?a(h,m.items):m.changes;return await p(async()=>{await Promise.all(_.added.map(e=>u(e))),await Promise.all(_.modified.map(e=>d(e.id,{$set:e}))),await Promise.all(_.removed.map(e=>f(e.id)))}),g}e.SyncManager=class{options;collections=new Map;changes;snapshots;syncOperations;scheduledPushes=new Set;remoteChanges=[];syncQueues=new Map;collectionsReady;isDisposed=!1;instanceId=(0,t.randomId)();id;debouncedFlush;constructor(e){this.options={autostart:!0,...e},this.id=this.options.id||`default-sync-manager`;let{reactivity:r}=this.options,i=this.options.dataAdapter??new t.DefaultDataAdapter;this.changes=new t.Collection(`${this.options.id}-changes`,i,{indices:[`collectionName`],reactivity:r}),this.snapshots=new t.Collection(`${this.options.id}-snapshots`,i,{indices:[`collectionName`],reactivity:r}),this.syncOperations=new t.Collection(`${this.options.id}-sync-operations`,i,{indices:[`collectionName`,`status`],reactivity:r});let a=[Promise.resolve(this.syncOperations.isReady()),Promise.resolve(this.changes.isReady()),Promise.resolve(this.snapshots.isReady())];this.collectionsReady=Promise.all(a).then(()=>{}),this.changes.setMaxListeners(1e3),this.snapshots.setMaxListeners(1e3),this.syncOperations.setMaxListeners(1e3),this.debouncedFlush=n(this.flushScheduledPushes,this.options.debounceTime??100)}getSyncQueue(e){return this.syncQueues.get(e)??this.syncQueues.set(e,new r),this.syncQueues.get(e)}detachSyncListeners(e){let t=this.collections.get(e);if(t==null)return;let{collection:n,syncListeners:r}=t;r!=null&&(n.off(`added`,r.added),n.off(`changed`,r.changed),n.off(`removed`,r.removed))}async removeCollection(e){this.collections.has(e)&&(await this.pauseSync(e),this.detachSyncListeners(e),this.collections.delete(e),this.syncQueues.delete(e),this.scheduledPushes.delete(e))}async dispose(){this.isDisposed=!0;for(let e of this.collections.keys())this.detachSyncListeners(e);this.collections.clear(),this.syncQueues.clear(),this.scheduledPushes.clear(),this.remoteChanges.splice(0),await Promise.all([this.changes.dispose(),this.snapshots.dispose(),this.syncOperations.dispose()])}getCollection(e){let{collection:t,options:n}=this.getCollectionProperties(e);return[t,n]}getCollectionProperties(e){let t=this.collections.get(e);if(t==null)throw Error(`Collection with id '${e}' not found`);return t}addCollection(e,t){if(this.isDisposed)throw Error(`SyncManager is disposed`);this.detachSyncListeners(t.name);let n=e=>{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;return!1},r=(e,t)=>{let n=[...this.remoteChanges];for(let r=0;r<n.length;r+=1){let i=n[r];i!=null&&i.collectionName===e&&(i.type!==`remove`||i.data===t)&&i.data.id===t&&(n[r]=null)}this.remoteChanges=n.filter(e=>e!=null)},i=e=>{if(!this.isDisposed){if(n({collectionName:t.name,type:`insert`,data:e})){r(t.name,e.id);return}this.changes.insert({collectionName:t.name,time:Date.now(),type:`insert`,data:e}).then(()=>{this.getCollectionProperties(t.name).syncPaused||this.schedulePush(t.name)}).catch(e=>{this.options.onError&&this.options.onError(this.getCollectionProperties(t.name).options,e)})}},a=({id:e},i)=>{if(this.isDisposed)return;let a={id:e,modifier:i};if(n({collectionName:t.name,type:`update`,data:a})){r(t.name,e);return}this.changes.insert({collectionName:t.name,time:Date.now(),type:`update`,data:a}).then(()=>{this.getCollectionProperties(t.name).syncPaused||this.schedulePush(t.name)}).catch(e=>{this.options.onError&&this.options.onError(this.getCollectionProperties(t.name).options,e)})},o=({id:e})=>{if(!this.isDisposed){if(n({collectionName:t.name,type:`remove`,data:e})){r(t.name,e);return}this.changes.insert({collectionName:t.name,time:Date.now(),type:`remove`,data:e}).then(()=>{this.getCollectionProperties(t.name).syncPaused||this.schedulePush(t.name)}).catch(e=>{this.options.onError&&this.options.onError(this.getCollectionProperties(t.name).options,e)})}};e.on(`added`,i),e.on(`changed`,a),e.on(`removed`,o);let s={added:i,changed:a,removed:o};this.collections.set(t.name,{collection:e,options:t,readyPromise:e.ready(),syncPaused:!0,syncListeners:s}),this.options.autostart&&this.startSync(t.name).catch(e=>{this.options.onError&&this.options.onError(this.getCollectionProperties(t.name).options,e)})}flushScheduledPushes(){this.scheduledPushes.forEach(e=>{this.pushChanges(e).catch(()=>{})}),this.scheduledPushes.clear()}schedulePush(e){this.scheduledPushes.add(e),this.debouncedFlush()}async startAll(){await Promise.all([...this.collections.keys()].map(e=>this.startSync(e)))}async startSync(e){let t=this.getCollectionProperties(e);if(!t.syncPaused)return;this.schedulePush(e);let n=this.options.registerRemoteChange?await this.options.registerRemoteChange(t.options,async t=>{this.isDisposed||(t==null?await this.sync(e):await this.getSyncQueue(e).add(async()=>{let n=Date.now(),r=await this.syncOperations.insert({start:n,collectionName:e,instanceId:this.instanceId,status:`active`});await this.syncWithData(e,t).then(async()=>{this.isDisposed||(await this.syncOperations.removeMany({id:{$ne:r},collectionName:e,$or:[{end:{$lte:n}},{status:`active`}]}),await this.syncOperations.updateOne({id:r},{$set:{status:`done`,end:Date.now()}}))}).catch(async t=>{throw this.options.onError&&this.options.onError(this.getCollectionProperties(e).options,t),await this.syncOperations.updateOne({id:r},{$set:{status:`error`,end:Date.now(),error:t.stack||t.message}}),t})}))}):void 0;this.collections.set(e,{...t,syncPaused:!1,cleanupFunction:n})}async pauseAll(){await Promise.all([...this.collections.keys()].map(e=>this.pauseSync(e)))}async pauseSync(e){let t=this.getCollectionProperties(e);t.syncPaused||(t.cleanupFunction&&await t.cleanupFunction(),this.collections.set(e,{...t,cleanupFunction:void 0,syncPaused:!0}))}async syncAll(){if(this.isDisposed)throw Error(`SyncManager is disposed`);let e=[];if(await Promise.all([...this.collections.keys()].map(t=>this.sync(t).catch(n=>{e.push({id:t,error:n})}))),e.length>0)throw Error(`Error while syncing collections:\n${e.map(e=>`${e.id}: ${e.error.message}`).join(`
2
2
 
3
3
  `)}`)}isSyncing(e,t){let n=this.syncOperations.findOne({...e?{collectionName:e}:{},status:`active`},{fields:{status:1},async:t});return n instanceof Promise?n.then(e=>e!=null):n!=null}async isReady(){await this.collectionsReady}async sync(e,t={}){if(this.isDisposed)throw Error(`SyncManager is disposed`);await this.isReady();let{options:n,readyPromise:r}=this.getCollectionProperties(e);await r;let i=await this.syncOperations.find({collectionName:e,instanceId:this.instanceId,status:`active`},{reactive:!1,async:!0}).count()>0,a=Date.now(),o=null;await new Promise(e=>{setTimeout(e,0)});let s=async()=>{let r=await this.syncOperations.findOne({collectionName:e,status:`done`},{sort:{end:-1},reactive:!1,async:!0});if(t?.onlyWithChanges&&await this.changes.find({collectionName:e,time:{$lte:a}},{sort:{time:1},reactive:!1,async:!0}).count()===0)return;i||(o=await this.syncOperations.insert({start:a,collectionName:e,instanceId:this.instanceId,status:`active`}));let s=await this.options.pull(n,{lastFinishedSyncStart:r?.start,lastFinishedSyncEnd:r?.end});await this.syncWithData(e,s)};await(t?.force?s():this.getSyncQueue(e).add(s)).catch(async e=>{throw o!=null&&(this.options.onError&&this.options.onError(n,e),await this.syncOperations.updateOne({id:o},{$set:{status:`error`,end:Date.now(),error:e.stack||e.message}})),e}),o!=null&&(await this.syncOperations.removeMany({id:{$ne:o},collectionName:e,$or:[{end:{$lte:a}},{status:`active`}]}),await this.syncOperations.updateOne({id:o},{$set:{status:`done`,end:Date.now()}}))}async pushChanges(e){await this.sync(e,{onlyWithChanges:!0})}async syncWithData(e,t){if(this.isDisposed)return;let{collection:n,options:r}=this.getCollectionProperties(e),i=Date.now(),a=await this.syncOperations.findOne({collectionName:e,status:`done`},{sort:{end:-1},reactive:!1,async:!0}),o=await this.snapshots.findOne({collectionName:e},{sort:{time:-1},reactive:!1,async:!0}),s=await this.changes.find({collectionName:e,time:{$lte:i}},{sort:{time:1},reactive:!1,async:!0}).fetch();await u({changes:s,lastSnapshot:o?.items,data:t,pull:()=>this.options.pull(r,{lastFinishedSyncStart:a?.start,lastFinishedSyncEnd:a?.end}),push:e=>this.options.push(r,{changes:e,rawChanges:s}),insert:async t=>{this.remoteChanges.push({collectionName:e,type:`insert`,data:t},{collectionName:e,type:`update`,data:{id:t.id,modifier:{$set:t}}}),await n.replaceOne({id:t.id},t,{upsert:!0})},update:async(t,r)=>{this.remoteChanges.push({collectionName:e,type:`insert`,data:{id:t,...r.$set}},{collectionName:e,type:`update`,data:{id:t,modifier:r}}),await n.updateOne({id:t},{...r,$setOnInsert:{id:t}},{upsert:!0})},remove:async t=>{await n.find({id:t},{reactive:!1,async:!0}).count()>0&&(this.remoteChanges.push({collectionName:e,type:`remove`,data:t}),await n.removeOne({id:t}))},batch:async e=>n.batch(async()=>{await e()})}).then(async t=>{if(this.isDisposed)return;if(await this.snapshots.removeMany({collectionName:e,time:{$lte:i}}),await this.changes.removeMany({collectionName:e,id:{$in:s.map(e=>e.id)}}),await this.snapshots.insert({time:i,collectionName:e,items:t}),await new Promise(e=>{setTimeout(e,0)}),await this.changes.find({collectionName:e},{reactive:!1,async:!0}).count()>0){await this.sync(e,{force:!0,onlyWithChanges:!0});return}let r=await n.find({id:{$nin:t.map(e=>e.id)}},{reactive:!1,async:!0}).map(e=>e.id);await n.batch(async()=>{await Promise.all(t.map(async t=>{this.remoteChanges.push({collectionName:e,type:`insert`,data:t},{collectionName:e,type:`update`,data:{id:t.id,modifier:{$set:t}}}),await n.replaceOne({id:t.id},t,{upsert:!0})})),await Promise.all(r.map(async e=>{await n.removeOne({id:e})}))})})}}});
4
4
  //# sourceMappingURL=index.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.js","names":[],"sources":["../src/utils/debounce.ts","../src/utils/PromiseQueue.ts","../src/computeChanges.ts","../src/getSnapshot.ts","../src/applyChanges.ts","../src/sync.ts","../src/SyncManager.ts"],"sourcesContent":["type DebounceOptions = {\n leading?: boolean,\n trailing?: boolean,\n}\n\n/**\n * Debounces a function.\n * @param fn Function to debounce\n * @param wait Time to wait before calling the function.\n * @param [options] Debounce options\n * @param [options.leading] Whether to call the function on the leading edge of the wait interval.\n * @param [options.trailing] Whether to call the function on the trailing edge of the wait interval.\n * @returns The debounced function.\n */\nexport default function debounce<Arguments extends any[], T, ThisType>(\n fn: (this: ThisType, ...args: Arguments) => T,\n wait: number,\n options: DebounceOptions = {},\n): (...args: Arguments) => T {\n let timeout: ReturnType<typeof setTimeout> | null\n let result: T | null\n\n const { leading = false, trailing = true } = options\n\n /**\n * The debounced function that will be returned.\n * @param this The context to bind the function to.\n * @param args The arguments to pass to the function.\n * @returns The result of the debounced function.\n */\n function debounced(this: ThisType, ...args: Arguments) {\n const shouldCallImmediately = leading && !timeout\n const shouldCallTrailing = trailing && !timeout\n\n if (timeout) {\n clearTimeout(timeout)\n }\n\n timeout = setTimeout(() => {\n timeout = null\n if (trailing && !shouldCallImmediately) {\n result = fn.apply(this, args)\n }\n }, wait)\n\n if (shouldCallImmediately) {\n result = fn.apply(this, args)\n } else if (!shouldCallTrailing) {\n result = null\n }\n\n return result as T\n }\n return debounced\n}\n","/**\n * Class for queuing promises to be executed one after the other.\n * This is useful for tasks that should not be executed in parallel.\n * @example\n * const queue = new PromiseQueue();\n * queue.add(() => fetch('https://example.com/api/endpoint1'));\n * queue.add(() => fetch('https://example.com/api/endpoint2'));\n * // The second fetch will only be executed after the first one is done.\n */\nexport default class PromiseQueue {\n private queue: (() => Promise<any>)[] = []\n private pendingPromise: boolean = false\n\n /**\n * Method to add a new promise to the queue and returns a promise that resolves when this task is done\n * @param task Function that returns a promise that will be added to the queue\n * @returns Promise that resolves when the task is done\n */\n add(task: () => Promise<any>): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n // Wrap the task with the resolve and reject to control its completion from the outside\n this.queue.push(() => task()\n .then(resolve)\n .catch((error: Error) => {\n reject(error)\n throw error\n }))\n this.dequeue()\n })\n }\n\n /**\n * Method to check if there is a pending promise in the queue\n * @returns True if there is a pending promise, false otherwise\n */\n public hasPendingPromise(): boolean {\n return this.pendingPromise\n }\n\n /**\n * Method to process the queue\n */\n private dequeue() {\n if (this.pendingPromise || this.queue.length === 0) {\n return\n }\n const task = this.queue.shift()\n if (!task) return\n this.pendingPromise = true\n task()\n .then(() => {\n this.pendingPromise = false\n this.dequeue()\n })\n .catch(() => {\n this.pendingPromise = false\n this.dequeue()\n })\n }\n}\n","import type { BaseItem } from '@signaldb/core'\nimport { isEqual } from '@signaldb/core'\n\n/**\n * Computes the modified fields between two items recursively.\n * @param oldItem The old item\n * @param newItem The new item\n * @returns The modified fields\n */\nexport function computeModifiedFields<T extends Record<string, any>>(\n oldItem: T,\n newItem: T,\n): string[] {\n const modifiedFields: string[] = []\n\n const oldKeys = Object.keys(oldItem)\n const newKeys = Object.keys(newItem)\n const allKeys = new Set([...oldKeys, ...newKeys])\n\n for (const key of allKeys) {\n if (newItem[key] !== oldItem[key]) {\n if (typeof newItem[key] === 'object' && typeof oldItem[key] === 'object' && newItem[key] != null && oldItem[key] != null) {\n const nestedModifiedFields = computeModifiedFields(oldItem[key], newItem[key])\n for (const nestedField of nestedModifiedFields) {\n modifiedFields.push(`${key}.${nestedField}`)\n }\n } else {\n modifiedFields.push(key)\n }\n }\n }\n\n return modifiedFields\n}\n\n/**\n * Compute changes between two arrays of items.\n * @param oldItems Array of the old items\n * @param newItems Array of the new items\n * @returns The changeset\n */\nexport default function computeChanges<ItemType extends BaseItem<IdType>, IdType>(\n oldItems: ItemType[],\n newItems: ItemType[],\n) {\n const added: ItemType[] = []\n const modified: ItemType[] = []\n const modifiedFields: Map<IdType, string[]> = new Map()\n const removed: ItemType[] = []\n\n const oldItemsMap = new Map(oldItems.map(item => [item.id, item]))\n const newItemsMap = new Map(newItems.map(item => [item.id, item]))\n\n for (const [id, oldItem] of oldItemsMap) {\n const newItem = newItemsMap.get(id)\n if (!newItem) {\n removed.push(oldItem)\n } else if (!isEqual(newItem, oldItem)) {\n modifiedFields.set(newItem.id, computeModifiedFields(oldItem, newItem))\n modified.push(newItem)\n }\n }\n\n for (const [id, newItem] of newItemsMap) {\n if (!oldItemsMap.has(id)) {\n added.push(newItem)\n }\n }\n\n return {\n added,\n modified,\n modifiedFields,\n removed,\n }\n}\n","import type { BaseItem } from '@signaldb/core'\nimport type { LoadResponse } from './types'\n\n/**\n * Gets the snapshot of items from the last snapshot and the changes.\n * @param lastSnapshot The last snapshot of items\n * @param data The changes to apply to the last snapshot\n * @returns The new snapshot of items\n */\nexport default function getSnapshot<ItemType extends BaseItem<IdType>, IdType>(\n lastSnapshot: ItemType[] | undefined,\n data: LoadResponse<ItemType>,\n) {\n if (data.items != null) return data.items\n\n const items = lastSnapshot || []\n data.changes.added.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index === -1) {\n items.push(item)\n } else {\n items[index] = item\n }\n })\n data.changes.modified.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index === -1) {\n items.push(item)\n } else {\n items[index] = item\n }\n })\n data.changes.removed.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index !== -1) items.splice(index, 1)\n })\n return items\n}\n","import type { BaseItem } from '@signaldb/core'\nimport { modify } from '@signaldb/core'\nimport type { Change } from './types'\n\n/**\n * applies changes to a collection of items\n * @param items The items to apply the changes to\n * @param changes The changes to apply to the items\n * @returns The new items after applying the changes\n */\nexport default function applyChanges<ItemType extends BaseItem<IdType>, IdType>(\n items: ItemType[],\n changes: Change<ItemType, IdType>[],\n): ItemType[] {\n // Create initial map of items by ID\n const itemMap = new Map(items.map(item => [item.id, item]))\n\n changes.forEach((change) => {\n if (change.type === 'remove') {\n itemMap.delete(change.data)\n } else if (change.type === 'insert') {\n const existingItem = itemMap.get(change.data.id)\n itemMap.set(\n change.data.id,\n existingItem ? { ...existingItem, ...change.data } : change.data,\n )\n } else { // change.type === 'update'\n const existingItem = itemMap.get(change.data.id)\n itemMap.set(\n change.data.id,\n existingItem\n ? modify(existingItem, change.data.modifier)\n : modify({ id: change.data.id } as ItemType, change.data.modifier),\n )\n }\n })\n\n // Convert map back to array\n return [...itemMap.values()]\n}\n","import type { BaseItem, Modifier, Changeset } from '@signaldb/core'\nimport computeChanges from './computeChanges'\nimport type { Change, LoadResponse } from './types'\nimport getSnapshot from './getSnapshot'\nimport applyChanges from './applyChanges'\n\n/**\n * Checks if there are any changes in the given changeset.\n * @param changes The changeset to check.\n * @returns True if there are changes, false otherwise.\n */\nfunction hasChanges<T>(\n changes: Changeset<T>,\n) {\n return changes.added.length > 0\n || changes.modified.length > 0\n || changes.removed.length > 0\n}\n/**\n * Checks if there is a difference between the old items and the new items.\n * @param oldItems The old items.\n * @param newItems The new items.\n * @returns True if there is a difference, false otherwise.\n */\nfunction hasDifference<ItemType extends BaseItem<IdType>, IdType>(\n oldItems: ItemType[],\n newItems: ItemType[],\n) {\n return hasChanges(computeChanges(oldItems, newItems))\n}\n\ninterface Options<ItemType extends BaseItem<IdType>, IdType> {\n changes: Change<ItemType, IdType>[],\n lastSnapshot?: ItemType[],\n data: LoadResponse<ItemType>,\n pull: () => Promise<LoadResponse<ItemType>>,\n push: (changes: Changeset<ItemType> & {\n modifiedFields: Map<IdType, string[]>,\n }) => Promise<void>,\n insert: (item: ItemType) => Promise<void>,\n update: (id: IdType, modifier: Modifier<ItemType>) => Promise<void>,\n remove: (id: IdType) => Promise<void>,\n batch: (fn: () => Promise<void>) => Promise<void>,\n}\n\n/**\n * Does a sync operation based on the provided options. If changes are supplied, these will be rebased on the new data.\n * Afterwards the push method will be called with the remaining changes. A new snapshot will be created and returned.\n * @param options Sync options\n * @param options.changes Changes to call the push method with\n * @param [options.lastSnapshot] The last snapshot\n * @param options.data The new data\n * @param options.pull Method to pull new data\n * @param options.push Method to push changes\n * @param options.insert Method to insert an item\n * @param options.update Method to update an item\n * @param options.remove Method to remove an item\n * @param options.batch Method to batch multiple operations\n * @returns The new snapshot\n */\nexport default async function sync<ItemType extends BaseItem<IdType>, IdType>({\n changes,\n lastSnapshot,\n data,\n pull,\n push,\n insert,\n update,\n remove,\n batch,\n}: Options<ItemType, IdType>): Promise<ItemType[]> {\n let newData = data\n let previousSnapshot = lastSnapshot || []\n let newSnapshot = getSnapshot(lastSnapshot, newData)\n if (changes.length > 0) {\n // apply changes on last snapshot and check if there is a difference\n const lastSnapshotWithChanges = applyChanges(previousSnapshot, changes)\n if (hasDifference(previousSnapshot, lastSnapshotWithChanges)) {\n // if yes, apply the changes on the newSnapshot and check if there is a difference\n const newSnapshotWithChanges = applyChanges(newSnapshot, changes)\n const changesToPush = computeChanges<ItemType, IdType>(newSnapshot, newSnapshotWithChanges)\n if (hasChanges(changesToPush)) {\n // if yes, push the changes to the server\n await push(changesToPush)\n\n // pull new data afterwards to ensure that all server changes are applied\n newData = await pull()\n newSnapshot = getSnapshot(newSnapshot, newData)\n }\n previousSnapshot = lastSnapshotWithChanges\n }\n }\n\n // apply the new changes on the collection\n const newChanges = newData.changes == null\n ? computeChanges(previousSnapshot, newData.items)\n : newData.changes\n await batch(async () => {\n await Promise.all(newChanges.added.map(item => insert(item)))\n await Promise.all(newChanges.modified.map(item => update(item.id, { $set: item })))\n await Promise.all(newChanges.removed.map(item => remove(item.id)))\n })\n\n return newSnapshot\n}\n","import type {\n BaseItem,\n DataAdapter,\n ReactivityAdapter,\n Changeset,\n Selector,\n} from '@signaldb/core'\nimport { DefaultDataAdapter, Collection, randomId } from '@signaldb/core'\nimport debounce from './utils/debounce'\nimport PromiseQueue from './utils/PromiseQueue'\nimport sync from './sync'\nimport type { Change, LoadResponse, Snapshot, SyncOperation } from './types'\n\ntype SyncOptions<T extends Record<string, any>> = {\n name: string,\n} & T\n\ntype CleanupFunction = (() => void | Promise<void>) | void\n\ninterface Options<\n CollectionOptions extends Record<string, any>,\n ItemType extends BaseItem<IdType> = BaseItem,\n IdType = any,\n> {\n pull: (\n collectionOptions: SyncOptions<CollectionOptions>,\n pullParameters: {\n lastFinishedSyncStart?: number,\n lastFinishedSyncEnd?: number,\n },\n ) => Promise<LoadResponse<ItemType>>,\n push: (\n collectionOptions: SyncOptions<CollectionOptions>,\n pushParameters: {\n rawChanges: Omit<Change, 'id' | 'collectionName'>[],\n changes: Changeset<ItemType> & {\n modifiedFields: Map<IdType, string[]>,\n },\n },\n ) => Promise<void>,\n registerRemoteChange?: (\n collectionOptions: SyncOptions<CollectionOptions>,\n onChange: (data?: LoadResponse<ItemType>) => Promise<void>,\n ) => CleanupFunction | Promise<CleanupFunction>,\n\n id?: string,\n dataAdapter?: DataAdapter,\n reactivity?: ReactivityAdapter,\n onError?: (collectionOptions: SyncOptions<CollectionOptions>, error: Error) => void,\n\n autostart?: boolean,\n debounceTime?: number,\n}\n\n/**\n * Class to manage syncing of collections.\n * @template CollectionOptions\n * @template ItemType\n * @template IdType\n * @example\n * const syncManager = new SyncManager({\n * pull: async (collectionOptions) => {\n * const response = await fetch(`/api/collections/${collectionOptions.name}`)\n * return await response.json()\n * },\n * push: async (collectionOptions, { changes }) => {\n * await fetch(`/api/collections/${collectionOptions.name}`, {\n * method: 'POST',\n * body: JSON.stringify(changes),\n * })\n * },\n * })\n *\n * const collection = new Collection()\n * syncManager.addCollection(collection, {\n * name: 'todos',\n * })\n *\n * syncManager.sync('todos')\n */\nexport default class SyncManager<\n CollectionOptions extends Record<string, any>,\n ItemType extends BaseItem<IdType> = BaseItem,\n IdType = any,\n> {\n protected options: Options<CollectionOptions, ItemType, IdType>\n protected collections: Map<string, {\n collection: Collection<ItemType, IdType, any>,\n options: SyncOptions<CollectionOptions>,\n readyPromise: Promise<void>,\n syncPaused: boolean,\n cleanupFunction?: CleanupFunction,\n }> = new Map()\n\n protected changes: Collection<Change<ItemType>, string>\n protected snapshots: Collection<Snapshot<ItemType>, string>\n protected syncOperations: Collection<SyncOperation, string>\n protected scheduledPushes: Set<string> = new Set()\n protected remoteChanges: Omit<Change, 'id' | 'time'>[] = []\n protected syncQueues: Map<string, PromiseQueue> = new Map()\n protected collectionsReady: Promise<void>\n protected isDisposed = false\n protected instanceId = randomId()\n protected id: string\n protected debouncedFlush: () => void\n\n /**\n * @param options Collection options\n * @param options.pull Function to pull data from remote source.\n * @param options.push Function to push data to remote source.\n * @param [options.registerRemoteChange] Function to register a callback for remote changes.\n * @param [options.id] Unique identifier for this sync manager. Only nessesary if you have multiple sync managers.\n * @param [options.storageAdapter] Storage adapter to use for storing changes, snapshots and sync operations.\n * @param [options.reactivity] Reactivity adapter to use for reactivity.\n * @param [options.onError] Function to handle errors that occur async during syncing.\n * @param [options.autostart] Whether to automatically start syncing new collections.\n * @param [options.debounceTime] The time in milliseconds to debounce push operations.\n */\n constructor(options: Options<CollectionOptions, ItemType, IdType>) {\n this.options = {\n autostart: true,\n ...options,\n }\n this.id = this.options.id || 'default-sync-manager'\n const { reactivity } = this.options\n const dataAdapter = this.options.dataAdapter ?? new DefaultDataAdapter()\n this.changes = new Collection(`${this.options.id}-changes`, dataAdapter, {\n indices: ['collectionName'],\n reactivity,\n })\n this.snapshots = new Collection(`${this.options.id}-snapshots`, dataAdapter, {\n indices: ['collectionName'],\n reactivity,\n })\n this.syncOperations = new Collection(`${this.options.id}-sync-operations`, dataAdapter, {\n indices: ['collectionName', 'status'],\n reactivity,\n })\n\n const readiness = [\n Promise.resolve(this.syncOperations.isReady()),\n Promise.resolve(this.changes.isReady()),\n Promise.resolve(this.snapshots.isReady()),\n ]\n this.collectionsReady = Promise.all(readiness).then(() => { /* noop */ })\n\n this.changes.setMaxListeners(1000)\n this.snapshots.setMaxListeners(1000)\n this.syncOperations.setMaxListeners(1000)\n\n this.debouncedFlush = debounce(this.flushScheduledPushes, this.options.debounceTime ?? 100)\n }\n\n protected getSyncQueue(name: string) {\n if (this.syncQueues.get(name) == null) {\n this.syncQueues.set(name, new PromiseQueue())\n }\n return this.syncQueues.get(name) as PromiseQueue\n }\n\n /**\n * Clears all internal data structures\n */\n public async dispose() {\n this.isDisposed = true\n this.collections.clear()\n this.syncQueues.clear()\n this.remoteChanges.splice(0)\n await Promise.all([\n this.changes.dispose(),\n this.snapshots.dispose(),\n this.syncOperations.dispose(),\n ])\n }\n\n /**\n * Gets a collection with it's options by name\n * @deprecated Use getCollectionProperties instead.\n * @param name Name of the collection\n * @throws {Error} Will throw an error if the name wasn't found\n * @returns Tuple of collection and options\n */\n public getCollection(name: string) {\n const { collection, options } = this.getCollectionProperties(name)\n return [collection, options]\n }\n\n /**\n * Gets collection options by name\n * @param name Name of the collection\n * @throws {Error} Will throw an error if the name wasn't found\n * @returns An object of all properties of the collection\n */\n public getCollectionProperties(name: string) {\n const collectionParameters = this.collections.get(name)\n if (collectionParameters == null) throw new Error(`Collection with id '${name}' not found`)\n return collectionParameters\n }\n\n /**\n * Adds a collection to the sync manager.\n * @param collection Collection to add\n * @param options Options for the collection. The object needs at least a `name` property.\n * @param options.name Unique name of the collection\n */\n public addCollection<\n CollectionItem extends ItemType = ItemType,\n >(\n collection: Collection<CollectionItem, IdType, any>,\n options: SyncOptions<CollectionOptions>,\n ) {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n\n this.collections.set(options.name, {\n collection: collection as unknown as Collection<ItemType, IdType, any>,\n options,\n readyPromise: collection.ready(),\n syncPaused: true, // always start paused as the autostart will start it\n })\n\n const hasRemoteChange = (change: Omit<Change, 'id' | 'time'>) => {\n for (const remoteChange of this.remoteChanges) {\n if (remoteChange == null) continue\n if (remoteChange.collectionName !== change.collectionName) continue\n if (remoteChange.type !== change.type) continue\n\n if (change.type === 'remove' && remoteChange.data !== change.data) continue\n if (remoteChange.data.id !== change.data.id) continue\n return true\n }\n return false\n }\n const removeRemoteChanges = (collectionName: string, id: IdType) => {\n const newRemoteChanges: (Omit<Change, 'id' | 'time'> | null)[] = [...this.remoteChanges]\n for (let i = 0; i < newRemoteChanges.length; i += 1) {\n const item = newRemoteChanges[i]\n if (item == null) continue\n if (item.collectionName !== collectionName) continue\n if (item.type === 'remove' && item.data !== id) continue\n if (item.data.id !== id) continue\n newRemoteChanges[i] = null\n }\n this.remoteChanges = newRemoteChanges.filter(item => item != null)\n }\n\n collection.on('added', (item) => {\n if (this.isDisposed) return\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'insert', data: item })) {\n removeRemoteChanges(options.name, item.id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'insert',\n data: item,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n })\n collection.on('changed', ({ id }, modifier) => {\n if (this.isDisposed) return\n const data = { id, modifier }\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'update', data })) {\n removeRemoteChanges(options.name, id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'update',\n data,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n })\n collection.on('removed', ({ id }) => {\n if (this.isDisposed) return\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'remove', data: id })) {\n removeRemoteChanges(options.name, id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'remove',\n data: id,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n })\n\n if (this.options.autostart) {\n this.startSync(options.name)\n .catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n }\n }\n\n protected flushScheduledPushes() {\n this.scheduledPushes.forEach((name) => {\n this.pushChanges(name).catch(() => { /* error handler is called in sync */ })\n })\n this.scheduledPushes.clear()\n }\n\n protected schedulePush(name: string) {\n this.scheduledPushes.add(name)\n this.debouncedFlush()\n }\n\n /**\n * Setup all collections to be synced with remote changes\n * and enable automatic pushing changes to the remote source.\n */\n public async startAll() {\n await Promise.all([...this.collections.keys()].map(id =>\n this.startSync(id)))\n }\n\n /**\n * Setup a collection to be synced with remote changes\n * and enable automatic pushing changes to the remote source.\n * @param name Name of the collection\n */\n public async startSync(name: string) {\n const collectionParameters = this.getCollectionProperties(name)\n if (!collectionParameters.syncPaused) return // already started\n\n this.schedulePush(name) // push changes that were made while paused\n\n const cleanupFunction = this.options.registerRemoteChange\n ? await this.options.registerRemoteChange(\n collectionParameters.options,\n async (data) => {\n if (this.isDisposed) return\n // eslint-disable-next-line unicorn/prefer-ternary -- this is easier to read than a ternary operator\n if (data == null) {\n // if no data is provided, we will sync the collection with the remote source\n await this.sync(name)\n } else {\n // if data is provided, we will sync the collection with the provided data\n await this.getSyncQueue(name).add(async () => {\n const syncTime = Date.now()\n const syncId = await this.syncOperations.insert({\n start: syncTime,\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n })\n await this.syncWithData(name, data)\n .then(async () => {\n if (this.isDisposed) return\n // clean up old sync operations\n await this.syncOperations.removeMany({\n id: { $ne: syncId },\n collectionName: name,\n $or: [\n { end: { $lte: syncTime } },\n { status: 'active' },\n ],\n })\n\n // update sync operation status to done after everthing was finished\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'done', end: Date.now() },\n })\n })\n .catch(async (error: Error) => {\n if (this.options.onError) {\n this.options.onError(this.getCollectionProperties(name).options, error)\n }\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'error', end: Date.now(), error: error.stack || error.message },\n })\n throw error\n })\n })\n }\n },\n )\n : undefined\n this.collections.set(name, {\n ...collectionParameters,\n syncPaused: false,\n cleanupFunction,\n })\n }\n\n /**\n * Pauses the sync process for all collections.\n * This means that the collections will not be synced with remote changes\n * and changes will not automatically be pushed to the remote source.\n */\n public async pauseAll() {\n await Promise.all([...this.collections.keys()].map(id =>\n this.pauseSync(id)))\n }\n\n /**\n * Pauses the sync process for a collection.\n * This means that the collection will not be synced with remote changes\n * and changes will not automatically be pushed to the remote source.\n * @param name Name of the collection\n */\n public async pauseSync(name: string) {\n const collectionParameters = this.getCollectionProperties(name)\n if (collectionParameters.syncPaused) return // already paused\n if (collectionParameters.cleanupFunction) await collectionParameters.cleanupFunction()\n this.collections.set(name, {\n ...collectionParameters,\n cleanupFunction: undefined,\n syncPaused: true,\n })\n }\n\n /**\n * Starts the sync process for all collections\n */\n public async syncAll() {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n const errors: { id: string, error: Error }[] = []\n await Promise.all([...this.collections.keys()].map(id =>\n this.sync(id).catch((error: Error) => {\n errors.push({ id, error })\n })))\n if (errors.length > 0) throw new Error(`Error while syncing collections:\\n${errors.map(error => `${error.id}: ${error.error.message}`).join('\\n\\n')}`)\n }\n\n /**\n * Checks if a collection is currently beeing synced\n * @param [name] Name of the collection. If not provided, it will check if any collection is currently beeing synced.\n * @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.\n * @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.\n */\n public isSyncing(\n name: string | undefined,\n async: true,\n ): Promise<boolean>\n\n public isSyncing(\n name?: string,\n async?: false,\n ): boolean\n\n public isSyncing<Async extends boolean = false>(\n name?: string,\n async?: Async,\n ): Promise<boolean> | boolean {\n const itemOrPromise = this.syncOperations.findOne({\n ...name ? { collectionName: name } : {},\n status: 'active',\n }, { fields: { status: 1 }, async })\n if (itemOrPromise instanceof Promise) {\n return itemOrPromise\n .then(item => item != null) as Async extends true ? Promise<boolean> : boolean\n }\n return (itemOrPromise != null) as Async extends true ? Promise<boolean> : boolean\n }\n\n /**\n * Checks if the sync manager is ready to sync.\n * @returns A promise that resolves when the sync manager is ready to sync.\n */\n public async isReady() {\n await this.collectionsReady\n }\n\n /**\n * Starts the sync process for a collection\n * @param name Name of the collection\n * @param options Options for the sync process.\n * @param options.force If true, the sync process will be started even if there are no changes and onlyWithChanges is true.\n * @param options.onlyWithChanges If true, the sync process will only be started if there are changes.\n */\n public async sync(name: string, options: { force?: boolean, onlyWithChanges?: boolean } = {}) {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n await this.isReady()\n const { options: collectionOptions, readyPromise } = this.getCollectionProperties(name)\n await readyPromise\n\n const hasActiveSyncs = await this.syncOperations.find({\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n }, {\n reactive: false,\n async: true,\n }).count() > 0\n const syncTime = Date.now()\n let syncId: string | null = null\n\n // schedule for next tick to allow other tasks to run first\n await new Promise((resolve) => {\n setTimeout(resolve, 0)\n })\n const doSync = async () => {\n const lastFinishedSync = await this.syncOperations.findOne({\n collectionName: name,\n status: 'done',\n }, {\n sort: { end: -1 },\n reactive: false,\n async: true,\n })\n if (options?.onlyWithChanges) {\n const currentChanges = await this.changes.find({\n collectionName: name,\n time: { $lte: syncTime },\n }, {\n sort: { time: 1 },\n reactive: false,\n async: true,\n }).count()\n if (currentChanges === 0) return\n }\n\n if (!hasActiveSyncs) {\n syncId = await this.syncOperations.insert({\n start: syncTime,\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n })\n }\n const data = await this.options.pull(collectionOptions, {\n lastFinishedSyncStart: lastFinishedSync?.start,\n lastFinishedSyncEnd: lastFinishedSync?.end,\n })\n\n await this.syncWithData(name, data)\n }\n\n await (options?.force ? doSync() : this.getSyncQueue(name).add(doSync))\n .catch(async (error: Error) => {\n if (syncId != null) {\n if (this.options.onError) this.options.onError(collectionOptions, error)\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'error', end: Date.now(), error: error.stack || error.message },\n })\n }\n throw error\n })\n\n if (syncId != null) {\n // clean up old sync operations\n await this.syncOperations.removeMany({\n id: { $ne: syncId },\n collectionName: name,\n $or: [\n { end: { $lte: syncTime } },\n { status: 'active' },\n ],\n })\n\n // update sync operation status to done after everthing was finished\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'done', end: Date.now() },\n })\n }\n }\n\n /**\n * Starts the push process for a collection (sync process but only if there are changes)\n * @param name Name of the collection\n */\n public async pushChanges(name: string) {\n await this.sync(name, {\n onlyWithChanges: true,\n })\n }\n\n protected async syncWithData(\n name: string,\n data: LoadResponse<ItemType>,\n ) {\n if (this.isDisposed) return\n const { collection, options: collectionOptions } = this.getCollectionProperties(name)\n\n const syncTime = Date.now()\n\n const lastFinishedSync = await this.syncOperations.findOne({\n collectionName: name,\n status: 'done',\n }, {\n sort: { end: -1 },\n reactive: false,\n async: true,\n })\n const lastSnapshot = await this.snapshots.findOne({\n collectionName: name,\n } as Selector<Snapshot<any>>, {\n sort: { time: -1 },\n reactive: false,\n async: true,\n })\n const currentChanges = await this.changes.find({\n collectionName: name,\n time: { $lte: syncTime },\n }, {\n sort: { time: 1 },\n reactive: false,\n async: true,\n }).fetch()\n\n await sync<ItemType, ItemType['id']>({\n changes: currentChanges,\n lastSnapshot: lastSnapshot?.items,\n data,\n pull: () => this.options.pull(collectionOptions, {\n lastFinishedSyncStart: lastFinishedSync?.start,\n lastFinishedSyncEnd: lastFinishedSync?.end,\n }),\n push: changes => this.options.push(collectionOptions, {\n changes,\n rawChanges: currentChanges,\n }),\n insert: async (item) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: item,\n }, {\n collectionName: name,\n type: 'update',\n data: { id: item.id, modifier: { $set: item } },\n })\n\n // replace the item\n await collection.replaceOne({ id: item.id } as Selector<any>, item, { upsert: true })\n },\n update: async (itemId, modifier) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: { id: itemId, ...modifier.$set },\n }, {\n collectionName: name,\n type: 'update',\n data: { id: itemId, modifier },\n })\n\n await collection.updateOne({ id: itemId } as Selector<any>, {\n ...modifier,\n $setOnInsert: { id: itemId } as Partial<ItemType>,\n }, { upsert: true })\n },\n remove: async (itemId) => {\n const itemExists = await collection.find({\n id: itemId,\n } as Selector<any>, { reactive: false, async: true }).count() > 0\n if (!itemExists) return\n this.remoteChanges.push({\n collectionName: name,\n type: 'remove',\n data: itemId,\n })\n await collection.removeOne({ id: itemId } as Selector<any>)\n },\n batch: async (fn) => {\n return collection.batch(async () => {\n await fn()\n })\n },\n })\n .then(async (snapshot) => {\n if (this.isDisposed) return\n\n // clean up old snapshots\n await this.snapshots.removeMany({\n collectionName: name,\n time: { $lte: syncTime },\n } as Selector<any>)\n\n // clean up processed changes\n await this.changes.removeMany({\n collectionName: name,\n id: { $in: currentChanges.map(c => c.id) },\n })\n\n // insert new snapshot\n await this.snapshots.insert({\n time: syncTime,\n collectionName: name,\n items: snapshot,\n })\n\n // delay sync operation update to next tick to allow other tasks to run first\n await new Promise((resolve) => {\n setTimeout(resolve, 0)\n })\n\n const hasChanges = await this.changes.find({\n collectionName: name,\n }, { reactive: false, async: true }).count() > 0\n\n if (hasChanges) {\n // check if there are unsynced changes to push\n // and sync again if there are any\n await this.sync(name, {\n force: true,\n onlyWithChanges: true,\n })\n return\n }\n\n // if there are no unsynced changes apply the last snapshot\n // to make sure that collection and snapshot are in sync\n\n // find all items that are not in the snapshot\n const nonExistingItemIds = await collection.find({\n id: { $nin: snapshot.map(item => item.id) },\n } as Selector<any>, {\n reactive: false,\n async: true,\n }).map(item => item.id) as IdType[]\n\n await collection.batch(async () => {\n // update all items that are in the snapshot\n await Promise.all(snapshot.map(async (item) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: item,\n }, {\n collectionName: name,\n type: 'update',\n data: { id: item.id, modifier: { $set: item } },\n })\n\n // replace the item\n await collection.replaceOne({ id: item.id } as Selector<any>, item, { upsert: true })\n }))\n\n // remove all items that are not in the snapshot\n await Promise.all(nonExistingItemIds.map(async (id) => {\n await collection.removeOne({ id } as Selector<any>)\n }))\n })\n })\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.umd.js","names":[],"sources":["../src/utils/debounce.ts","../src/utils/PromiseQueue.ts","../src/computeChanges.ts","../src/getSnapshot.ts","../src/applyChanges.ts","../src/sync.ts","../src/SyncManager.ts"],"sourcesContent":["type DebounceOptions = {\n leading?: boolean,\n trailing?: boolean,\n}\n\n/**\n * Debounces a function.\n * @param fn Function to debounce\n * @param wait Time to wait before calling the function.\n * @param [options] Debounce options\n * @param [options.leading] Whether to call the function on the leading edge of the wait interval.\n * @param [options.trailing] Whether to call the function on the trailing edge of the wait interval.\n * @returns The debounced function.\n */\nexport default function debounce<Arguments extends any[], T, ThisType>(\n fn: (this: ThisType, ...args: Arguments) => T,\n wait: number,\n options: DebounceOptions = {},\n): (...args: Arguments) => T {\n let timeout: ReturnType<typeof setTimeout> | null\n let result: T | null\n\n const { leading = false, trailing = true } = options\n\n /**\n * The debounced function that will be returned.\n * @param this The context to bind the function to.\n * @param args The arguments to pass to the function.\n * @returns The result of the debounced function.\n */\n function debounced(this: ThisType, ...args: Arguments) {\n const shouldCallImmediately = leading && !timeout\n const shouldCallTrailing = trailing && !timeout\n\n if (timeout) {\n clearTimeout(timeout)\n }\n\n timeout = setTimeout(() => {\n timeout = null\n if (trailing && !shouldCallImmediately) {\n result = fn.apply(this, args)\n }\n }, wait)\n\n if (shouldCallImmediately) {\n result = fn.apply(this, args)\n } else if (!shouldCallTrailing) {\n result = null\n }\n\n return result as T\n }\n return debounced\n}\n","/**\n * Class for queuing promises to be executed one after the other.\n * This is useful for tasks that should not be executed in parallel.\n * @example\n * const queue = new PromiseQueue();\n * queue.add(() => fetch('https://example.com/api/endpoint1'));\n * queue.add(() => fetch('https://example.com/api/endpoint2'));\n * // The second fetch will only be executed after the first one is done.\n */\nexport default class PromiseQueue {\n private queue: (() => Promise<any>)[] = []\n private pendingPromise: boolean = false\n\n /**\n * Method to add a new promise to the queue and returns a promise that resolves when this task is done\n * @param task Function that returns a promise that will be added to the queue\n * @returns Promise that resolves when the task is done\n */\n add(task: () => Promise<any>): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n // Wrap the task with the resolve and reject to control its completion from the outside\n this.queue.push(() => task()\n .then(resolve)\n .catch((error: Error) => {\n reject(error)\n throw error\n }))\n this.dequeue()\n })\n }\n\n /**\n * Method to check if there is a pending promise in the queue\n * @returns True if there is a pending promise, false otherwise\n */\n public hasPendingPromise(): boolean {\n return this.pendingPromise\n }\n\n /**\n * Method to process the queue\n */\n private dequeue() {\n if (this.pendingPromise || this.queue.length === 0) {\n return\n }\n const task = this.queue.shift()\n if (!task) return\n this.pendingPromise = true\n task()\n .then(() => {\n this.pendingPromise = false\n this.dequeue()\n })\n .catch(() => {\n this.pendingPromise = false\n this.dequeue()\n })\n }\n}\n","import type { BaseItem } from '@signaldb/core'\nimport { isEqual } from '@signaldb/core'\n\n/**\n * Computes the modified fields between two items recursively.\n * @param oldItem The old item\n * @param newItem The new item\n * @returns The modified fields\n */\nexport function computeModifiedFields<T extends Record<string, any>>(\n oldItem: T,\n newItem: T,\n): string[] {\n const modifiedFields: string[] = []\n\n const oldKeys = Object.keys(oldItem)\n const newKeys = Object.keys(newItem)\n const allKeys = new Set([...oldKeys, ...newKeys])\n\n for (const key of allKeys) {\n if (newItem[key] !== oldItem[key]) {\n if (typeof newItem[key] === 'object' && typeof oldItem[key] === 'object' && newItem[key] != null && oldItem[key] != null) {\n const nestedModifiedFields = computeModifiedFields(oldItem[key], newItem[key])\n for (const nestedField of nestedModifiedFields) {\n modifiedFields.push(`${key}.${nestedField}`)\n }\n } else {\n modifiedFields.push(key)\n }\n }\n }\n\n return modifiedFields\n}\n\n/**\n * Compute changes between two arrays of items.\n * @param oldItems Array of the old items\n * @param newItems Array of the new items\n * @returns The changeset\n */\nexport default function computeChanges<ItemType extends BaseItem<IdType>, IdType>(\n oldItems: ItemType[],\n newItems: ItemType[],\n) {\n const added: ItemType[] = []\n const modified: ItemType[] = []\n const modifiedFields: Map<IdType, string[]> = new Map()\n const removed: ItemType[] = []\n\n const oldItemsMap = new Map(oldItems.map(item => [item.id, item]))\n const newItemsMap = new Map(newItems.map(item => [item.id, item]))\n\n for (const [id, oldItem] of oldItemsMap) {\n const newItem = newItemsMap.get(id)\n if (!newItem) {\n removed.push(oldItem)\n } else if (!isEqual(newItem, oldItem)) {\n modifiedFields.set(newItem.id, computeModifiedFields(oldItem, newItem))\n modified.push(newItem)\n }\n }\n\n for (const [id, newItem] of newItemsMap) {\n if (!oldItemsMap.has(id)) {\n added.push(newItem)\n }\n }\n\n return {\n added,\n modified,\n modifiedFields,\n removed,\n }\n}\n","import type { BaseItem } from '@signaldb/core'\nimport type { LoadResponse } from './types'\n\n/**\n * Gets the snapshot of items from the last snapshot and the changes.\n * @param lastSnapshot The last snapshot of items\n * @param data The changes to apply to the last snapshot\n * @returns The new snapshot of items\n */\nexport default function getSnapshot<ItemType extends BaseItem<IdType>, IdType>(\n lastSnapshot: ItemType[] | undefined,\n data: LoadResponse<ItemType>,\n) {\n if (data.items != null) return data.items\n\n // copy the array to not mutate the last snapshot\n const items = [...lastSnapshot || []]\n data.changes.added.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index === -1) {\n items.push(item)\n } else {\n items[index] = item\n }\n })\n data.changes.modified.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index === -1) {\n items.push(item)\n } else {\n items[index] = item\n }\n })\n data.changes.removed.forEach((item) => {\n const index = items.findIndex(i => i.id === item.id)\n if (index !== -1) items.splice(index, 1)\n })\n return items\n}\n","import type { BaseItem } from '@signaldb/core'\nimport { modify } from '@signaldb/core'\nimport type { Change } from './types'\n\n/**\n * applies changes to a collection of items\n * @param items The items to apply the changes to\n * @param changes The changes to apply to the items\n * @param [fallbackItems] Items that are used as a base for update changes that\n * target an item which isn't part of `items` anymore (e.g. because it was removed\n * remotely). Without them, the resulting item would only contain the fields of the\n * modifier and all other fields would be lost.\n * @returns The new items after applying the changes\n */\nexport default function applyChanges<ItemType extends BaseItem<IdType>, IdType>(\n items: ItemType[],\n changes: Change<ItemType, IdType>[],\n fallbackItems?: ItemType[],\n): ItemType[] {\n // Create initial map of items by ID\n const itemMap = new Map(items.map(item => [item.id, item]))\n const fallbackItemMap = fallbackItems == null\n ? undefined\n : new Map(fallbackItems.map(item => [item.id, item]))\n\n changes.forEach((change) => {\n if (change.type === 'remove') {\n itemMap.delete(change.data)\n } else if (change.type === 'insert') {\n const existingItem = itemMap.get(change.data.id)\n itemMap.set(\n change.data.id,\n existingItem ? { ...existingItem, ...change.data } : change.data,\n )\n } else { // change.type === 'update'\n // fall back to the last known state of the item to preserve all fields\n // that aren't part of the modifier\n const existingItem = itemMap.get(change.data.id)\n ?? fallbackItemMap?.get(change.data.id)\n itemMap.set(\n change.data.id,\n existingItem\n ? modify(existingItem, change.data.modifier)\n : modify({ id: change.data.id } as ItemType, change.data.modifier),\n )\n }\n })\n\n // Convert map back to array\n return [...itemMap.values()]\n}\n","import type { BaseItem, Modifier, Changeset } from '@signaldb/core'\nimport computeChanges from './computeChanges'\nimport type { Change, LoadResponse } from './types'\nimport getSnapshot from './getSnapshot'\nimport applyChanges from './applyChanges'\n\n/**\n * Checks if there are any changes in the given changeset.\n * @param changes The changeset to check.\n * @returns True if there are changes, false otherwise.\n */\nfunction hasChanges<T>(\n changes: Changeset<T>,\n) {\n return changes.added.length > 0\n || changes.modified.length > 0\n || changes.removed.length > 0\n}\n/**\n * Checks if there is a difference between the old items and the new items.\n * @param oldItems The old items.\n * @param newItems The new items.\n * @returns True if there is a difference, false otherwise.\n */\nfunction hasDifference<ItemType extends BaseItem<IdType>, IdType>(\n oldItems: ItemType[],\n newItems: ItemType[],\n) {\n return hasChanges(computeChanges(oldItems, newItems))\n}\n\ninterface Options<ItemType extends BaseItem<IdType>, IdType> {\n changes: Change<ItemType, IdType>[],\n lastSnapshot?: ItemType[],\n data: LoadResponse<ItemType>,\n pull: () => Promise<LoadResponse<ItemType>>,\n push: (changes: Changeset<ItemType> & {\n modifiedFields: Map<IdType, string[]>,\n }) => Promise<void>,\n insert: (item: ItemType) => Promise<void>,\n update: (id: IdType, modifier: Modifier<ItemType>) => Promise<void>,\n remove: (id: IdType) => Promise<void>,\n batch: (fn: () => Promise<void>) => Promise<void>,\n}\n\n/**\n * Does a sync operation based on the provided options. If changes are supplied, these will be rebased on the new data.\n * Afterwards the push method will be called with the remaining changes. A new snapshot will be created and returned.\n * @param options Sync options\n * @param options.changes Changes to call the push method with\n * @param [options.lastSnapshot] The last snapshot\n * @param options.data The new data\n * @param options.pull Method to pull new data\n * @param options.push Method to push changes\n * @param options.insert Method to insert an item\n * @param options.update Method to update an item\n * @param options.remove Method to remove an item\n * @param options.batch Method to batch multiple operations\n * @returns The new snapshot\n */\nexport default async function sync<ItemType extends BaseItem<IdType>, IdType>({\n changes,\n lastSnapshot,\n data,\n pull,\n push,\n insert,\n update,\n remove,\n batch,\n}: Options<ItemType, IdType>): Promise<ItemType[]> {\n let newData = data\n let previousSnapshot = lastSnapshot || []\n let newSnapshot = getSnapshot(lastSnapshot, newData)\n if (changes.length > 0) {\n // apply changes on last snapshot and check if there is a difference\n const lastSnapshotWithChanges = applyChanges(previousSnapshot, changes)\n if (hasDifference(previousSnapshot, lastSnapshotWithChanges)) {\n // if yes, apply the changes on the newSnapshot and check if there is a difference\n // pass the previous snapshot as fallback to preserve all fields of items that\n // were removed remotely, but were updated locally\n const newSnapshotWithChanges = applyChanges(newSnapshot, changes, previousSnapshot)\n const changesToPush = computeChanges<ItemType, IdType>(newSnapshot, newSnapshotWithChanges)\n if (hasChanges(changesToPush)) {\n // if yes, push the changes to the server\n await push(changesToPush)\n\n // pull new data afterwards to ensure that all server changes are applied\n newData = await pull()\n newSnapshot = getSnapshot(newSnapshot, newData)\n }\n previousSnapshot = lastSnapshotWithChanges\n }\n }\n\n // apply the new changes on the collection\n const newChanges = newData.changes == null\n ? computeChanges(previousSnapshot, newData.items)\n : newData.changes\n await batch(async () => {\n await Promise.all(newChanges.added.map(item => insert(item)))\n await Promise.all(newChanges.modified.map(item => update(item.id, { $set: item })))\n await Promise.all(newChanges.removed.map(item => remove(item.id)))\n })\n\n return newSnapshot\n}\n","import type {\n BaseItem,\n DataAdapter,\n ReactivityAdapter,\n Changeset,\n Modifier,\n Selector,\n} from '@signaldb/core'\nimport { DefaultDataAdapter, Collection, randomId } from '@signaldb/core'\nimport debounce from './utils/debounce'\nimport PromiseQueue from './utils/PromiseQueue'\nimport sync from './sync'\nimport type { Change, LoadResponse, Snapshot, SyncOperation } from './types'\n\ntype SyncOptions<T extends Record<string, any>> = {\n name: string,\n} & T\n\ntype CleanupFunction = (() => void | Promise<void>) | void\n\ninterface SyncListeners<ItemType extends BaseItem<IdType>, IdType = any> {\n added: (item: ItemType) => void,\n changed: (item: ItemType, modifier: Modifier<ItemType>) => void,\n removed: (item: ItemType) => void,\n}\n\ninterface Options<\n CollectionOptions extends Record<string, any>,\n ItemType extends BaseItem<IdType> = BaseItem,\n IdType = any,\n> {\n pull: (\n collectionOptions: SyncOptions<CollectionOptions>,\n pullParameters: {\n lastFinishedSyncStart?: number,\n lastFinishedSyncEnd?: number,\n },\n ) => Promise<LoadResponse<ItemType>>,\n push: (\n collectionOptions: SyncOptions<CollectionOptions>,\n pushParameters: {\n rawChanges: Omit<Change, 'id' | 'collectionName'>[],\n changes: Changeset<ItemType> & {\n modifiedFields: Map<IdType, string[]>,\n },\n },\n ) => Promise<void>,\n registerRemoteChange?: (\n collectionOptions: SyncOptions<CollectionOptions>,\n onChange: (data?: LoadResponse<ItemType>) => Promise<void>,\n ) => CleanupFunction | Promise<CleanupFunction>,\n\n id?: string,\n dataAdapter?: DataAdapter,\n reactivity?: ReactivityAdapter,\n onError?: (collectionOptions: SyncOptions<CollectionOptions>, error: Error) => void,\n\n autostart?: boolean,\n debounceTime?: number,\n}\n\n/**\n * Class to manage syncing of collections.\n * @template CollectionOptions\n * @template ItemType\n * @template IdType\n * @example\n * const syncManager = new SyncManager({\n * pull: async (collectionOptions) => {\n * const response = await fetch(`/api/collections/${collectionOptions.name}`)\n * return await response.json()\n * },\n * push: async (collectionOptions, { changes }) => {\n * await fetch(`/api/collections/${collectionOptions.name}`, {\n * method: 'POST',\n * body: JSON.stringify(changes),\n * })\n * },\n * })\n *\n * const collection = new Collection()\n * syncManager.addCollection(collection, {\n * name: 'todos',\n * })\n *\n * syncManager.sync('todos')\n */\nexport default class SyncManager<\n CollectionOptions extends Record<string, any>,\n ItemType extends BaseItem<IdType> = BaseItem,\n IdType = any,\n> {\n protected options: Options<CollectionOptions, ItemType, IdType>\n protected collections: Map<string, {\n collection: Collection<ItemType, IdType, any>,\n options: SyncOptions<CollectionOptions>,\n readyPromise: Promise<void>,\n syncPaused: boolean,\n cleanupFunction?: CleanupFunction,\n syncListeners?: SyncListeners<ItemType, IdType>,\n }> = new Map()\n\n protected changes: Collection<Change<ItemType>, string>\n protected snapshots: Collection<Snapshot<ItemType>, string>\n protected syncOperations: Collection<SyncOperation, string>\n protected scheduledPushes: Set<string> = new Set()\n protected remoteChanges: Omit<Change, 'id' | 'time'>[] = []\n protected syncQueues: Map<string, PromiseQueue> = new Map()\n protected collectionsReady: Promise<void>\n protected isDisposed = false\n protected instanceId = randomId()\n protected id: string\n protected debouncedFlush: () => void\n\n /**\n * @param options Collection options\n * @param options.pull Function to pull data from remote source.\n * @param options.push Function to push data to remote source.\n * @param [options.registerRemoteChange] Function to register a callback for remote changes.\n * @param [options.id] Unique identifier for this sync manager. Only nessesary if you have multiple sync managers.\n * @param [options.storageAdapter] Storage adapter to use for storing changes, snapshots and sync operations.\n * @param [options.reactivity] Reactivity adapter to use for reactivity.\n * @param [options.onError] Function to handle errors that occur async during syncing.\n * @param [options.autostart] Whether to automatically start syncing new collections.\n * @param [options.debounceTime] The time in milliseconds to debounce push operations.\n */\n constructor(options: Options<CollectionOptions, ItemType, IdType>) {\n this.options = {\n autostart: true,\n ...options,\n }\n this.id = this.options.id || 'default-sync-manager'\n const { reactivity } = this.options\n const dataAdapter = this.options.dataAdapter ?? new DefaultDataAdapter()\n this.changes = new Collection(`${this.options.id}-changes`, dataAdapter, {\n indices: ['collectionName'],\n reactivity,\n })\n this.snapshots = new Collection(`${this.options.id}-snapshots`, dataAdapter, {\n indices: ['collectionName'],\n reactivity,\n })\n this.syncOperations = new Collection(`${this.options.id}-sync-operations`, dataAdapter, {\n indices: ['collectionName', 'status'],\n reactivity,\n })\n\n const readiness = [\n Promise.resolve(this.syncOperations.isReady()),\n Promise.resolve(this.changes.isReady()),\n Promise.resolve(this.snapshots.isReady()),\n ]\n this.collectionsReady = Promise.all(readiness).then(() => { /* noop */ })\n\n this.changes.setMaxListeners(1000)\n this.snapshots.setMaxListeners(1000)\n this.syncOperations.setMaxListeners(1000)\n\n this.debouncedFlush = debounce(this.flushScheduledPushes, this.options.debounceTime ?? 100)\n }\n\n protected getSyncQueue(name: string) {\n if (this.syncQueues.get(name) == null) {\n this.syncQueues.set(name, new PromiseQueue())\n }\n return this.syncQueues.get(name) as PromiseQueue\n }\n\n /**\n * Removes the event listeners this sync manager attached to a registered collection.\n * The collection itself is left untouched, including listeners registered by the user.\n * @param name Name of the collection\n */\n protected detachSyncListeners(name: string) {\n const collectionParameters = this.collections.get(name)\n if (collectionParameters == null) return\n const { collection, syncListeners } = collectionParameters\n if (syncListeners == null) return\n collection.off('added', syncListeners.added)\n collection.off('changed', syncListeners.changed)\n collection.off('removed', syncListeners.removed)\n }\n\n /**\n * Removes a collection from the sync manager. Pauses the sync process for the collection\n * and detaches all event listeners the sync manager attached to it. The collection itself\n * won't be disposed and stays usable afterwards.\n * @param name Name of the collection\n */\n public async removeCollection(name: string) {\n if (!this.collections.has(name)) return\n await this.pauseSync(name)\n this.detachSyncListeners(name)\n this.collections.delete(name)\n this.syncQueues.delete(name)\n this.scheduledPushes.delete(name)\n }\n\n /**\n * Clears all internal data structures\n */\n public async dispose() {\n this.isDisposed = true\n // detach the listeners from the user provided collections before disposing the\n // internal collections. Otherwise the listeners would stay attached and reference\n // the disposed internal collections of this sync manager forever.\n for (const name of this.collections.keys()) {\n this.detachSyncListeners(name)\n }\n this.collections.clear()\n this.syncQueues.clear()\n this.scheduledPushes.clear()\n this.remoteChanges.splice(0)\n await Promise.all([\n this.changes.dispose(),\n this.snapshots.dispose(),\n this.syncOperations.dispose(),\n ])\n }\n\n /**\n * Gets a collection with it's options by name\n * @deprecated Use getCollectionProperties instead.\n * @param name Name of the collection\n * @throws {Error} Will throw an error if the name wasn't found\n * @returns Tuple of collection and options\n */\n public getCollection(name: string) {\n const { collection, options } = this.getCollectionProperties(name)\n return [collection, options]\n }\n\n /**\n * Gets collection options by name\n * @param name Name of the collection\n * @throws {Error} Will throw an error if the name wasn't found\n * @returns An object of all properties of the collection\n */\n public getCollectionProperties(name: string) {\n const collectionParameters = this.collections.get(name)\n if (collectionParameters == null) throw new Error(`Collection with id '${name}' not found`)\n return collectionParameters\n }\n\n /**\n * Adds a collection to the sync manager.\n * @param collection Collection to add\n * @param options Options for the collection. The object needs at least a `name` property.\n * @param options.name Unique name of the collection\n */\n public addCollection<\n CollectionItem extends ItemType = ItemType,\n >(\n collection: Collection<CollectionItem, IdType, any>,\n options: SyncOptions<CollectionOptions>,\n ) {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n\n // detach listeners of a previous registration under the same name,\n // otherwise every re-registration would add another set of listeners\n this.detachSyncListeners(options.name)\n\n const hasRemoteChange = (change: Omit<Change, 'id' | 'time'>) => {\n for (const remoteChange of this.remoteChanges) {\n if (remoteChange == null) continue\n if (remoteChange.collectionName !== change.collectionName) continue\n if (remoteChange.type !== change.type) continue\n\n if (change.type === 'remove' && remoteChange.data !== change.data) continue\n if (remoteChange.data.id !== change.data.id) continue\n return true\n }\n return false\n }\n const removeRemoteChanges = (collectionName: string, id: IdType) => {\n const newRemoteChanges: (Omit<Change, 'id' | 'time'> | null)[] = [...this.remoteChanges]\n for (let i = 0; i < newRemoteChanges.length; i += 1) {\n const item = newRemoteChanges[i]\n if (item == null) continue\n if (item.collectionName !== collectionName) continue\n if (item.type === 'remove' && item.data !== id) continue\n if (item.data.id !== id) continue\n newRemoteChanges[i] = null\n }\n this.remoteChanges = newRemoteChanges.filter(item => item != null)\n }\n\n const onAdded: SyncListeners<CollectionItem, IdType>['added'] = (item) => {\n if (this.isDisposed) return\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'insert', data: item })) {\n removeRemoteChanges(options.name, item.id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'insert',\n data: item,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n }\n const onChanged: SyncListeners<CollectionItem, IdType>['changed'] = ({ id }, modifier) => {\n if (this.isDisposed) return\n const data = { id, modifier }\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'update', data })) {\n removeRemoteChanges(options.name, id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'update',\n data,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n }\n const onRemoved: SyncListeners<CollectionItem, IdType>['removed'] = ({ id }) => {\n if (this.isDisposed) return\n // skip the change if it was a remote change\n if (hasRemoteChange({ collectionName: options.name, type: 'remove', data: id })) {\n removeRemoteChanges(options.name, id)\n return\n }\n this.changes.insert({\n collectionName: options.name,\n time: Date.now(),\n type: 'remove',\n data: id,\n }).then(() => {\n if (this.getCollectionProperties(options.name).syncPaused) return\n this.schedulePush(options.name)\n }).catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n }\n\n collection.on('added', onAdded)\n collection.on('changed', onChanged)\n collection.on('removed', onRemoved)\n\n const syncListeners = {\n added: onAdded,\n changed: onChanged,\n removed: onRemoved,\n } as unknown as SyncListeners<ItemType, IdType>\n\n this.collections.set(options.name, {\n collection: collection as unknown as Collection<ItemType, IdType, any>,\n options,\n readyPromise: collection.ready(),\n syncPaused: true, // always start paused as the autostart will start it\n syncListeners,\n })\n\n if (this.options.autostart) {\n this.startSync(options.name)\n .catch((error: Error) => {\n if (!this.options.onError) return\n this.options.onError(this.getCollectionProperties(options.name).options, error)\n })\n }\n }\n\n protected flushScheduledPushes() {\n this.scheduledPushes.forEach((name) => {\n this.pushChanges(name).catch(() => { /* error handler is called in sync */ })\n })\n this.scheduledPushes.clear()\n }\n\n protected schedulePush(name: string) {\n this.scheduledPushes.add(name)\n this.debouncedFlush()\n }\n\n /**\n * Setup all collections to be synced with remote changes\n * and enable automatic pushing changes to the remote source.\n */\n public async startAll() {\n await Promise.all([...this.collections.keys()].map(id =>\n this.startSync(id)))\n }\n\n /**\n * Setup a collection to be synced with remote changes\n * and enable automatic pushing changes to the remote source.\n * @param name Name of the collection\n */\n public async startSync(name: string) {\n const collectionParameters = this.getCollectionProperties(name)\n if (!collectionParameters.syncPaused) return // already started\n\n this.schedulePush(name) // push changes that were made while paused\n\n const cleanupFunction = this.options.registerRemoteChange\n ? await this.options.registerRemoteChange(\n collectionParameters.options,\n async (data) => {\n if (this.isDisposed) return\n // eslint-disable-next-line unicorn/prefer-ternary -- this is easier to read than a ternary operator\n if (data == null) {\n // if no data is provided, we will sync the collection with the remote source\n await this.sync(name)\n } else {\n // if data is provided, we will sync the collection with the provided data\n await this.getSyncQueue(name).add(async () => {\n const syncTime = Date.now()\n const syncId = await this.syncOperations.insert({\n start: syncTime,\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n })\n await this.syncWithData(name, data)\n .then(async () => {\n if (this.isDisposed) return\n // clean up old sync operations\n await this.syncOperations.removeMany({\n id: { $ne: syncId },\n collectionName: name,\n $or: [\n { end: { $lte: syncTime } },\n { status: 'active' },\n ],\n })\n\n // update sync operation status to done after everthing was finished\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'done', end: Date.now() },\n })\n })\n .catch(async (error: Error) => {\n if (this.options.onError) {\n this.options.onError(this.getCollectionProperties(name).options, error)\n }\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'error', end: Date.now(), error: error.stack || error.message },\n })\n throw error\n })\n })\n }\n },\n )\n : undefined\n this.collections.set(name, {\n ...collectionParameters,\n syncPaused: false,\n cleanupFunction,\n })\n }\n\n /**\n * Pauses the sync process for all collections.\n * This means that the collections will not be synced with remote changes\n * and changes will not automatically be pushed to the remote source.\n */\n public async pauseAll() {\n await Promise.all([...this.collections.keys()].map(id =>\n this.pauseSync(id)))\n }\n\n /**\n * Pauses the sync process for a collection.\n * This means that the collection will not be synced with remote changes\n * and changes will not automatically be pushed to the remote source.\n * @param name Name of the collection\n */\n public async pauseSync(name: string) {\n const collectionParameters = this.getCollectionProperties(name)\n if (collectionParameters.syncPaused) return // already paused\n if (collectionParameters.cleanupFunction) await collectionParameters.cleanupFunction()\n this.collections.set(name, {\n ...collectionParameters,\n cleanupFunction: undefined,\n syncPaused: true,\n })\n }\n\n /**\n * Starts the sync process for all collections\n */\n public async syncAll() {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n const errors: { id: string, error: Error }[] = []\n await Promise.all([...this.collections.keys()].map(id =>\n this.sync(id).catch((error: Error) => {\n errors.push({ id, error })\n })))\n if (errors.length > 0) throw new Error(`Error while syncing collections:\\n${errors.map(error => `${error.id}: ${error.error.message}`).join('\\n\\n')}`)\n }\n\n /**\n * Checks if a collection is currently beeing synced\n * @param [name] Name of the collection. If not provided, it will check if any collection is currently beeing synced.\n * @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.\n * @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.\n */\n public isSyncing(\n name: string | undefined,\n async: true,\n ): Promise<boolean>\n\n public isSyncing(\n name?: string,\n async?: false,\n ): boolean\n\n public isSyncing<Async extends boolean = false>(\n name?: string,\n async?: Async,\n ): Promise<boolean> | boolean {\n const itemOrPromise = this.syncOperations.findOne({\n ...name ? { collectionName: name } : {},\n status: 'active',\n }, { fields: { status: 1 }, async })\n if (itemOrPromise instanceof Promise) {\n return itemOrPromise\n .then(item => item != null)\n }\n return (itemOrPromise != null)\n }\n\n /**\n * Checks if the sync manager is ready to sync.\n * @returns A promise that resolves when the sync manager is ready to sync.\n */\n public async isReady() {\n await this.collectionsReady\n }\n\n /**\n * Starts the sync process for a collection\n * @param name Name of the collection\n * @param options Options for the sync process.\n * @param options.force If true, the sync process will be started even if there are no changes and onlyWithChanges is true.\n * @param options.onlyWithChanges If true, the sync process will only be started if there are changes.\n */\n public async sync(name: string, options: { force?: boolean, onlyWithChanges?: boolean } = {}) {\n if (this.isDisposed) throw new Error('SyncManager is disposed')\n await this.isReady()\n const { options: collectionOptions, readyPromise } = this.getCollectionProperties(name)\n await readyPromise\n\n const hasActiveSyncs = await this.syncOperations.find({\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n }, {\n reactive: false,\n async: true,\n }).count() > 0\n const syncTime = Date.now()\n let syncId: string | null = null\n\n // schedule for next tick to allow other tasks to run first\n await new Promise((resolve) => {\n setTimeout(resolve, 0)\n })\n const doSync = async () => {\n const lastFinishedSync = await this.syncOperations.findOne({\n collectionName: name,\n status: 'done',\n }, {\n sort: { end: -1 },\n reactive: false,\n async: true,\n })\n if (options?.onlyWithChanges) {\n const currentChanges = await this.changes.find({\n collectionName: name,\n time: { $lte: syncTime },\n }, {\n sort: { time: 1 },\n reactive: false,\n async: true,\n }).count()\n if (currentChanges === 0) return\n }\n\n if (!hasActiveSyncs) {\n syncId = await this.syncOperations.insert({\n start: syncTime,\n collectionName: name,\n instanceId: this.instanceId,\n status: 'active',\n })\n }\n const data = await this.options.pull(collectionOptions, {\n lastFinishedSyncStart: lastFinishedSync?.start,\n lastFinishedSyncEnd: lastFinishedSync?.end,\n })\n\n await this.syncWithData(name, data)\n }\n\n await (options?.force ? doSync() : this.getSyncQueue(name).add(doSync))\n .catch(async (error: Error) => {\n if (syncId != null) {\n if (this.options.onError) this.options.onError(collectionOptions, error)\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'error', end: Date.now(), error: error.stack || error.message },\n })\n }\n throw error\n })\n\n if (syncId != null) {\n // clean up old sync operations\n await this.syncOperations.removeMany({\n id: { $ne: syncId },\n collectionName: name,\n $or: [\n { end: { $lte: syncTime } },\n { status: 'active' },\n ],\n })\n\n // update sync operation status to done after everthing was finished\n await this.syncOperations.updateOne({ id: syncId }, {\n $set: { status: 'done', end: Date.now() },\n })\n }\n }\n\n /**\n * Starts the push process for a collection (sync process but only if there are changes)\n * @param name Name of the collection\n */\n public async pushChanges(name: string) {\n await this.sync(name, {\n onlyWithChanges: true,\n })\n }\n\n protected async syncWithData(\n name: string,\n data: LoadResponse<ItemType>,\n ) {\n if (this.isDisposed) return\n const { collection, options: collectionOptions } = this.getCollectionProperties(name)\n\n const syncTime = Date.now()\n\n const lastFinishedSync = await this.syncOperations.findOne({\n collectionName: name,\n status: 'done',\n }, {\n sort: { end: -1 },\n reactive: false,\n async: true,\n })\n const lastSnapshot = await this.snapshots.findOne({\n collectionName: name,\n } as Selector<Snapshot<any>>, {\n sort: { time: -1 },\n reactive: false,\n async: true,\n })\n const currentChanges = await this.changes.find({\n collectionName: name,\n time: { $lte: syncTime },\n }, {\n sort: { time: 1 },\n reactive: false,\n async: true,\n }).fetch()\n\n await sync<ItemType, ItemType['id']>({\n changes: currentChanges,\n lastSnapshot: lastSnapshot?.items,\n data,\n pull: () => this.options.pull(collectionOptions, {\n lastFinishedSyncStart: lastFinishedSync?.start,\n lastFinishedSyncEnd: lastFinishedSync?.end,\n }),\n push: changes => this.options.push(collectionOptions, {\n changes,\n rawChanges: currentChanges,\n }),\n insert: async (item) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: item,\n }, {\n collectionName: name,\n type: 'update',\n data: { id: item.id, modifier: { $set: item } },\n })\n\n // replace the item\n await collection.replaceOne({ id: item.id } as Selector<any>, item, { upsert: true })\n },\n update: async (itemId, modifier) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: { id: itemId, ...modifier.$set },\n }, {\n collectionName: name,\n type: 'update',\n data: { id: itemId, modifier },\n })\n\n await collection.updateOne({ id: itemId } as Selector<any>, {\n ...modifier,\n $setOnInsert: { id: itemId } as Partial<ItemType>,\n }, { upsert: true })\n },\n remove: async (itemId) => {\n const itemExists = await collection.find({\n id: itemId,\n } as Selector<any>, { reactive: false, async: true }).count() > 0\n if (!itemExists) return\n this.remoteChanges.push({\n collectionName: name,\n type: 'remove',\n data: itemId,\n })\n await collection.removeOne({ id: itemId } as Selector<any>)\n },\n batch: async (fn) => {\n return collection.batch(async () => {\n await fn()\n })\n },\n })\n .then(async (snapshot) => {\n if (this.isDisposed) return\n\n // clean up old snapshots\n await this.snapshots.removeMany({\n collectionName: name,\n time: { $lte: syncTime },\n } as Selector<any>)\n\n // clean up processed changes\n await this.changes.removeMany({\n collectionName: name,\n id: { $in: currentChanges.map(c => c.id) },\n })\n\n // insert new snapshot\n await this.snapshots.insert({\n time: syncTime,\n collectionName: name,\n items: snapshot,\n })\n\n // delay sync operation update to next tick to allow other tasks to run first\n await new Promise((resolve) => {\n setTimeout(resolve, 0)\n })\n\n const hasChanges = await this.changes.find({\n collectionName: name,\n }, { reactive: false, async: true }).count() > 0\n\n if (hasChanges) {\n // check if there are unsynced changes to push\n // and sync again if there are any\n await this.sync(name, {\n force: true,\n onlyWithChanges: true,\n })\n return\n }\n\n // if there are no unsynced changes apply the last snapshot\n // to make sure that collection and snapshot are in sync\n\n // find all items that are not in the snapshot\n const nonExistingItemIds = await collection.find({\n id: { $nin: snapshot.map(item => item.id) },\n } as Selector<any>, {\n reactive: false,\n async: true,\n }).map(item => item.id) as IdType[]\n\n await collection.batch(async () => {\n // update all items that are in the snapshot\n await Promise.all(snapshot.map(async (item) => {\n // add multiple remote changes as we don't know if the item will be updated or inserted during replace\n this.remoteChanges.push({\n collectionName: name,\n type: 'insert',\n data: item,\n }, {\n collectionName: name,\n type: 'update',\n data: { id: item.id, modifier: { $set: item } },\n })\n\n // replace the item\n await collection.replaceOne({ id: item.id } as Selector<any>, item, { upsert: true })\n }))\n\n // remove all items that are not in the snapshot\n await Promise.all(nonExistingItemIds.map(async (id) => {\n await collection.removeOne({ id } as Selector<any>)\n }))\n })\n })\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaldb/sync",
3
- "version": "2.0.0-beta.20",
3
+ "version": "2.0.0-beta.22",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "build": "rimraf dist && vite build",
@@ -54,6 +54,6 @@
54
54
  "dist"
55
55
  ],
56
56
  "peerDependencies": {
57
- "@signaldb/core": "^2.0.0-beta.20"
57
+ "@signaldb/core": "^2.0.0-beta.22"
58
58
  }
59
59
  }