@signaldb/core 1.7.1 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/.vite/manifest.json +138 -82
  2. package/dist/AsyncDataAdapter.d.ts +64 -0
  3. package/dist/AutoFetchDataAdapter.d.ts +112 -0
  4. package/dist/Collection/Cursor.d.ts +9 -9
  5. package/dist/Collection/Observer.d.ts +3 -2
  6. package/dist/Collection/index.d.ts +54 -46
  7. package/dist/Collection/types.d.ts +4 -9
  8. package/dist/DataAdapter.d.ts +34 -0
  9. package/dist/DefaultDataAdapter.d.ts +33 -0
  10. package/dist/WorkerDataAdapter.d.ts +25 -0
  11. package/dist/WorkerDataAdapterHost.d.ts +62 -0
  12. package/dist/createIndex.d.ts +7 -0
  13. package/dist/createStorageAdapter.d.ts +9 -0
  14. package/dist/{Collection/getIndexInfo.d.ts → getIndexInfo.d.ts} +17 -19
  15. package/dist/index.cjs.js +21 -17
  16. package/dist/index.cjs10.js +15 -26
  17. package/dist/index.cjs11.js +13 -10
  18. package/dist/index.cjs12.js +348 -3
  19. package/dist/index.cjs13.js +396 -110
  20. package/dist/index.cjs14.js +178 -68
  21. package/dist/index.cjs15.js +384 -8
  22. package/dist/index.cjs16.js +572 -20
  23. package/dist/index.cjs17.js +12 -4
  24. package/dist/index.cjs18.js +24 -5
  25. package/dist/index.cjs2.js +29 -32
  26. package/dist/index.cjs20.js +5 -13
  27. package/dist/index.cjs21.js +99 -21
  28. package/dist/index.cjs22.js +108 -58
  29. package/dist/index.cjs23.js +5 -82
  30. package/dist/index.cjs24.js +23 -14
  31. package/dist/index.cjs25.js +8 -27
  32. package/dist/index.cjs26.js +27 -7
  33. package/dist/index.cjs27.js +44 -5
  34. package/dist/index.cjs28.js +6 -27
  35. package/dist/index.cjs29.js +7 -40
  36. package/dist/index.cjs3.js +198 -444
  37. package/dist/index.cjs30.js +5 -0
  38. package/dist/index.cjs31.js +7 -0
  39. package/dist/index.cjs32.js +29 -0
  40. package/dist/index.cjs33.js +42 -0
  41. package/dist/index.cjs4.js +3 -165
  42. package/dist/index.cjs5.js +3 -66
  43. package/dist/index.cjs6.js +27 -3
  44. package/dist/index.cjs7.js +10 -3
  45. package/dist/index.cjs8.js +3 -3
  46. package/dist/index.cjs9.js +131 -3
  47. package/dist/index.d.ts +11 -8
  48. package/dist/index.mjs +15 -11
  49. package/dist/index10.mjs +15 -26
  50. package/dist/index11.mjs +13 -10
  51. package/dist/index12.mjs +348 -3
  52. package/dist/index13.mjs +396 -110
  53. package/dist/index14.mjs +178 -67
  54. package/dist/index15.mjs +384 -8
  55. package/dist/index16.mjs +572 -20
  56. package/dist/index17.mjs +12 -4
  57. package/dist/index18.mjs +24 -5
  58. package/dist/index2.mjs +29 -32
  59. package/dist/index20.mjs +5 -13
  60. package/dist/index21.mjs +99 -21
  61. package/dist/index22.mjs +108 -58
  62. package/dist/index23.mjs +5 -81
  63. package/dist/index24.mjs +23 -14
  64. package/dist/index25.mjs +8 -27
  65. package/dist/index26.mjs +27 -7
  66. package/dist/index27.mjs +44 -5
  67. package/dist/index28.mjs +6 -27
  68. package/dist/index29.mjs +7 -40
  69. package/dist/index3.mjs +198 -445
  70. package/dist/index30.mjs +6 -0
  71. package/dist/index31.mjs +8 -0
  72. package/dist/index32.mjs +30 -0
  73. package/dist/index33.mjs +43 -0
  74. package/dist/index4.mjs +3 -165
  75. package/dist/index5.mjs +3 -65
  76. package/dist/index6.mjs +27 -3
  77. package/dist/index7.mjs +10 -3
  78. package/dist/index8.mjs +3 -3
  79. package/dist/index9.mjs +131 -3
  80. package/dist/types/IndexProvider.d.ts +12 -7
  81. package/dist/types/StorageAdapter.d.ts +20 -0
  82. package/dist/utils/batchOnNextTick.d.ts +16 -0
  83. package/dist/utils/objectId.d.ts +8 -0
  84. package/dist/utils/queryId.d.ts +9 -0
  85. package/package.json +1 -1
  86. package/dist/AutoFetchCollection.d.ts +0 -60
  87. package/dist/Collection/createIndex.d.ts +0 -15
  88. package/dist/ReplicatedCollection.d.ts +0 -60
  89. package/dist/createMemoryAdapter.d.ts +0 -7
  90. package/dist/persistence/combinePersistenceAdapters.d.ts +0 -32
  91. package/dist/persistence/createPersistenceAdapter.d.ts +0 -9
  92. package/dist/types/MemoryAdapter.d.ts +0 -15
  93. package/dist/types/PersistenceAdapter.d.ts +0 -20
