@signaldb/core 2.0.0-beta.0 → 2.0.0-beta.2

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 (54) hide show
  1. package/dist/.vite/manifest.json +24 -11
  2. package/dist/AsyncDataAdapter.d.ts +1 -1
  3. package/dist/AutoFetchDataAdapter.d.ts +1 -2
  4. package/dist/Collection/Cursor.d.ts +3 -2
  5. package/dist/Collection/index.d.ts +17 -12
  6. package/dist/Collection/types.d.ts +1 -0
  7. package/dist/DataAdapter.d.ts +9 -7
  8. package/dist/DefaultDataAdapter.d.ts +2 -1
  9. package/dist/WorkerDataAdapter.d.ts +5 -2
  10. package/dist/WorkerDataAdapterHost.d.ts +9 -6
  11. package/dist/index.cjs12.js +35 -21
  12. package/dist/index.cjs13.js +27 -15
  13. package/dist/index.cjs14.js +72 -56
  14. package/dist/index.cjs15.js +122 -75
  15. package/dist/index.cjs16.js +45 -36
  16. package/dist/index.cjs17.js +4 -2
  17. package/dist/index.cjs19.js +0 -1
  18. package/dist/index.cjs2.js +6 -2
  19. package/dist/index.cjs20.js +14 -102
  20. package/dist/index.cjs21.js +93 -118
  21. package/dist/index.cjs22.js +127 -5
  22. package/dist/index.cjs23.js +5 -25
  23. package/dist/index.cjs24.js +25 -5
  24. package/dist/index.cjs27.js +44 -3
  25. package/dist/index.cjs28.js +6 -5
  26. package/dist/index.cjs29.js +5 -27
  27. package/dist/index.cjs3.js +41 -22
  28. package/dist/index.cjs31.js +27 -7
  29. package/dist/index.cjs32.js +9 -0
  30. package/dist/index.cjs33.js +5 -0
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index12.mjs +35 -21
  33. package/dist/index13.mjs +27 -15
  34. package/dist/index14.mjs +72 -56
  35. package/dist/index15.mjs +122 -75
  36. package/dist/index16.mjs +45 -36
  37. package/dist/index17.mjs +4 -2
  38. package/dist/index19.mjs +0 -1
  39. package/dist/index2.mjs +6 -2
  40. package/dist/index20.mjs +14 -102
  41. package/dist/index21.mjs +93 -117
  42. package/dist/index22.mjs +126 -5
  43. package/dist/index23.mjs +5 -25
  44. package/dist/index24.mjs +25 -5
  45. package/dist/index27.mjs +44 -3
  46. package/dist/index28.mjs +6 -5
  47. package/dist/index29.mjs +5 -27
  48. package/dist/index3.mjs +41 -22
  49. package/dist/index31.mjs +27 -7
  50. package/dist/index32.mjs +10 -0
  51. package/dist/index33.mjs +6 -0
  52. package/dist/utils/batchOnNextTick.d.ts +16 -0
  53. package/dist/utils/deepClone.d.ts +1 -1
  54. package/package.json +3 -3
package/dist/index13.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  import deepClone from "./index19.mjs";
2
- import match from "./index22.mjs";
2
+ import match from "./index23.mjs";
3
3
  import modify from "./index7.mjs";
4
- import queryId from "./index24.mjs";
4
+ import queryId from "./index20.mjs";
5
5
  import isEqual from "./index6.mjs";
6
- import getIndexInfo from "./index21.mjs";
6
+ import getIndexInfo from "./index22.mjs";
7
7
  import getMatchingKeys from "./index26.mjs";
8
8
  import sortItems from "./index25.mjs";
