@signaldb/core 1.7.1 → 2.0.0-beta.0

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 (88) hide show
  1. package/dist/.vite/manifest.json +125 -82
  2. package/dist/AsyncDataAdapter.d.ts +64 -0
  3. package/dist/AutoFetchDataAdapter.d.ts +113 -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 +52 -46
  7. package/dist/Collection/types.d.ts +4 -9
  8. package/dist/DataAdapter.d.ts +33 -0
  9. package/dist/DefaultDataAdapter.d.ts +32 -0
  10. package/dist/WorkerDataAdapter.d.ts +22 -0
  11. package/dist/WorkerDataAdapterHost.d.ts +59 -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 +337 -3
  19. package/dist/index.cjs13.js +384 -110
  20. package/dist/index.cjs14.js +183 -67
  21. package/dist/index.cjs15.js +339 -8
  22. package/dist/index.cjs16.js +563 -20
  23. package/dist/index.cjs17.js +12 -4
  24. package/dist/index.cjs18.js +24 -5
  25. package/dist/index.cjs2.js +24 -30
  26. package/dist/index.cjs20.js +102 -13
  27. package/dist/index.cjs21.js +124 -21
  28. package/dist/index.cjs22.js +5 -77
  29. package/dist/index.cjs23.js +22 -79
  30. package/dist/index.cjs24.js +5 -16
  31. package/dist/index.cjs25.js +8 -27
  32. package/dist/index.cjs26.js +27 -7
  33. package/dist/index.cjs27.js +3 -5
  34. package/dist/index.cjs28.js +5 -27
  35. package/dist/index.cjs29.js +27 -40
  36. package/dist/index.cjs3.js +191 -441
  37. package/dist/index.cjs30.js +42 -0
  38. package/dist/index.cjs31.js +9 -0
  39. package/dist/index.cjs4.js +3 -165
  40. package/dist/index.cjs5.js +3 -66
  41. package/dist/index.cjs6.js +27 -3
  42. package/dist/index.cjs7.js +10 -3
  43. package/dist/index.cjs8.js +3 -3
  44. package/dist/index.cjs9.js +131 -3
  45. package/dist/index.d.ts +10 -8
  46. package/dist/index.mjs +15 -11
  47. package/dist/index10.mjs +15 -26
  48. package/dist/index11.mjs +13 -10
  49. package/dist/index12.mjs +337 -3
  50. package/dist/index13.mjs +384 -110
  51. package/dist/index14.mjs +183 -66
  52. package/dist/index15.mjs +339 -8
  53. package/dist/index16.mjs +563 -20
  54. package/dist/index17.mjs +12 -4
  55. package/dist/index18.mjs +24 -5
  56. package/dist/index2.mjs +24 -30
  57. package/dist/index20.mjs +102 -13
  58. package/dist/index21.mjs +123 -21
  59. package/dist/index22.mjs +5 -76
  60. package/dist/index23.mjs +22 -78
  61. package/dist/index24.mjs +5 -16
  62. package/dist/index25.mjs +8 -27
  63. package/dist/index26.mjs +27 -7
  64. package/dist/index27.mjs +3 -5
  65. package/dist/index28.mjs +5 -27
  66. package/dist/index29.mjs +27 -40
  67. package/dist/index3.mjs +191 -442
  68. package/dist/index30.mjs +43 -0
  69. package/dist/index31.mjs +10 -0
  70. package/dist/index4.mjs +3 -165
  71. package/dist/index5.mjs +3 -65
  72. package/dist/index6.mjs +27 -3
  73. package/dist/index7.mjs +10 -3
  74. package/dist/index8.mjs +3 -3
  75. package/dist/index9.mjs +131 -3
  76. package/dist/types/IndexProvider.d.ts +12 -7
  77. package/dist/types/StorageAdapter.d.ts +20 -0
  78. package/dist/utils/objectId.d.ts +8 -0
  79. package/dist/utils/queryId.d.ts +9 -0
  80. package/package.json +1 -1
  81. package/dist/AutoFetchCollection.d.ts +0 -60
  82. package/dist/Collection/createIndex.d.ts +0 -15
  83. package/dist/ReplicatedCollection.d.ts +0 -60
  84. package/dist/createMemoryAdapter.d.ts +0 -7
  85. package/dist/persistence/combinePersistenceAdapters.d.ts +0 -32
  86. package/dist/persistence/createPersistenceAdapter.d.ts +0 -9
  87. package/dist/types/MemoryAdapter.d.ts +0 -15
  88. package/dist/types/PersistenceAdapter.d.ts +0 -20