package/dist/index3.mjs CHANGED
@@ -1,37 +1,11 @@
1
- import EventEmitter from "./index13.mjs";
2
- import match from "./index18.mjs";
3
- import modify from "./index11.mjs";
4
- import isEqual from "./index10.mjs";
5
- import randomId from "./index12.mjs";
1
+ import EventEmitter from "./index9.mjs";
2
+ import createSignal from "./index18.mjs";
3
+ import randomId from "./index8.mjs";
4
+ import DefaultDataAdapter from "./index12.mjs";
5
+ import modify from "./index7.mjs";
6
6
  import deepClone from "./index19.mjs";
7
- import serializeValue from "./index20.mjs";
8
- import createSignal from "./index21.mjs";
7
+ import queryId from "./index20.mjs";
9
8
  import Cursor from "./index2.mjs";
10
- import getIndexInfo from "./index22.mjs";
11
- import { createExternalIndex } from "./index14.mjs";
12
- import { default as default2 } from "./index14.mjs";
13
- function hasPendingUpdates(pendingUpdates) {
14
- return pendingUpdates.added.length > 0 || pendingUpdates.modified.length > 0 || pendingUpdates.removed.length > 0;
15
- }
16
- function applyUpdates(currentItems, { added, modified, removed }) {
17
- const items = [...currentItems];
18
- added.forEach((item) => {
19
- items.push(item);
20
- });
21
- modified.forEach((item) => {
22
- const index = items.findIndex(({ id }) => id === item.id);
23
- if (index === -1)
24
- return;
25
- items[index] = item;
26
- });
27
- removed.forEach((item) => {
28
- const index = items.findIndex(({ id }) => id === item.id);
29
- if (index === -1)
30
- return;
31
- items.splice(index, 1);
32
- });
33
- return items;
34
- }
35
9
  class Collection extends EventEmitter {
36
10
  static collections = [];
37
11
  static debugMode = false;
@@ -67,210 +41,59 @@ class Collection extends EventEmitter {
67
41
  collection.setFieldTracking(enable);
68
42
  });
69
43
  };
70
- /**
71
- * Executes a batch operation, allowing multiple modifications to the collection
72
- * while deferring index rebuilding until all operations in the batch are completed.
73
- * This improves performance by avoiding repetitive index recalculations and
74
- * provides atomicity for the batch of operations.
75
- * @param callback - The batch operation to execute.
76
- */
77
44
  static batch(callback) {
78
45
  Collection.batchOperationInProgress = true;
79
- Collection.collections.reduce((memo, collection) => () => collection.batch(() => memo()), callback)();
80
- Collection.batchOperationInProgress = false;
46
+ const execute = () => Collection.collections.reduce((memo, collection) => () => {
47
+ return collection.batch(memo);
48
+ }, callback)();
49
+ const maybePromise = execute();
50
+ const afterBatch = () => {
51
+ Collection.batchOperationInProgress = false;
52
+ };
53
+ if (maybePromise && typeof maybePromise.then === "function") {
54
+ return maybePromise.then(() => afterBatch());
55
+ } else {
56
+ afterBatch();
57
+ }
81
58
  }
82
59
  name;
60
+ backend;
83
61
  options;
84
- persistenceAdapter = null;
85
62
  isPullingSignal;
86
63
  isPushingSignal;
87
- indexProviders = [];
88
- indicesOutdated = false;
89
- idIndex = /* @__PURE__ */ new Map();
64
+ readySignal;
90
65
  debugMode;
91
66
  batchOperationInProgress = false;
92
67
  isDisposed = false;
93
68
  postBatchCallbacks = /* @__PURE__ */ new Set();
94
69
  fieldTracking = false;