9
- import project from "./index23.mjs";
9
+ import project from "./index24.mjs";
10
10
  class AsyncDataAdapter {
11
11
  options;
12
12
  id;
@@ -119,6 +119,13 @@ class AsyncDataAdapter {
119
119
  getQueryError,
120
120
  getQueryResult,
121
121
  onQueryStateChange,
122
+ executeQuery: async (selector, options) => {
123
+ await ready;
124
+ registerQuery(selector, options);
125
+ const result = getQueryResult(selector, options);
126
+ unregisterQuery(selector, options);
127
+ return result;
128
+ },
122
129
  dispose: async () => {
123
130
  this.storageAdapters.delete(collection.name);
124
131
  this.queries.delete(collection.name);
@@ -134,11 +141,8 @@ class AsyncDataAdapter {
134
141
  const storage = this.storageAdapters.get(collectionName);
135
142
  if (!storage)
136
143
  throw new Error(`No persistence adapter for collection ${collectionName}`);
137
- await Promise.all([
138
- storage.createIndex("id"),
139
- ...indices.map((index) => storage.createIndex(index))
140
- ]);
141
144
  await storage.setup();
145
+ await Promise.all(indices.map((index) => storage.createIndex(index)));
142
146
  }
143
147
  ensureStorageAdapter(name) {
144
148
  if (this.storageAdapters.has(name))
@@ -199,20 +203,28 @@ class AsyncDataAdapter {
199
203
  rec.items = items;
200
204
  }
201
205
  async getIndexInfo(collectionName, selector) {
202
- const storage = this.storageAdapters.get(collectionName);
203
- if (!storage)
206
+ const storageAdapter = this.storageAdapters.get(collectionName);
207
+ if (!storageAdapter)
204
208
  throw new Error(`No persistence adapter for collection ${collectionName}`);
205
209
  if (selector != null && Object.keys(selector).length === 1 && "id" in selector && typeof selector.id !== "object") {
206
- return { matched: true, ids: [selector.id], optimizedSelector: {} };
210
+ return {
211
+ matched: true,
212
+ ids: [selector.id].filter(Boolean),
213
+ optimizedSelector: {}
214
+ };
207
215
  }
208
216
  if (selector == null) {
209
- return { matched: false, ids: [], optimizedSelector: {} };
217
+ return {
218
+ matched: false,
219
+ ids: [],
220
+ optimizedSelector: {}
221
+ };
210
222
  }
211
223
  const indices = this.collectionIndices.get(collectionName) ?? [];
212
224
  return getIndexInfo(indices.map((field) => async (flatSelector) => {
213
225
  if (!Object.hasOwnProperty.call(flatSelector, field))
214
226
  return { matched: false };
215
- const index = await storage.readIndex(field);
227
+ const index = await storageAdapter.readIndex(field);
216
228
  const fieldSelector = flatSelector[field];
217
229
  const filtersForNull = fieldSelector == null || fieldSelector.$exists === false;
218
230
  const keys = filtersForNull ? { include: null, exclude: [...index.keys()].filter((key) => key != null) } : getMatchingKeys(field, flatSelector);
@@ -314,8 +326,8 @@ class AsyncDataAdapter {
314
326
  const storage = this.storageAdapters.get(collectionName);
315
327
  if (!storage)
316
328
  throw new Error(`No persistence adapter for collection ${collectionName}`);
317
- const existing = await this.executeQuery(collectionName, { id: newItem.id }, { limit: 1 });
318
- if (existing.length > 0)
329
+ const existingItems = await storage.readIds([newItem.id]);
330
+ if (existingItems.length > 0)
319
331
  throw new Error(`Item with id ${String(newItem.id)} already exists`);
320
332
  await storage.insert([newItem]);
321
333
  await this.checkQueryUpdates(collectionName, [newItem]);
package/dist/index14.mjs CHANGED
@@ -1,17 +1,23 @@
1
- import queryId from "./index24.mjs";
1
+ import queryId from "./index20.mjs";
2
2
  import randomId from "./index8.mjs";
3
+ import batchOnNextTick from "./index27.mjs";
3
4
  class WorkerDataAdapter {
4
5
  worker;
5
6
  options;
6
7
  id;
7
8
  isDisposed = false;
8
9
  workerReady;
10
+ log = () => {
11
+ };
9
12
  collectionReady = /* @__PURE__ */ new Map();
13
+ batchExecutionHelpers = /* @__PURE__ */ new Map();
10
14
  queries = {};
11
15
  constructor(worker, options) {
12
16
  this.worker = worker;
13
17
  this.options = options;
14
18
  this.id = this.options.id || "default-worker-data-adapter";
19
+ if (this.options.log)
20
+ this.log = this.options.log;
15
21
  this.workerReady = new Promise((resolve, reject) => {
16
22
  const timeoutId = setTimeout(() => {
17
23
  reject(new Error("WorkerDataAdapter initialization timed out"));
@@ -50,6 +56,7 @@ class WorkerDataAdapter {
50
56
  return;
51
57
  if (id !== messageId)
52
58
  return;
59
+ this.log(method, "result", data ?? error);
53
60
  if (error) {
54
61
  reject(error);
55
62
  } else {
@@ -66,88 +73,59 @@ class WorkerDataAdapter {
66
73
  });
67
74
  });
68
75
  }
69
- queryListeners(collectionName, query, listeners) {
70
- if (listeners != null) {
71
- return this.updateQuery(collectionName, query, { listeners });
72
- }
73
- const id = queryId(query.selector, query.options);
74
- const collectionQueries = this.queries[collectionName];
75
- if (!collectionQueries)
76
- return 0;
77
- const existing = collectionQueries.get(id);
78
- return existing?.listeners || 0;
76
+ enqueueBatched(collectionName, method, args) {
77
+ const helper = this.batchExecutionHelpers.get(collectionName);
78
+ if (!helper)
79
+ throw new Error(`Collection "${collectionName}" is not registered in WorkerDataAdapter`);
80
+ return helper.enqueue(method, args);
79
81
  }
82
+ // ---------- end batching integration ----------
80
83
  updateQuery(collectionName, query, update) {
81
84
  const id = queryId(query.selector, query.options);
82
85
  const collectionQueries = this.queries[collectionName];
83
86
  if (!collectionQueries)
84
87
  return;
85
88
  const existing = collectionQueries.get(id);
86
- collectionQueries.set(id, {
87
- listeners: 0,
89
+ const newState = {
88
90
  state: "active",
89
91
  error: null,
90
92
  items: [],
93
+ stateChangeCallbacks: [],
94
+ eventHandler: existing?.eventHandler,
91
95
  ...existing,
92
96
  ...update
93
- });
97
+ };
98
+ collectionQueries.set(id, newState);
94
99
  this.queries[collectionName] = collectionQueries;
95
100
  }
96
- createCollectionBackend(collection, indices = []) {
101
+ createCollectionBackend(collection, indices) {
97
102
  this.queries[collection.name] = /* @__PURE__ */ new Map();
98
103
  void this.exec("registerCollection", collection.name, indices);
99
104
  this.collectionReady.set(collection.name, this.exec("isReady", collection.name));
105
+ this.batchExecutionHelpers.set(collection.name, batchOnNextTick(async (method, args) => this.exec(method, collection.name, args)));
100
106
  return {
101
107
  insert: async (item) => {
102
- return this.exec("insert", collection.name, item);
108
+ return this.enqueueBatched(collection.name, "insert", [item]);
103
109
  },
104
110
  updateOne: async (selector, modifier) => {
105
- return this.exec("updateOne", collection.name, selector, modifier);
111
+ return this.enqueueBatched(collection.name, "updateOne", [selector, modifier]);
106
112
  },
107
113
  updateMany: async (selector, modifier) => {
108
- return this.exec("updateMany", collection.name, selector, modifier);
114
+ return this.enqueueBatched(collection.name, "updateMany", [selector, modifier]);
109
115
  },
110
116
  replaceOne: async (selector, replacement) => {
111
- return this.exec("replaceOne", collection.name, selector, replacement);
117
+ return this.enqueueBatched(collection.name, "replaceOne", [selector, replacement]);
112
118
  },
113
119
  removeOne: async (selector) => {
114
- return this.exec("removeOne", collection.name, selector);
120
+ return this.enqueueBatched(collection.name, "removeOne", [selector]);
115
121
  },
116
122
  removeMany: async (selector) => {
117
- return this.exec("removeMany", collection.name, selector);
123
+ return this.enqueueBatched(collection.name, "removeMany", [selector]);
118
124
  },
119
125
  // methods for registering and unregistering queries that will be called from the collection during find/findOne
120
126
  registerQuery: (selector, options) => {
121
- const listeners = this.queryListeners(collection.name, { selector, options });
122
- if (listeners === 0) {
123
- this.updateQuery(collection.name, { selector, options }, { state: "active", error: null, items: [] });
124
- void this.exec("registerQuery", collection.name, selector, options);
125
- }
126
- this.queryListeners(collection.name, { selector, options }, listeners + 1);
127
- },
128
- unregisterQuery: (selector, options) => {
129
- setTimeout(() => {
130
- const listeners = this.queryListeners(collection.name, { selector, options });
131
- const newListeners = Math.max(0, listeners - 1);
132
- if (newListeners === 0) {
133
- this.queries[collection.name]?.delete(queryId(selector, options));
134
- void this.exec("unregisterQuery", collection.name, selector, options);
135
- }
136
- }, 0);
137
- },
138
- getQueryState: (selector, options) => {
139
- const query = this.queries[collection.name]?.get(queryId(selector, options));
140
- return query?.state || "active";
141
- },
142
- getQueryError: (selector, options) => {
143
- const query = this.queries[collection.name]?.get(queryId(selector, options));
144
- return query?.error || null;
145
- },
146
- getQueryResult: (selector, options) => {
147
- const query = this.queries[collection.name]?.get(queryId(selector, options));
148
- return query?.items || [];
149
- },
150
- onQueryStateChange: (selector, options, callback) => {
127
+ this.updateQuery(collection.name, { selector, options }, { state: "active", error: null, items: [] });
128
+ void this.exec("registerQuery", collection.name, selector, options);
151
129
  const handler = (event) => {
152
130
  const { type, data, workerId, error } = event.data;
153
131
  if (type !== "queryUpdate")
@@ -159,21 +137,59 @@ class WorkerDataAdapter {
159
137
  return;
160
138
  if (collectionName !== collection.name)
161
139
  return;
162
- if (JSON.stringify(responseSelector) !== JSON.stringify(selector))
163
- return;
164
- if (JSON.stringify(responseOptions) !== JSON.stringify(options))
140
+ if (queryId(responseSelector, responseOptions) !== queryId(selector, options))
165
141
  return;
142
+ this.log("queryUpdate", responseSelector, responseOptions, state, data ?? error);
166
143
  this.updateQuery(collection.name, {
167
144
  selector: responseSelector,
168
145
  options: responseOptions
169
146
  }, { state, error, items });
170
- callback(state);
147
+ const query = this.queries[collection.name]?.get(queryId(selector, options));
148
+ if (!query)
149
+ return;
150
+ query.stateChangeCallbacks.forEach((callback) => callback(state));
171
151
  };
172
152
  this.worker.addEventListener("message", handler);
153
+ this.updateQuery(collection.name, { selector, options }, { eventHandler: handler });
154
+ },
155
+ unregisterQuery: (selector, options) => {
156
+ const qid = queryId(selector, options);
157
+ const query = this.queries[collection.name]?.get(qid);
158
+ if (query?.eventHandler) {
159
+ this.worker.removeEventListener("message", query.eventHandler);
160
+ }
161
+ this.queries[collection.name]?.delete(qid);
162
+ void this.exec("unregisterQuery", collection.name, selector, options);
163
+ },
164
+ getQueryState: (selector, options) => {
165
+ const query = this.queries[collection.name]?.get(queryId(selector, options));
166
+ return query?.state || "active";
167
+ },
168
+ getQueryError: (selector, options) => {
169
+ const query = this.queries[collection.name]?.get(queryId(selector, options));
170
+ return query?.error || null;
171
+ },
172
+ getQueryResult: (selector, options) => {
173
+ const query = this.queries[collection.name]?.get(queryId(selector, options));
174
+ return query?.items || [];
175
+ },
176
+ onQueryStateChange: (selector, options, callback) => {
177
+ this.updateQuery(collection.name, { selector, options }, {
178
+ stateChangeCallbacks: [
179
+ ...this.queries[collection.name]?.get(queryId(selector, options))?.stateChangeCallbacks || [],
180
+ callback
181
+ ]
182
+ });
173
183
  return () => {
174
- this.worker.removeEventListener("message", handler);
184
+ const currentCallbacks = this.queries[collection.name]?.get(queryId(selector, options))?.stateChangeCallbacks;
185
+ if (!currentCallbacks)
186
+ throw new Error("State change callbacks are not defined!");
187
+ this.updateQuery(collection.name, { selector, options }, {
188
+ stateChangeCallbacks: currentCallbacks.filter((existingCallback) => existingCallback !== callback)
189
+ });
175
190
  };
176
191
  },
192
+ executeQuery: (selector, options) => this.exec("executeQuery", collection.name, selector, options),
177
193
  // lifecycle methods
178
194
  dispose: async () => {
179
195
  await this.exec("unregisterCollection", collection.name);
package/dist/index15.mjs CHANGED
@@ -1,16 +1,19 @@
1
1
  import deepClone from "./index19.mjs";
2
- import match from "./index22.mjs";
2
+ import match from "./index23.mjs";
3
3
  import modify from "./index7.mjs";
4
- import queryId from "./index24.mjs";
4
+ import queryId from "./index20.mjs";
5
5
  import isEqual from "./index6.mjs";
6
- import getIndexInfo from "./index21.mjs";
6
+ import getIndexInfo from "./index22.mjs";
7
7
  import getMatchingKeys from "./index26.mjs";
8
8
  import sortItems from "./index25.mjs";
9
- import project from "./index23.mjs";
9
+ import project from "./index24.mjs";
10
+ import compact from "./index28.mjs";
10
11
  class WorkerDataAdapterHost {
11
12
  workerContext;
12
13
  options;
13
14
  id;
15
+ log = () => {
16
+ };
14
17
  storageAdapters = /* @__PURE__ */ new Map();
15
18
  storageAdapterReady = /* @__PURE__ */ new Map();
16
19
  collectionIndices = /* @__PURE__ */ new Map();
@@ -25,6 +28,8 @@ class WorkerDataAdapterHost {
25
28
  if (this.options.onError) {
26
29
  this.onError = this.options.onError;
27
30
  }
31
+ if (this.options.log)
32
+ this.log = this.options.log;
28
33
  if (typeof addEventListener === "undefined" || typeof postMessage === "undefined") {
29
34
  throw new TypeError("WorkerDataAdapterHost can only be used in a Web Worker context");
30
35
  }
@@ -49,6 +54,8 @@ class WorkerDataAdapterHost {
49
54
  this.respond(id, null, new Error(`Method ${method} not found`));
50
55
  return;
51
56
  }
57
+ this.log(method, ...args);
58
+ await this.isReady(args[0]);
52
59
  try {
53
60
  const result = await fn.apply(this, args);
54
61
  this.respond(id, result);
@@ -63,7 +70,7 @@ class WorkerDataAdapterHost {
63
70
  if (selector != null && Object.keys(selector).length === 1 && "id" in selector && typeof selector.id !== "object") {
64
71
  return {
65
72
  matched: true,
66
- ids: [selector.id],
73
+ ids: compact([selector.id]),
67
74
  optimizedSelector: {}
68
75
  };
69
76
  }
@@ -148,6 +155,8 @@ class WorkerDataAdapterHost {
148
155
  }
149
156
  }
150
157
  async executeQuery(collectionName, selector, options) {
158
+ if (selector === null)
159
+ return [];
151
160
  const items = await this.queryItems(collectionName, selector || {});
152
161
  const { sort, skip, limit, fields } = options || {};
153
162
  const sorted = sort ? sortItems(items, sort) : items;
@@ -213,10 +222,7 @@ class WorkerDataAdapterHost {
213
222
  if (!storageAdapter)
214
223
  throw new Error(`No persistence adapter for collection ${collectionName}`);
215
224
  const setupPromise = (async () => {
216
- await Promise.all([
217
- storageAdapter.createIndex("id"),
218
- ...indices.map((index) => storageAdapter.createIndex(index))
219
- ]);
225
+ await Promise.all(indices.map((index) => storageAdapter.createIndex(index)));
220
226
  await storageAdapter.setup();
221
227
  })();
222
228
  this.storageAdapterReady.set(collectionName, setupPromise);
@@ -237,101 +243,142 @@ class WorkerDataAdapterHost {
237
243
  throw new Error(`Collection ${collectionName} not initialized!`);
238
244
  this.queries.get(collectionName)?.delete(id);
239
245
  };
240
- insert = async (collectionName, newItem) => {
246
+ insert = async (collectionName, input) => {
241
247
  const storageAdapter = this.storageAdapters.get(collectionName);
242
248
  if (!storageAdapter)
243
249
  throw new Error(`No persistence adapter for collection ${collectionName}`);
244
- const existingItems = await this.executeQuery(collectionName, { id: newItem.id }, { limit: 1 });
245
- if (existingItems.length > 0) {
246
- throw new Error(`Item with id ${newItem.id} already exists`);
247
- }
248
- await storageAdapter.insert([newItem]);
249
- await this.checkQueryUpdates(collectionName, [newItem]);
250
- return newItem;
251
- };
252
- updateOne = async (collectionName, selector, modifier) => {
253
- const storageAdapter = this.storageAdapters.get(collectionName);
254
- if (!storageAdapter)
255
- throw new Error(`No persistence adapter for collection ${collectionName}`);
256
- const item = await this.executeQuery(collectionName, selector, { limit: 1 }).then((items) => items[0] ?? null);
257
- const { $setOnInsert, ...restModifier } = modifier;
258
- if (item == null)
259
- return [];
260
- const modifiedItem = modify(deepClone(item), restModifier);
261
- if (item.id !== modifiedItem.id) {
262
- const existingItems = await this.executeQuery(collectionName, { id: modifiedItem.id }, { limit: 1 });
263
- if (existingItems.length > 0) {
264
- throw new Error(`Item with id ${modifiedItem.id} already exists`);
250
+ const existingItems = await this.executeQuery(collectionName, { id: { $in: input.map((i) => i[0].id) } });
251
+ const result = input.map(([item]) => {
252
+ if (item.id == null)
253
+ return new Error("Item must have an id");
254
+ if (existingItems.some((existing) => existing.id === item.id)) {
255
+ return new Error(`Item with id ${item.id} already exists`);
265
256
  }
266
- }
267
- await storageAdapter.replace([modifiedItem]);
268
- await this.checkQueryUpdates(collectionName, [modifiedItem]);
269
- return [modifiedItem];
257
+ return item;
258
+ });
259
+ const newItems = result.filter((item) => !(item instanceof Error));
260
+ await storageAdapter.insert(newItems);
261
+ await this.checkQueryUpdates(collectionName, newItems);
262
+ return result;
270
263
  };
271
- updateMany = async (collectionName, selector, modifier) => {
264
+ updateOne = async (collectionName, parameters) => {
272
265
  const storageAdapter = this.storageAdapters.get(collectionName);
273
266
  if (!storageAdapter)
274
267
  throw new Error(`No persistence adapter for collection ${collectionName}`);
275
- const items = await this.executeQuery(collectionName, selector);
276
- if (items.length === 0)
277
- return [];
278
- const { $setOnInsert, ...restModifier } = modifier;
279
- const changedItems = await Promise.all(items.map(async (item) => {
268
+ const previousItems = [];
269
+ const result = await Promise.all(parameters.map(async ([selector, modifier]) => {
270
+ const item = await this.executeQuery(collectionName, selector, { limit: 1 }).then((items) => items[0] ?? null);
271
+ const { $setOnInsert, ...restModifier } = modifier;
272
+ if (item == null)
273
+ return [];
274
+ previousItems.push(item);
280
275
  const modifiedItem = modify(deepClone(item), restModifier);
281
276
  if (item.id !== modifiedItem.id) {
282
277
  const existingItems = await this.executeQuery(collectionName, { id: modifiedItem.id }, { limit: 1 });
283
278
  if (existingItems.length > 0) {
284
- throw new Error(`Item with id ${modifiedItem.id} already exists`);
279
+ return new Error(`Item with id ${modifiedItem.id} already exists`);
285
280
  }
286
281
  }
287
- return modifiedItem;
282
+ return [modifiedItem];
288
283
  }));
289
- await storageAdapter.replace(changedItems);
290
- await this.checkQueryUpdates(collectionName, changedItems);
291
- return changedItems;
284
+ const modifiedItems = compact(result.filter((item) => !(item instanceof Error)).flat());
285
+ if (modifiedItems.length > 0) {
286
+ await storageAdapter.replace(modifiedItems);
287
+ await this.checkQueryUpdates(collectionName, [...modifiedItems, ...previousItems]);
288
+ }
289
+ return result;
292
290
  };
293
- replaceOne = async (collectionName, selector, replacement) => {
291
+ updateMany = async (collectionName, parameters) => {
294
292
  const storageAdapter = this.storageAdapters.get(collectionName);
295
293
  if (!storageAdapter)
296
294
  throw new Error(`No persistence adapter for collection ${collectionName}`);
297
- const item = await this.executeQuery(collectionName, selector, { limit: 1 }).then((items) => items[0] ?? null);
298
- if (item == null)
299
- return [];
300
- const modifiedItem = {
301
- ...replacement,
302
- id: replacement.id ?? item.id
303
- };
304
- if (item.id !== modifiedItem.id) {
305
- const existingItems = await this.executeQuery(collectionName, { id: modifiedItem.id }, { limit: 1 });
306
- if (existingItems.length > 0) {
307
- throw new Error(`Item with id ${modifiedItem.id} already exists`);
295
+ const previousItems = [];
296
+ const result = await Promise.all(parameters.map(async ([selector, modifier]) => {
297
+ const items = await this.executeQuery(collectionName, selector);
298
+ if (items.length === 0)
299
+ return [];
300
+ const { $setOnInsert, ...restModifier } = modifier;
301
+ try {
302
+ const changedItems = await Promise.all(items.map(async (item) => {
303
+ const modifiedItem = modify(deepClone(item), restModifier);
304
+ if (item.id !== modifiedItem.id) {
305
+ const existingItems = await this.executeQuery(collectionName, { id: modifiedItem.id }, { limit: 1 });
306
+ if (existingItems.length > 0) {
307
+ throw new Error(`Item with id ${modifiedItem.id} already exists`);
308
+ }
309
+ }
310
+ previousItems.push(item);
311
+ return modifiedItem;
312
+ }));
313
+ return changedItems;
314
+ } catch (error) {
315
+ return error;
308
316
  }
317
+ }));
318
+ const modifiedItems = compact(result.filter((item) => !(item instanceof Error)).flat());
319
+ if (modifiedItems.length > 0) {
320
+ await storageAdapter.replace(modifiedItems);
321
+ await this.checkQueryUpdates(collectionName, [...modifiedItems, ...previousItems]);
309
322
  }
310
- await storageAdapter.replace([modifiedItem]);
311
- await this.checkQueryUpdates(collectionName, [modifiedItem]);
312
- return [modifiedItem];
323
+ return result;
313
324
  };
314
- removeOne = async (collectionName, selector) => {
325
+ replaceOne = async (collectionName, parameters) => {
315
326
  const storageAdapter = this.storageAdapters.get(collectionName);
316
327
  if (!storageAdapter)
317
328
  throw new Error(`No persistence adapter for collection ${collectionName}`);
318
- const item = await this.executeQuery(collectionName, selector, { limit: 1 }).then((items) => items[0] ?? null);
319
- if (item == null)
320
- return [];
321
- await storageAdapter.remove([item]);
322
- await this.checkQueryUpdates(collectionName, [item]);
323
- return [item];
329
+ const previousItems = [];
330
+ const result = await Promise.all(parameters.map(async ([selector, replacement]) => {
331
+ const item = await this.executeQuery(collectionName, selector, { limit: 1 }).then((items) => items[0] ?? null);
332
+ if (item == null)
333
+ return [];
334
+ previousItems.push(item);
335
+ const modifiedItem = {
336
+ ...replacement,
337
+ id: replacement.id ?? item.id
338
+ };
339
+ if (item.id !== modifiedItem.id) {
340
+ const existingItems = await this.executeQuery(collectionName, { id: modifiedItem.id }, { limit: 1 });
341
+ if (existingItems.length > 0) {
342
+ return new Error(`Item with id ${modifiedItem.id} already exists`);
343
+ }
344
+ }
345
+ return [modifiedItem];
346
+ }));
347
+ const modifiedItems = compact(result.filter((item) => !(item instanceof Error)).flat());
348
+ if (modifiedItems.length > 0) {
349
+ await storageAdapter.replace(modifiedItems);
350
+ await this.checkQueryUpdates(collectionName, [...modifiedItems, ...previousItems]);
351
+ }
352
+ return result;
324
353
  };
325
- removeMany = async (collectionName, selector) => {
354
+ removeOne = async (collectionName, selectors) => {
326
355
  const storageAdapter = this.storageAdapters.get(collectionName);
327
356
  if (!storageAdapter)
328
357
  throw new Error(`No persistence adapter for collection ${collectionName}`);
329
- const items = await this.executeQuery(collectionName, selector);
330
- if (items.length === 0)
331
- return [];
332
- await storageAdapter.remove(items);
333
- await this.checkQueryUpdates(collectionName, items);
334
- return items;
358
+ const result = await Promise.all(selectors.map(async ([selector]) => {
359
+ const item = await this.executeQuery(collectionName, selector, { limit: 1 }).then((items2) => items2[0] ?? null);
360
+ if (item == null)
361
+ return [];
362
+ return [item];
363
+ }));
364
+ const items = result.flat();
365
+ if (items.length > 0) {
366
+ await storageAdapter.remove(items);
367
+ await this.checkQueryUpdates(collectionName, items);
368
+ }
369
+ return result;
370
+ };
371
+ removeMany = async (collectionName, selectors) => {
372
+ const storageAdapter = this.storageAdapters.get(collectionName);
373
+ if (!storageAdapter)
374
+ throw new Error(`No persistence adapter for collection ${collectionName}`);
375
+ const result = await Promise.all(selectors.map(async ([selector]) => this.executeQuery(collectionName, selector)));
376
+ const items = result.flat();
377
+ if (items.length > 0) {
378
+ await storageAdapter.remove(items);
379
+ await this.checkQueryUpdates(collectionName, items);
380
+ }
381
+ return result;
335
382
  };
336
383
  isReady = async (collectionName) => {
337
384
  return this.storageAdapterReady.get(collectionName);