package/dist/index3.mjs CHANGED
@@ -1,37 +1,10 @@
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";
9
7
  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
8
  class Collection extends EventEmitter {
36
9
  static collections = [];
37
10
  static debugMode = false;
@@ -67,210 +40,58 @@ class Collection extends EventEmitter {
67
40
  collection.setFieldTracking(enable);
68
41
  });
69
42
  };
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
43
  static batch(callback) {
78
44
  Collection.batchOperationInProgress = true;
79
- Collection.collections.reduce((memo, collection) => () => collection.batch(() => memo()), callback)();
80
- Collection.batchOperationInProgress = false;
45
+ const execute = () => Collection.collections.reduce((memo, collection) => () => {
46
+ return collection.batch(memo);
47
+ }, callback)();
48
+ const maybePromise = execute();
49
+ const afterBatch = () => {
50
+ Collection.batchOperationInProgress = false;
51
+ };
52
+ if (maybePromise && typeof maybePromise.then === "function") {
53
+ return maybePromise.then(() => afterBatch());
54
+ } else {
55
+ afterBatch();
56
+ }
81
57
  }
82
58
  name;
59
+ backend;
83
60
  options;
84
- persistenceAdapter = null;
85
61
  isPullingSignal;
86
62
  isPushingSignal;
87
- indexProviders = [];
88
- indicesOutdated = false;
89
- idIndex = /* @__PURE__ */ new Map();
63
+ readySignal;
90
64
  debugMode;
91
65
  batchOperationInProgress = false;
92
66
  isDisposed = false;
93
67
  postBatchCallbacks = /* @__PURE__ */ new Set();
94
68
  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) {
69
+ constructor(nameOrOptions, maybeDataAdapter, maybeOptions) {
113
70
  super();
71
+ const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name || `${this.constructor.name}-${randomId()}`;
72
+ const options = typeof nameOrOptions === "string" ? maybeOptions || {} : nameOrOptions || {};
73
+ const persistence = options.persistence;
74
+ const dataAdapter = maybeDataAdapter || new DefaultDataAdapter({
75
+ ...persistence ? { storage: () => persistence } : {}
76
+ });
114
77
  Collection.collections.push(this);
115
- this.name = options?.name ?? `${this.constructor.name}-${randomId()}`;
116
- this.options = {
117
- memory: [],
118
- ...options
119
- };
78
+ this.name = name;
79
+ this.options = { ...options };
120
80
  this.fieldTracking = this.options.fieldTracking ?? Collection.fieldTracking;
121
81
  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);
82
+ this.isPullingSignal = createSignal(this.options.reactivity, false);
128
83
  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);
84
+ this.readySignal = createSignal(this.options.reactivity, false);
85
+ this.backend = dataAdapter.createCollectionBackend(this, this.options.indices ?? []);
86
+ void this.backend.isReady().then(() => {
87
+ this.readySignal.set(true);
88
+ }).catch(() => {
271
89
  });
272
90
  Collection.onCreationCallbacks.forEach((callback) => callback(this));
273
91
  }
92
+ isBatchOperationInProgress() {
93
+ return Collection.batchOperationInProgress || this.batchOperationInProgress;
94
+ }
274
95
  /**
275
96
  * Checks whether the collection is currently performing a pull operation
276
97
  * ⚡️ this function is reactive!
@@ -335,17 +156,28 @@ class Collection extends EventEmitter {
335
156
  *
336
157
  * collection.insert({ name: 'Item 1' })
337
158
  */