95
- persistenceReadyPromise;
96
- /**
97
- * Initializes a new instance of the `Collection` class with optional configuration.
98
- * Sets up memory, persistence, reactivity, and indices as specified in the options.
99
- * @template T - The type of the items stored in the collection.
100
- * @template I - The type of the unique identifier for the items.
101
- * @template U - The transformed item type after applying transformations (default is T).
102
- * @param options - Optional configuration for the collection.
103
- * @param options.name - An optional name for the collection.
104
- * @param options.memory - The in-memory adapter for storing items.
105
- * @param options.reactivity - The reactivity adapter for observing changes in the collection.
106
- * @param options.transform - A transformation function to apply to items when retrieving them.
107
- * @param options.persistence - The persistence adapter for saving and loading items.
108
- * @param options.indices - An array of index providers for optimized querying.
109
- * @param options.enableDebugMode - A boolean to enable or disable debug mode.
110
- * @param options.fieldTracking - A boolean to enable or disable field tracking by default.
111
- */
112
- constructor(options) {
70
+ queryListenersMap = /* @__PURE__ */ new Map();
71
+ constructor(nameOrOptions, maybeDataAdapter, maybeOptions) {
113
72
  super();
73
+ const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name || `${this.constructor.name}-${randomId()}`;
74
+ const options = typeof nameOrOptions === "string" ? maybeOptions || {} : nameOrOptions || {};
75
+ const persistence = options.persistence;
76
+ const dataAdapter = maybeDataAdapter || new DefaultDataAdapter({
77
+ ...persistence ? { storage: () => persistence } : {}
78
+ });
114
79
  Collection.collections.push(this);
115
- this.name = options?.name ?? `${this.constructor.name}-${randomId()}`;
116
- this.options = {
117
- memory: [],
118
- ...options
119
- };
80
+ this.name = name;
81
+ this.options = { ...options };
120
82
  this.fieldTracking = this.options.fieldTracking ?? Collection.fieldTracking;
121
83
  this.debugMode = this.options.enableDebugMode ?? Collection.debugMode;
122
- this.indexProviders = [
123
- createExternalIndex("id", this.idIndex),
124
- ...this.options.indices || []
125
- ];
126
- this.rebuildIndices();
127
- this.isPullingSignal = createSignal(this.options.reactivity, !!options?.persistence);
84
+ this.isPullingSignal = createSignal(this.options.reactivity, false);
128
85
  this.isPushingSignal = createSignal(this.options.reactivity, false);
129
- this.on("persistence.pullStarted", () => {
130
- this.isPullingSignal.set(true);
131
- });
132
- this.on("persistence.pullCompleted", () => {
133
- this.isPullingSignal.set(false);
134
- });
135
- this.on("persistence.pushStarted", () => {
136
- this.isPushingSignal.set(true);
137
- });
138
- this.on("persistence.pushCompleted", () => {
139
- this.isPushingSignal.set(false);
140
- });
141
- this.persistenceAdapter = this.options.persistence ?? null;
142
- if (this.persistenceAdapter) {
143
- let ongoingSaves = 0;
144
- let isInitialized = false;
145
- const pendingUpdates = { added: [], modified: [], removed: [] };
146
- const loadPersistentData = async (data) => {
147
- if (!this.persistenceAdapter)
148
- throw new Error("Persistence adapter not found");
149
- this.emit("persistence.pullStarted");
150
- const { items, changes } = data ?? await this.persistenceAdapter.load();
151
- if (items) {
152
- if (ongoingSaves > 0)
153
- return;
154
- this.memory().splice(0, this.memoryArray().length, ...items);
155
- this.idIndex.clear();
156
- this.memory().map((item, index) => {
157
- this.idIndex.set(serializeValue(item.id), /* @__PURE__ */ new Set([index]));
158
- });
159
- } else if (changes) {
160
- changes.added.forEach((item) => {
161
- const index = this.memory().findIndex((document) => document.id === item.id);
162
- if (index !== -1) {
163
- this.memory().splice(index, 1, item);
164
- return;
165
- }
166
- this.memory().push(item);
167
- const itemIndex = this.memory().findIndex((document) => document === item);
168
- this.idIndex.set(serializeValue(item.id), /* @__PURE__ */ new Set([itemIndex]));
169
- });
170
- changes.modified.forEach((item) => {
171
- const index = this.memory().findIndex((document) => document.id === item.id);
172
- if (index === -1)
173
- throw new Error("Cannot resolve index for item");
174
- this.memory().splice(index, 1, item);
175
- });
176
- changes.removed.forEach((item) => {
177
- const index = this.memory().findIndex((document) => document.id === item.id);
178
- if (index === -1)
179
- throw new Error("Cannot resolve index for item");
180
- this.memory().splice(index, 1);
181
- });
182
- }
183
- this.rebuildIndices();
184
- this.emit("persistence.received");
185
- setTimeout(() => this.emit("persistence.pullCompleted"), 0);
186
- };
187
- const saveQueue = {
188
- added: [],
189
- modified: [],
190
- removed: []
191
- };
192
- let isFlushing = false;
193
- const flushQueue = () => {
194
- if (!this.persistenceAdapter)
195
- throw new Error("Persistence adapter not found");
196
- if (ongoingSaves <= 0)
197
- this.emit("persistence.pushStarted");
198
- if (isFlushing)
199
- return;
200
- if (!hasPendingUpdates(saveQueue))
201
- return;
202
- isFlushing = true;
203
- ongoingSaves += 1;
204
- const currentItems = this.memoryArray();
205
- const changes = { ...saveQueue };
206
- saveQueue.added = [];
207
- saveQueue.modified = [];
208
- saveQueue.removed = [];
209
- this.persistenceAdapter.save(currentItems, changes).then(() => {
210
- this.emit("persistence.transmitted");
211
- }).catch((error) => {
212
- this.emit("persistence.error", error instanceof Error ? error : new Error(error));
213
- }).finally(() => {
214
- ongoingSaves -= 1;
215
- isFlushing = false;
216
- flushQueue();
217
- if (ongoingSaves <= 0)
218
- this.emit("persistence.pushCompleted");
219
- });
220
- };
221
- this.on("added", (item) => {
222
- if (!isInitialized) {
223
- pendingUpdates.added.push(item);
224
- return;
225
- }
226
- saveQueue.added.push(item);
227
- flushQueue();
228
- });
229
- this.on("changed", (item) => {
230
- if (!isInitialized) {
231
- pendingUpdates.modified.push(item);
232
- return;
233
- }
234
- saveQueue.modified.push(item);
235
- flushQueue();
236
- });
237
- this.on("removed", (item) => {
238
- if (!isInitialized) {
239
- pendingUpdates.removed.push(item);
240
- return;
241
- }
242
- saveQueue.removed.push(item);
243
- flushQueue();
244
- });
245
- this.persistenceAdapter.register((data) => loadPersistentData(data)).then(async () => {
246
- if (!this.persistenceAdapter)
247
- throw new Error("Persistence adapter not found");
248
- let currentItems = this.memoryArray();
249
- await loadPersistentData();
250
- while (hasPendingUpdates(pendingUpdates)) {
251
- const added = pendingUpdates.added.splice(0);
252
- const modified = pendingUpdates.modified.splice(0);
253
- const removed = pendingUpdates.removed.splice(0);
254
- currentItems = applyUpdates(this.memoryArray(), { added, modified, removed });
255
- await this.persistenceAdapter.save(currentItems, { added, modified, removed }).then(() => {
256
- this.emit("persistence.transmitted");
257
- });
258
- }
259
- await loadPersistentData();
260
- isInitialized = true;
261
- setTimeout(() => this.emit("persistence.init"), 0);
262
- }).catch((error) => {
263
- this.emit("persistence.error", error instanceof Error ? error : new Error(error));
264
- });
265
- }
266
- this.persistenceReadyPromise = new Promise((resolve, reject) => {
267
- if (!this.persistenceAdapter)
268
- return resolve();
269
- this.once("persistence.init", resolve);
270
- this.once("persistence.error", reject);
86
+ this.readySignal = createSignal(this.options.reactivity, false);
87
+ this.backend = dataAdapter.createCollectionBackend(this, this.options.indices ?? []);
88
+ void this.backend.isReady().then(() => {
89
+ this.readySignal.set(true);
90
+ }).catch(() => {
271
91
  });
272
92
  Collection.onCreationCallbacks.forEach((callback) => callback(this));
273
93
  }
94
+ isBatchOperationInProgress() {
95
+ return Collection.batchOperationInProgress || this.batchOperationInProgress;
96
+ }
274
97
  /**
275
98
  * Checks whether the collection is currently performing a pull operation
276
99
  * ⚡️ this function is reactive!
@@ -335,17 +158,28 @@ class Collection extends EventEmitter {
335
158
  *
336
159
  * collection.insert({ name: 'Item 1' })
337
160
  */
338
- async isReady() {
339
- return this.persistenceReadyPromise;
161
+ async ready() {
162
+ return this.backend.isReady();
163
+ }
164
+ /**
165
+ * Checks if the collection is ready.
166
+ * ⚡️ this function is reactive!
167
+ * @returns A boolean indicating whether the collection is ready.
168
+ */
169
+ isReady() {
170
+ return this.readySignal.get() ?? false;
340
171
  }
341
172
  profile(fn, measureFunction) {
342
173
  if (!this.debugMode)
343
174
  return fn();
344
175
  const startTime = performance.now();
345
- const result = fn();
346
- const endTime = performance.now();
347
- measureFunction(endTime - startTime);
348
- return result;
176
+ const handleProfileEnd = (result) => {
177
+ const endTime = performance.now();
178
+ measureFunction(endTime - startTime);
179
+ return result;
180
+ };
181
+ const maybePromise = fn();
182
+ return maybePromise instanceof Promise ? maybePromise.then(handleProfileEnd) : handleProfileEnd(maybePromise);
349
183
  }
350
184
  executeInDebugMode(fn) {
351
185
  if (!this.debugMode)
@@ -353,114 +187,53 @@ class Collection extends EventEmitter {
353
187
  const callstack = new Error().stack || "";
354
188
  fn(callstack);
355
189
  }
356
- rebuildIndices() {
357
- this.indicesOutdated = true;
358
- if (this.batchOperationInProgress)
359
- return;
360
- this.rebuildAllIndices();
361
- }
362
- rebuildAllIndices() {
363
- this.idIndex.clear();
364
- this.memory().map((item, index) => {
365
- this.idIndex.set(serializeValue(item.id), /* @__PURE__ */ new Set([index]));
366
- });
367
- this.indexProviders.forEach((index) => index.rebuild(this.memoryArray()));
368
- this.indicesOutdated = false;
369
- }
370
- getIndexInfo(selector) {
371
- if (selector != null && Object.keys(selector).length === 1 && "id" in selector && typeof selector.id !== "object") {
372
- return {
373
- matched: true,
374
- positions: [...this.idIndex.get(serializeValue(selector.id)) || []],
375
- optimizedSelector: {}
376
- };
377
- }
378
- if (selector == null) {
379
- return {
380
- matched: false,
381
- positions: [],
382
- optimizedSelector: {}
383
- };
384
- }
385
- if (this.indicesOutdated) {
386
- return {
387
- matched: false,
388
- positions: [],
389
- optimizedSelector: selector
390
- };
391
- }
392
- return getIndexInfo(this.indexProviders, selector);
393
- }
394
- getItemAndIndex(selector) {
395
- const memory = this.memoryArray();
396
- const indexInfo = this.getIndexInfo(selector);
397
- const items = indexInfo.matched ? indexInfo.positions.map((index2) => memory[index2]) : memory;
398
- const item = items.find((document) => match(document, selector));
399
- const foundInIndex = indexInfo.matched && indexInfo.positions.find((itemIndex) => memory[itemIndex] === item);
400
- const index = foundInIndex || memory.findIndex((document) => document === item);
401
- if (item == null)
402
- return { item: null, index: -1 };
403
- if (index === -1)
404
- throw new Error("Cannot resolve index for item");
405
- return { item, index };
406
- }
407
- deleteFromIdIndex(id, index) {
408
- this.idIndex.delete(serializeValue(id));
409
- if (!this.batchOperationInProgress)
410
- return;
411
- this.idIndex.forEach(([currenIndex], key) => {
412
- if (currenIndex > index) {
413
- this.idIndex.set(key, /* @__PURE__ */ new Set([currenIndex - 1]));
414
- }
415
- });
416
- }
417
- memory() {
418
- return this.options.memory;
419
- }
420
- memoryArray() {
421
- return this.memory().map((item) => item);
422
- }
423
190
  transform(item) {
424
191
  if (!this.options.transform)
425
192
  return item;
426
193
  return this.options.transform(item);
427
194
  }
428
- getItems(selector) {
195
+ getItem(selector, options) {
196
+ const itemsOrPromise = this.getItems(selector, { ...options, limit: 1 });
197
+ if (itemsOrPromise instanceof Promise) {
198
+ return itemsOrPromise.then((items) => {
199
+ return items[0] || void 0;
200
+ });
201
+ }
202
+ return itemsOrPromise[0];
203
+ }
204
+ getItems(selector, options) {
205
+ this.emit("getItems", selector);
429
206
  return this.profile(() => {
430
- const indexInfo = this.getIndexInfo(selector);
431
- const matchItems = (item) => {
432
- if (indexInfo.optimizedSelector == null)
433
- return true;
434
- if (Object.keys(indexInfo.optimizedSelector).length <= 0)
435
- return true;
436
- const matches = match(item, indexInfo.optimizedSelector);
437
- return matches;
438
- };
439
- this.emit("getItems", selector);
440
- const memory = this.memoryArray();
441
- if (!indexInfo.matched) {
442
- if (isEqual(selector, {}))
443
- return memory;
444
- return memory.filter(matchItems);
445
- }
446
- const items = indexInfo.positions.map((index) => memory[index]);
447
- if (isEqual(indexInfo.optimizedSelector, {}))
448
- return items;
449
- return items.filter(matchItems);
207
+ if (!options?.async)
208
+ return this.backend.getQueryResult(selector, options);
209
+ this.isPullingSignal.set(true);
210
+ return this.backend.executeQuery(selector, options).finally(() => {
211
+ this.isPullingSignal.set(false);
212
+ });
450
213
  }, (measuredTime) => this.executeInDebugMode((callstack) => this.emit("_debug.getItems", callstack, selector, measuredTime)));
451
214
  }
215
+ async withPushState(asyncFunction) {
216
+ this.isPushingSignal.set(true);
217
+ try {
218
+ return await asyncFunction();
219
+ } finally {
220
+ this.isPushingSignal.set(false);
221
+ }
222
+ }
223
+ queryListeners(query, listeners) {
224
+ const id = queryId(query.selector, query.options);
225
+ if (listeners != null) {
226
+ return this.queryListenersMap.set(id, listeners);
227
+ }
228
+ return this.queryListenersMap.get(id) ?? 0;
229
+ }
452
230
  /**
453
231
  * Disposes the collection, unregisters persistence adapters, clears memory, and
454
232
  * cleans up all resources used by the collection.
455
233
  * @returns A promise that resolves when the collection is disposed.
456
234
  */
457
235
  async dispose() {
458
- if (this.persistenceAdapter?.unregister)
459
- await this.persistenceAdapter.unregister();
460
- this.persistenceAdapter = null;
461
- this.memory().map(() => this.memory().pop());
462
- this.idIndex.clear();
463
- this.indexProviders = [];
236
+ await this.backend.dispose();
464
237
  this.isDisposed = true;
465
238
  this.removeAllListeners();
466
239
  Collection.collections = Collection.collections.filter((collection) => collection !== this);
@@ -474,12 +247,12 @@ class Collection extends EventEmitter {
474
247
  * @param [options] - Options for the find operation, such as limit and sort.
475
248
  * @returns A cursor to fetch and observe the matching items.
476
249
  */
477
- find(selector, options) {
250
+ find(selector = {}, options) {
478
251
  if (this.isDisposed)
479
252
  throw new Error("Collection is disposed");
480
253
  if (selector !== void 0 && (!selector || typeof selector !== "object"))
481
254
  throw new Error("Invalid selector");
482
- const cursor = new Cursor(() => this.getItems(selector), {
255
+ const cursor = new Cursor((() => this.getItems(selector, options || {})), {
483
256
  reactive: this.options.reactivity,
484
257
  fieldTracking: this.fieldTracking,
485
258
  ...options,
@@ -492,17 +265,25 @@ class Collection extends EventEmitter {
492
265
  }
493
266
  requery();
494
267
  };
495
- this.addListener("persistence.received", handleRequery);
496
- this.addListener("added", handleRequery);
497
- this.addListener("changed", handleRequery);
498
- this.addListener("removed", handleRequery);
268
+ const listeners = this.queryListeners({ selector, options });
269
+ if (listeners === 0)
270
+ this.backend.registerQuery(selector, options || {});
271
+ this.queryListeners({ selector, options }, listeners + 1);
272
+ const queryStateChangeCleanup = this.backend.onQueryStateChange(selector, options || {}, (state) => {
273
+ if (state !== "complete")
274
+ return;
275
+ handleRequery();
276
+ });
499
277
  this.emit("observer.created", selector, options);
500
278
  return () => {
501
- this.removeListener("persistence.received", handleRequery);
502
- this.removeListener("added", handleRequery);
503
- this.removeListener("changed", handleRequery);
504
- this.removeListener("removed", handleRequery);
505
- this.emit("observer.disposed", selector, options);
279
+ setTimeout(() => {
280
+ const newListeners = Math.max(0, this.queryListeners({ selector, options }) - 1);
281
+ if (newListeners === 0)
282
+ this.backend.unregisterQuery(selector, options || {});
283
+ this.queryListeners({ selector, options }, newListeners);
284
+ queryStateChangeCleanup();
285
+ this.emit("observer.disposed", selector, options);
286
+ }, 0);
506
287
  };
507
288
  }
508
289
  });
@@ -514,6 +295,7 @@ class Collection extends EventEmitter {
514
295
  * Finds a single item in the collection based on a selector and optional options.
515
296
  * ⚡️ this function is reactive!
516
297
  * Returns the found item or undefined if no item matches.
298
+ * @template Async - Whether to perform the operation asynchronously.
517
299
  * @template O - The options type for the find operation.
518
300
  * @param selector - The criteria to select the item.
519
301
  * @param [options] - Options for the find operation, such as projection.
@@ -526,23 +308,39 @@ class Collection extends EventEmitter {
526
308
  limit: 1,
527
309
  ...options
528
310
  });
529
- const returnValue = cursor.fetch()[0] || void 0;
530
- this.emit("findOne", selector, options, returnValue);
531
- this.executeInDebugMode((callstack) => this.emit("_debug.findOne", callstack, selector, options, returnValue));
532
- return returnValue;
311
+ const handleItems = (items) => {
312
+ const returnValue = items[0] || void 0;
313
+ this.emit("findOne", selector, options, returnValue);
314
+ this.executeInDebugMode((callstack) => this.emit("_debug.findOne", callstack, selector, options, returnValue));
315
+ return returnValue;
316
+ };
317
+ const maybePromise = cursor.fetch();
318
+ return maybePromise instanceof Promise ? maybePromise.then(handleItems) : handleItems(maybePromise);
533
319
  }
534
- /**
535
- * Performs a batch operation, deferring index rebuilds and allowing multiple
536
- * modifications to be made atomically. Executes any post-batch callbacks afterwards.
537
- * @param callback - The batch operation to execute.
538
- */
539
320
  batch(callback) {
321
+ if (this.batchOperationInProgress)
322
+ return callback();
540
323
  this.batchOperationInProgress = true;
541
- callback();
542
- this.batchOperationInProgress = false;
543
- this.rebuildAllIndices();
544
- this.postBatchCallbacks.forEach((callback_) => callback_());
545
- this.postBatchCallbacks.clear();
324
+ const maybePromise = callback();
325
+ const afterBatch = () => {
326
+ this.batchOperationInProgress = false;
327
+ this.postBatchCallbacks.forEach((callback_) => callback_());
328
+ this.postBatchCallbacks.clear();
329
+ };
330
+ if (maybePromise && typeof maybePromise.then === "function") {
331
+ return maybePromise.then(() => afterBatch());
332
+ } else {
333
+ afterBatch();
334
+ }
335
+ }
336
+ onPostBatch(callback) {
337
+ if (this.isDisposed)
338
+ throw new Error("Collection is disposed");
339
+ if (this.batchOperationInProgress) {
340
+ this.postBatchCallbacks.add(callback);
341
+ return;
342
+ }
343
+ return callback();
546
344
  }
547
345
  /**
548
346
  * Inserts a single item into the collection. Generates a unique ID if not provided.
@@ -550,20 +348,18 @@ class Collection extends EventEmitter {
550
348
  * @returns The ID of the inserted item.
551
349
  * @throws {Error} If the collection is disposed or the item has an invalid ID.
552
350
  */
553
- insert(item) {
351
+ async insert(item) {
554
352
  if (this.isDisposed)
555
353
  throw new Error("Collection is disposed");
556
354
  if (!item)
557
355
  throw new Error("Invalid item");
558
356
  const primaryKeyGenerator = this.options.primaryKeyGenerator ?? randomId;
559
- const newItem = { id: primaryKeyGenerator(item), ...item };
560
- this.emit("validate", newItem);
561
- if (this.idIndex.has(serializeValue(newItem.id)))
562
- throw new Error("Item with same id already exists");
563
- this.memory().push(newItem);
564
- const itemIndex = this.memory().findIndex((document) => document === newItem);
565
- this.idIndex.set(serializeValue(newItem.id), /* @__PURE__ */ new Set([itemIndex]));
566
- this.rebuildIndices();
357
+ const itemWithId = {
358
+ id: primaryKeyGenerator(item),
359
+ ...item
360
+ };
361
+ this.emit("validate", itemWithId);
362
+ const newItem = await this.withPushState(() => this.backend.insert(itemWithId));
567
363
  this.emit("added", newItem);
568
364
  this.emit("insert", newItem);
569
365
  this.executeInDebugMode((callstack) => this.emit("_debug.insert", callstack, newItem));
@@ -575,19 +371,18 @@ class Collection extends EventEmitter {
575
371
  * @returns An array of IDs of the inserted items.
576
372
  * @throws {Error} If the collection is disposed or the items are invalid.
577
373
  */
578
- insertMany(items) {
374
+ async insertMany(items) {
579
375
  if (this.isDisposed)
580
376
  throw new Error("Collection is disposed");
581
377
  if (!items)
582
378
  throw new Error("Invalid items");
583
- if (items.length === 0) {
379
+ if (items.length === 0)
584
380
  return [];
585
- }
586
381
  const ids = [];
587
- this.batch(() => {
588
- items.forEach((item) => {
589
- ids.push(this.insert(item));
590
- });
382
+ await this.batch(async () => {
383
+ await Promise.all(items.map(async (item) => {
384
+ ids.push(await this.insert(item));
385
+ }));
591
386
  });
592
387
  return ids;
593
388
  }
@@ -600,7 +395,7 @@ class Collection extends EventEmitter {
600
395
  * @returns The number of items updated (0 or 1).
601
396
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
602
397
  */
603
- updateOne(selector, modifier, options) {
398
+ async updateOne(selector, modifier, options) {
604
399
  if (this.isDisposed)
605
400
  throw new Error("Collection is disposed");
606
401
  if (!selector)
@@ -608,7 +403,7 @@ class Collection extends EventEmitter {
608
403
  if (!modifier)
609
404
  throw new Error("Invalid modifier");
610
405
  const { $setOnInsert, ...restModifier } = modifier;
611
- const { item, index } = this.getItemAndIndex(selector);
406
+ const item = await this.getItem(selector, { async: true });
612
407
  if (item == null) {
613
408
  if (options?.upsert) {
614
409
  const newItem = modify({}, {
@@ -618,26 +413,18 @@ class Collection extends EventEmitter {
618
413
  ...restModifier.$set
619
414
  }
620
415
  });
621
- if (newItem.id != null && this.getItemAndIndex({ id: newItem.id }).item != null) {
622
- throw new Error("Item with same id already exists");
623
- }
624
- this.insert(newItem);
625
- }
626
- } else {
627
- const modifiedItem = modify(deepClone(item), restModifier);
628
- if (item.id !== modifiedItem.id && this.getItemAndIndex({ id: modifiedItem.id }).item != null) {
629
- throw new Error("Item with same id already exists");
416
+ await this.insert(newItem);
417
+ return 1;
630
418
  }
631
- this.emit("validate", modifiedItem);
632
- this.memory().splice(index, 1, modifiedItem);
633
- this.rebuildIndices();
634
- this.emit("changed", modifiedItem, restModifier);
419
+ return 0;
635
420
  }
421
+ const modifiedItem = modify(deepClone(item), restModifier);
422
+ this.emit("validate", modifiedItem);
423
+ const changes = await this.withPushState(() => this.backend.updateOne(selector, modifier));
424
+ this.emit("changed", modifiedItem, restModifier);
636
425
  this.emit("updateOne", selector, modifier);
637
426
  this.executeInDebugMode((callstack) => this.emit("_debug.updateOne", callstack, selector, modifier));
638
- if (item == null && !options?.upsert)
639
- return 0;
640
- return 1;
427
+ return changes.length;
641
428
  }
642
429
  /**
643
430
  * Updates multiple items in the collection that match the given selector.
@@ -648,7 +435,7 @@ class Collection extends EventEmitter {
648
435
  * @returns The number of items updated.
649
436
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
650
437
  */
651
- updateMany(selector, modifier, options) {
438
+ async updateMany(selector, modifier, options) {
652
439
  if (this.isDisposed)
653
440
  throw new Error("Collection is disposed");
654
441
  if (!selector)
@@ -656,44 +443,32 @@ class Collection extends EventEmitter {
656
443
  if (!modifier)
657
444
  throw new Error("Invalid modifier");
658
445
  const { $setOnInsert, ...restModifier } = modifier;
659
- const items = this.getItems(selector);
660
- if (items.length === 0 && options?.upsert) {
661
- const newItem = modify({}, {
662
- ...restModifier,
663
- $set: {
664
- ...$setOnInsert,
665
- ...restModifier.$set
666
- }
667
- });
668
- if (newItem.id != null && this.getItemAndIndex({ id: newItem.id }).item != null) {
669
- throw new Error("Item with same id already exists");
446
+ const items = await this.getItems(selector, { async: true });
447
+ if (items.length === 0) {
448
+ if (options?.upsert) {
449
+ const newItem = modify({}, {
450
+ ...restModifier,
451
+ $set: {
452
+ ...$setOnInsert,
453
+ ...restModifier.$set
454
+ }
455
+ });
456
+ await this.insert(newItem);
457
+ return 1;
670
458
  }
671
- this.insert(newItem);
459
+ return 0;
672
460
  }
673
- const changes = items.map((item) => {
674
- const { index } = this.getItemAndIndex({ id: item.id });
675
- if (index === -1)
676
- throw new Error(`Cannot resolve index for item with id '${item.id}'`);
461
+ items.forEach((item) => {
677
462
  const modifiedItem = modify(deepClone(item), restModifier);
678
- if (item.id !== modifiedItem.id && this.getItemAndIndex({ id: modifiedItem.id }).item != null) {
679
- throw new Error(`Item with same id '${modifiedItem.id}' already exists`);
680
- }
681
463
  this.emit("validate", modifiedItem);
682
- return {
683
- item: modifiedItem,
684
- index
685
- };
686
464
  });
687
- changes.forEach(({ item, index }) => {
688
- this.memory().splice(index, 1, item);
689
- });
690
- this.rebuildIndices();
691
- changes.forEach(({ item }) => {
465
+ const changes = await this.withPushState(() => this.backend.updateMany(selector, modifier));
466
+ changes.forEach((item) => {
692
467
  this.emit("changed", item, restModifier);
693
468
  });
694
469
  this.emit("updateMany", selector, modifier);
695
470
  this.executeInDebugMode((callstack) => this.emit("_debug.updateMany", callstack, selector, modifier));
696
- return changes.length === 0 && options?.upsert ? 1 : changes.length;
471
+ return changes.length;
697
472
  }
698
473
  /**
699
474
  * Replaces a single item in the collection that matches the given selector.
@@ -704,34 +479,26 @@ class Collection extends EventEmitter {
704
479
  * @returns The number of items replaced (0 or 1).
705
480
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
706
481
  */
707
- replaceOne(selector, replacement, options) {
482
+ async replaceOne(selector, replacement, options) {
708
483
  if (this.isDisposed)
709
484
  throw new Error("Collection is disposed");
710
485
  if (!selector)
711
486
  throw new Error("Invalid selector");
712
- const { item, index } = this.getItemAndIndex(selector);
487
+ const item = await this.getItem(selector, { async: true });
713
488
  if (item == null) {
714
489
  if (options?.upsert) {
715
- if (replacement.id != null && this.getItemAndIndex({ id: replacement.id }).item != null) {
716
- throw new Error("Item with same id already exists");
717
- }
718
- this.insert(replacement);
490
+ await this.insert(replacement);
491
+ return 1;
719
492
  }
720
- } else {
721
- if (item.id !== replacement.id && this.getItemAndIndex({ id: replacement.id }).item != null) {
722
- throw new Error("Item with same id already exists");
723
- }
724
- const modifiedItem = { id: item.id, ...replacement };
725
- this.emit("validate", modifiedItem);
726
- this.memory().splice(index, 1, modifiedItem);
727
- this.rebuildIndices();
728
- this.emit("changed", modifiedItem, replacement);
493
+ return 0;
729
494
  }
495
+ const modifiedItem = { id: item.id, ...replacement };
496
+ this.emit("validate", modifiedItem);
497
+ const changes = await this.withPushState(() => this.backend.replaceOne(selector, replacement));
498
+ this.emit("changed", modifiedItem, replacement);
730
499
  this.emit("replaceOne", selector, replacement);
731
500
  this.executeInDebugMode((callstack) => this.emit("_debug.replaceOne", callstack, selector, replacement));
732
- if (item == null && !options?.upsert)
733
- return 0;
734
- return 1;
501
+ return changes.length;
735
502
  }
736
503
  /**
737
504
  * Removes a single item from the collection that matches the given selector.
@@ -739,21 +506,16 @@ class Collection extends EventEmitter {
739
506
  * @returns The number of items removed (0 or 1).
740
507
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
741
508
  */
742
- removeOne(selector) {
509
+ async removeOne(selector) {
743
510
  if (this.isDisposed)
744
511
  throw new Error("Collection is disposed");
745
512
  if (!selector)
746
513
  throw new Error("Invalid selector");
747
- const { item, index } = this.getItemAndIndex(selector);
748
- if (item != null) {
749
- this.memory().splice(index, 1);
750
- this.deleteFromIdIndex(item.id, index);
751
- this.rebuildIndices();
752
- this.emit("removed", item);
753
- }
514
+ const removedItems = await this.withPushState(() => this.backend.removeOne(selector));
515
+ this.emit("removed", removedItems[0]);
754
516
  this.emit("removeOne", selector);
755
517
  this.executeInDebugMode((callstack) => this.emit("_debug.removeOne", callstack, selector));
756
- return item == null ? 0 : 1;
518
+ return removedItems.length;
757
519
  }
758
520
  /**
759
521
  * Removes multiple items from the collection that match the given selector.
@@ -761,29 +523,20 @@ class Collection extends EventEmitter {
761
523
  * @returns The number of items removed.
762
524
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
763
525
  */
764
- removeMany(selector) {
526
+ async removeMany(selector) {
765
527
  if (this.isDisposed)
766
528
  throw new Error("Collection is disposed");
767
529
  if (!selector)
768
530
  throw new Error("Invalid selector");
769
- const items = this.getItems(selector);
770
- items.forEach((item) => {
771
- const index = this.memory().findIndex((document) => document === item);
772
- if (index === -1)
773
- throw new Error("Cannot resolve index for item");
774
- this.memory().splice(index, 1);
775
- this.deleteFromIdIndex(item.id, index);
776
- this.rebuildIndices();
777
- });
778
- items.forEach((item) => {
531
+ const removedItems = await this.withPushState(() => this.backend.removeMany(selector));
532
+ removedItems.forEach((item) => {
779
533
  this.emit("removed", item);
780
534
  });
781
535
  this.emit("removeMany", selector);
782
536
  this.executeInDebugMode((callstack) => this.emit("_debug.removeMany", callstack, selector));
783
- return items.length;
537
+ return removedItems.length;
784
538
  }
785
539
  }
786
540
  export {
787
- default2 as createIndex,
788
541
  Collection as default
789
542
  };