338
- async isReady() {
339
- return this.persistenceReadyPromise;
159
+ async ready() {
160
+ return this.backend.isReady();
161
+ }
162
+ /**
163
+ * Checks if the collection is ready.
164
+ * ⚡️ this function is reactive!
165
+ * @returns A boolean indicating whether the collection is ready.
166
+ */
167
+ isReady() {
168
+ return this.readySignal.get() ?? false;
340
169
  }
341
170
  profile(fn, measureFunction) {
342
171
  if (!this.debugMode)
343
172
  return fn();
344
173
  const startTime = performance.now();
345
- const result = fn();
346
- const endTime = performance.now();
347
- measureFunction(endTime - startTime);
348
- return result;
174
+ const handleProfileEnd = (result) => {
175
+ const endTime = performance.now();
176
+ measureFunction(endTime - startTime);
177
+ return result;
178
+ };
179
+ const maybePromise = fn();
180
+ return maybePromise instanceof Promise ? maybePromise.then(handleProfileEnd) : handleProfileEnd(maybePromise);
349
181
  }
350
182
  executeInDebugMode(fn) {
351
183
  if (!this.debugMode)
@@ -353,114 +185,58 @@ class Collection extends EventEmitter {
353
185
  const callstack = new Error().stack || "";
354
186
  fn(callstack);
355
187
  }
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
188
  transform(item) {
424
189
  if (!this.options.transform)
425
190
  return item;
426
191
  return this.options.transform(item);
427
192
  }
428
- getItems(selector) {
193
+ getItem(selector, options) {
194
+ const itemsOrPromise = this.getItems(selector, { ...options, limit: 1 });
195
+ if (itemsOrPromise instanceof Promise) {
196
+ return itemsOrPromise.then((items) => {
197
+ return items[0] || void 0;
198
+ });
199
+ }
200
+ return itemsOrPromise[0];
201
+ }
202
+ getItems(selector, options) {
203
+ this.emit("getItems", selector);
429
204
  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);
205
+ if (!options?.async)
206
+ return this.backend.getQueryResult(selector, options);
207
+ return new Promise((resolve, reject) => {
208
+ this.isPullingSignal.set(true);
209
+ const cleanup = this.backend.onQueryStateChange(selector, options, (state) => {
210
+ if (state === "error") {
211
+ cleanup();
212
+ reject(this.backend.getQueryError(selector, options) || new Error("Unknown error"));
213
+ } else if (state === "complete") {
214
+ cleanup();
215
+ resolve(this.backend.getQueryResult(selector, options) || []);
216
+ }
217
+ });
218
+ this.backend.registerQuery(selector, options);
219
+ }).finally(() => {
220
+ this.isPullingSignal.set(false);
221
+ this.backend.unregisterQuery(selector, options);
222
+ });
450
223
  }, (measuredTime) => this.executeInDebugMode((callstack) => this.emit("_debug.getItems", callstack, selector, measuredTime)));
451
224
  }
225
+ async withPushState(asyncFunction) {
226
+ this.isPushingSignal.set(true);
227
+ try {
228
+ return await asyncFunction();
229
+ } finally {
230
+ this.isPushingSignal.set(false);
231
+ }
232
+ }
452
233
  /**
453
234
  * Disposes the collection, unregisters persistence adapters, clears memory, and
454
235
  * cleans up all resources used by the collection.
455
236
  * @returns A promise that resolves when the collection is disposed.
456
237
  */
457
238
  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 = [];
239
+ await this.backend.dispose();
464
240
  this.isDisposed = true;
465
241
  this.removeAllListeners();
466
242
  Collection.collections = Collection.collections.filter((collection) => collection !== this);
@@ -474,12 +250,12 @@ class Collection extends EventEmitter {
474
250
  * @param [options] - Options for the find operation, such as limit and sort.
475
251
  * @returns A cursor to fetch and observe the matching items.
476
252
  */
477
- find(selector, options) {
253
+ find(selector = {}, options) {
478
254
  if (this.isDisposed)
479
255
  throw new Error("Collection is disposed");
480
256
  if (selector !== void 0 && (!selector || typeof selector !== "object"))
481
257
  throw new Error("Invalid selector");
482
- const cursor = new Cursor(() => this.getItems(selector), {
258
+ const cursor = new Cursor((() => this.getItems(selector, options || {})), {
483
259
  reactive: this.options.reactivity,
484
260
  fieldTracking: this.fieldTracking,
485
261
  ...options,
@@ -492,16 +268,16 @@ class Collection extends EventEmitter {
492
268
  }
493
269
  requery();
494
270
  };
495
- this.addListener("persistence.received", handleRequery);
496
- this.addListener("added", handleRequery);
497
- this.addListener("changed", handleRequery);
498
- this.addListener("removed", handleRequery);
271
+ this.backend.registerQuery(selector, options);
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);
279
+ this.backend.unregisterQuery(selector, options);
280
+ queryStateChangeCleanup();
505
281
  this.emit("observer.disposed", selector, options);
506
282
  };
507
283
  }
@@ -514,6 +290,7 @@ class Collection extends EventEmitter {
514
290
  * Finds a single item in the collection based on a selector and optional options.
515
291
  * ⚡️ this function is reactive!
516
292
  * Returns the found item or undefined if no item matches.
293
+ * @template Async - Whether to perform the operation asynchronously.
517
294
  * @template O - The options type for the find operation.
518
295
  * @param selector - The criteria to select the item.
519
296
  * @param [options] - Options for the find operation, such as projection.
@@ -526,23 +303,39 @@ class Collection extends EventEmitter {
526
303
  limit: 1,
527
304
  ...options
528
305
  });
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;
306
+ const handleItems = (items) => {
307
+ const returnValue = items[0] || void 0;
308
+ this.emit("findOne", selector, options, returnValue);
309
+ this.executeInDebugMode((callstack) => this.emit("_debug.findOne", callstack, selector, options, returnValue));
310
+ return returnValue;
311
+ };
312
+ const maybePromise = cursor.fetch();
313
+ return maybePromise instanceof Promise ? maybePromise.then(handleItems) : handleItems(maybePromise);
533
314
  }
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
315
  batch(callback) {
316
+ if (this.batchOperationInProgress)
317
+ return callback();
540
318
  this.batchOperationInProgress = true;
541
- callback();
542
- this.batchOperationInProgress = false;
543
- this.rebuildAllIndices();
544
- this.postBatchCallbacks.forEach((callback_) => callback_());
545
- this.postBatchCallbacks.clear();
319
+ const maybePromise = callback();
320
+ const afterBatch = () => {
321
+ this.batchOperationInProgress = false;
322
+ this.postBatchCallbacks.forEach((callback_) => callback_());
323
+ this.postBatchCallbacks.clear();
324
+ };
325
+ if (maybePromise && typeof maybePromise.then === "function") {
326
+ return maybePromise.then(() => afterBatch());
327
+ } else {
328
+ afterBatch();
329
+ }
330
+ }
331
+ onPostBatch(callback) {
332
+ if (this.isDisposed)
333
+ throw new Error("Collection is disposed");
334
+ if (this.batchOperationInProgress) {
335
+ this.postBatchCallbacks.add(callback);
336
+ return;
337
+ }
338
+ return callback();
546
339
  }
547
340
  /**
548
341
  * Inserts a single item into the collection. Generates a unique ID if not provided.
@@ -550,20 +343,18 @@ class Collection extends EventEmitter {
550
343
  * @returns The ID of the inserted item.
551
344
  * @throws {Error} If the collection is disposed or the item has an invalid ID.
552
345
  */
553
- insert(item) {
346
+ async insert(item) {
554
347
  if (this.isDisposed)
555
348
  throw new Error("Collection is disposed");
556
349
  if (!item)
557
350
  throw new Error("Invalid item");
558
351
  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();
352
+ const itemWithId = {
353
+ id: primaryKeyGenerator(item),
354
+ ...item
355
+ };
356
+ this.emit("validate", itemWithId);
357
+ const newItem = await this.withPushState(() => this.backend.insert(itemWithId));
567
358
  this.emit("added", newItem);
568
359
  this.emit("insert", newItem);
569
360
  this.executeInDebugMode((callstack) => this.emit("_debug.insert", callstack, newItem));
@@ -575,7 +366,7 @@ class Collection extends EventEmitter {
575
366
  * @returns An array of IDs of the inserted items.
576
367
  * @throws {Error} If the collection is disposed or the items are invalid.
577
368
  */
578
- insertMany(items) {
369
+ async insertMany(items) {
579
370
  if (this.isDisposed)
580
371
  throw new Error("Collection is disposed");
581
372
  if (!items)
@@ -584,10 +375,10 @@ class Collection extends EventEmitter {
584
375
  return [];
585
376
  }
586
377
  const ids = [];
587
- this.batch(() => {
588
- items.forEach((item) => {
589
- ids.push(this.insert(item));
590
- });
378
+ await this.batch(async () => {
379
+ await Promise.all(items.map(async (item) => {
380
+ ids.push(await this.insert(item));
381
+ }));
591
382
  });
592
383
  return ids;
593
384
  }
@@ -600,7 +391,7 @@ class Collection extends EventEmitter {
600
391
  * @returns The number of items updated (0 or 1).
601
392
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
602
393
  */
603
- updateOne(selector, modifier, options) {
394
+ async updateOne(selector, modifier, options) {
604
395
  if (this.isDisposed)
605
396
  throw new Error("Collection is disposed");
606
397
  if (!selector)
@@ -608,7 +399,7 @@ class Collection extends EventEmitter {
608
399
  if (!modifier)
609
400
  throw new Error("Invalid modifier");
610
401
  const { $setOnInsert, ...restModifier } = modifier;
611
- const { item, index } = this.getItemAndIndex(selector);
402
+ const item = await this.getItem(selector, { async: true });
612
403
  if (item == null) {
613
404
  if (options?.upsert) {
614
405
  const newItem = modify({}, {
@@ -618,26 +409,18 @@ class Collection extends EventEmitter {
618
409
  ...restModifier.$set
619
410
  }
620
411
  });
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");
412
+ await this.insert(newItem);
413
+ return 1;
630
414
  }
631
- this.emit("validate", modifiedItem);
632
- this.memory().splice(index, 1, modifiedItem);
633
- this.rebuildIndices();
634
- this.emit("changed", modifiedItem, restModifier);
415
+ return 0;
635
416
  }
417
+ const modifiedItem = modify(deepClone(item), restModifier);
418
+ this.emit("validate", modifiedItem);
419
+ const changes = await this.withPushState(() => this.backend.updateOne(selector, modifier));
420
+ this.emit("changed", modifiedItem, restModifier);
636
421
  this.emit("updateOne", selector, modifier);
637
422
  this.executeInDebugMode((callstack) => this.emit("_debug.updateOne", callstack, selector, modifier));
638
- if (item == null && !options?.upsert)
639
- return 0;
640
- return 1;
423
+ return changes.length;
641
424
  }
642
425
  /**
643
426
  * Updates multiple items in the collection that match the given selector.
@@ -648,7 +431,7 @@ class Collection extends EventEmitter {
648
431
  * @returns The number of items updated.
649
432
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
650
433
  */
651
- updateMany(selector, modifier, options) {
434
+ async updateMany(selector, modifier, options) {
652
435
  if (this.isDisposed)
653
436
  throw new Error("Collection is disposed");
654
437
  if (!selector)
@@ -656,44 +439,32 @@ class Collection extends EventEmitter {
656
439
  if (!modifier)
657
440
  throw new Error("Invalid modifier");
658
441
  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");
442
+ const items = await this.getItems(selector, { async: true });
443
+ if (items.length === 0) {
444
+ if (options?.upsert) {
445
+ const newItem = modify({}, {
446
+ ...restModifier,
447
+ $set: {
448
+ ...$setOnInsert,
449
+ ...restModifier.$set
450
+ }
451
+ });
452
+ await this.insert(newItem);
453
+ return 1;
670
454
  }
671
- this.insert(newItem);
455
+ return 0;
672
456
  }
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}'`);
457
+ items.forEach((item) => {
677
458
  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
459
  this.emit("validate", modifiedItem);
682
- return {
683
- item: modifiedItem,
684
- index
685
- };
686
- });
687
- changes.forEach(({ item, index }) => {
688
- this.memory().splice(index, 1, item);
689
460
  });
690
- this.rebuildIndices();
691
- changes.forEach(({ item }) => {
461
+ const changes = await this.withPushState(() => this.backend.updateMany(selector, modifier));
462
+ changes.forEach((item) => {
692
463
  this.emit("changed", item, restModifier);
693
464
  });
694
465
  this.emit("updateMany", selector, modifier);
695
466
  this.executeInDebugMode((callstack) => this.emit("_debug.updateMany", callstack, selector, modifier));
696
- return changes.length === 0 && options?.upsert ? 1 : changes.length;
467
+ return changes.length;
697
468
  }
698
469
  /**
699
470
  * Replaces a single item in the collection that matches the given selector.
@@ -704,34 +475,26 @@ class Collection extends EventEmitter {
704
475
  * @returns The number of items replaced (0 or 1).
705
476
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
706
477
  */
707
- replaceOne(selector, replacement, options) {
478
+ async replaceOne(selector, replacement, options) {
708
479
  if (this.isDisposed)
709
480
  throw new Error("Collection is disposed");
710
481
  if (!selector)
711
482
  throw new Error("Invalid selector");
712
- const { item, index } = this.getItemAndIndex(selector);
483
+ const item = await this.getItem(selector, { async: true });
713
484
  if (item == null) {
714
485
  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);
719
- }
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");
486
+ await this.insert(replacement);
487
+ return 1;
723
488
  }
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);
489
+ return 0;
729
490
  }
491
+ const modifiedItem = { id: item.id, ...replacement };
492
+ this.emit("validate", modifiedItem);
493
+ const changes = await this.withPushState(() => this.backend.replaceOne(selector, replacement));
494
+ this.emit("changed", modifiedItem, replacement);
730
495
  this.emit("replaceOne", selector, replacement);
731
496
  this.executeInDebugMode((callstack) => this.emit("_debug.replaceOne", callstack, selector, replacement));
732
- if (item == null && !options?.upsert)
733
- return 0;
734
- return 1;
497
+ return changes.length;
735
498
  }
736
499
  /**
737
500
  * Removes a single item from the collection that matches the given selector.
@@ -739,21 +502,16 @@ class Collection extends EventEmitter {
739
502
  * @returns The number of items removed (0 or 1).
740
503
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
741
504
  */
742
- removeOne(selector) {
505
+ async removeOne(selector) {
743
506
  if (this.isDisposed)
744
507
  throw new Error("Collection is disposed");
745
508
  if (!selector)
746
509
  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
- }
510
+ const removedItems = await this.withPushState(() => this.backend.removeOne(selector));
511
+ this.emit("removed", removedItems[0]);
754
512
  this.emit("removeOne", selector);
755
513
  this.executeInDebugMode((callstack) => this.emit("_debug.removeOne", callstack, selector));
756
- return item == null ? 0 : 1;
514
+ return removedItems.length;
757
515
  }
758
516
  /**
759
517
  * Removes multiple items from the collection that match the given selector.
@@ -761,29 +519,20 @@ class Collection extends EventEmitter {
761
519
  * @returns The number of items removed.
762
520
  * @throws {Error} If the collection is disposed or invalid arguments are provided.
763
521
  */
764
- removeMany(selector) {
522
+ async removeMany(selector) {
765
523
  if (this.isDisposed)
766
524
  throw new Error("Collection is disposed");
767
525
  if (!selector)
768
526
  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) => {
527
+ const removedItems = await this.withPushState(() => this.backend.removeMany(selector));
528
+ removedItems.forEach((item) => {
779
529
  this.emit("removed", item);
780
530
  });
781
531
  this.emit("removeMany", selector);
782
532
  this.executeInDebugMode((callstack) => this.emit("_debug.removeMany", callstack, selector));
783
- return items.length;
533
+ return removedItems.length;
784
534
  }
785
535
  }
786
536
  export {
787
- default2 as createIndex,
788
537
  Collection as default
789
538
  };