@signaldb/core 2.0.0-beta.13 → 2.0.0-beta.14

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 (82) hide show
  1. package/dist/.vite/manifest.json +72 -46
  2. package/dist/AsyncDataAdapter.d.ts +11 -2
  3. package/dist/Collection/Cursor.d.ts +11 -1
  4. package/dist/Collection/Observer.d.ts +31 -0
  5. package/dist/DataAdapter.d.ts +15 -2
  6. package/dist/WorkerDataAdapter.d.ts +25 -2
  7. package/dist/WorkerDataAdapterHost.d.ts +2 -0
  8. package/dist/index.cjs.js +15 -15
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.mjs +15 -15
  11. package/dist/index10.cjs.js +7 -19
  12. package/dist/index10.mjs +7 -19
  13. package/dist/index11.cjs.js +19 -41
  14. package/dist/index11.mjs +19 -41
  15. package/dist/index12.cjs.js +41 -18
  16. package/dist/index12.mjs +41 -18
  17. package/dist/index13.cjs.js +18 -41
  18. package/dist/index13.mjs +18 -41
  19. package/dist/index14.cjs.js +39 -80
  20. package/dist/index14.mjs +39 -80
  21. package/dist/index15.cjs.js +82 -11
  22. package/dist/index15.mjs +82 -11
  23. package/dist/index16.cjs.js +11 -132
  24. package/dist/index16.mjs +11 -132
  25. package/dist/index17.cjs.js +127 -38
  26. package/dist/index17.mjs +127 -38
  27. package/dist/index18.cjs.js +43 -11
  28. package/dist/index18.mjs +43 -11
  29. package/dist/index19.cjs.js +10 -18
  30. package/dist/index19.mjs +11 -19
  31. package/dist/index20.cjs.js +19 -33
  32. package/dist/index20.mjs +19 -33
  33. package/dist/index21.cjs.js +33 -31
  34. package/dist/index21.mjs +33 -31
  35. package/dist/index22.cjs.js +31 -21
  36. package/dist/index22.mjs +31 -21
  37. package/dist/index23.cjs.js +16 -14
  38. package/dist/index23.mjs +16 -14
  39. package/dist/index24.cjs.js +15 -338
  40. package/dist/index24.mjs +15 -338
  41. package/dist/index25.cjs.js +122 -554
  42. package/dist/index25.mjs +121 -554
  43. package/dist/index26.cjs.js +34 -7
  44. package/dist/index26.mjs +34 -7
  45. package/dist/index27.cjs.js +339 -7
  46. package/dist/index27.mjs +339 -7
  47. package/dist/index28.cjs.js +550 -83
  48. package/dist/index28.mjs +550 -82
  49. package/dist/index29.cjs.js +8 -422
  50. package/dist/index29.mjs +8 -422
  51. package/dist/index30.cjs.js +7 -68
  52. package/dist/index30.mjs +7 -68
  53. package/dist/index31.cjs.js +86 -28
  54. package/dist/index31.mjs +85 -28
  55. package/dist/index32.cjs.js +446 -278
  56. package/dist/index32.mjs +446 -278
  57. package/dist/index33.cjs.js +68 -17
  58. package/dist/index33.mjs +68 -17
  59. package/dist/index34.cjs.js +460 -304
  60. package/dist/index34.mjs +460 -304
  61. package/dist/index35.cjs.js +14 -532
  62. package/dist/index35.mjs +14 -532
  63. package/dist/index36.cjs.js +389 -0
  64. package/dist/index36.mjs +389 -0
  65. package/dist/index37.cjs.js +539 -0
  66. package/dist/index37.mjs +539 -0
  67. package/dist/index4.cjs.js +199 -140
  68. package/dist/index4.mjs +195 -140
  69. package/dist/index5.cjs.js +152 -253
  70. package/dist/index5.mjs +152 -253
  71. package/dist/index6.cjs.js +267 -89
  72. package/dist/index6.mjs +267 -89
  73. package/dist/index7.cjs.js +121 -29
  74. package/dist/index7.mjs +121 -29
  75. package/dist/index8.cjs.js +29 -8
  76. package/dist/index8.mjs +29 -8
  77. package/dist/index9.cjs.js +8 -7
  78. package/dist/index9.mjs +8 -7
  79. package/dist/utils/incrementalQueryUpdate.d.ts +60 -0
  80. package/dist/utils/projectItems.d.ts +12 -0
  81. package/dist/utils/queryDelta.d.ts +83 -0
  82. package/package.json +1 -1
@@ -1,90 +1,557 @@
1
- //#region src/utils/reactiveOrAsync.ts
1
+ const require_Cursor = require("./index6.cjs.js");
2
+ const require_EventEmitter = require("./index7.cjs.js");
3
+ const require_createSignal = require("./index8.cjs.js");
4
+ const require_randomId = require("./index9.cjs.js");
5
+ require("./index15.cjs.js");
6
+ const require_deepClone = require("./index18.cjs.js");
7
+ const require_modify = require("./index20.cjs.js");
8
+ const require_queryId = require("./index26.cjs.js");
9
+ const require_DefaultDataAdapter = require("./index27.cjs.js");
10
+ //#region src/Collection/index.ts
2
11
  /**
3
- * A generator helper that makes TypeScript infer the “synchronous value type” for maybe-async expressions.
4
- *
5
- * Usage:
6
- * const doc = yield* unwrap(Collection.findOne(...))
7
- * const list = yield* unwrap(Collection.find(...).fetch())
8
- *
9
- * Runtime note:
10
- * This does not “unwrap” Promises by itself. It yields the value/Promise to the runner and returns the
11
- * value that the runner feeds back via `.next(...)`.
12
- * @param value The value (or Promise of a value) to yield to the runner.
13
- * @returns A generator that yields `value` and resolves to the runner-supplied unwrapped `T`.
12
+ * Represents a collection of data items with support for in-memory operations,
13
+ * persistence, reactivity, and event-based notifications. The collection provides
14
+ * CRUD operations, observer patterns, and batch operations.
15
+ * @template T - The type of the items stored in the collection.
16
+ * @template I - The type of the unique identifier for the items.
17
+ * @template U - The transformed item type after applying transformations (default is T).
14
18
  */
15
- function unwrap(value) {
16
- return (function* () {
17
- return yield value;
18
- })();
19
- }
20
- /**
21
- * Internal: checks for thenables (Promise-like).
22
- * @param value The value to test.
23
- * @returns `true` if `value` looks like a Promise/thenable.
24
- */
25
- function isThenable(value) {
26
- return typeof value === "object" && value !== null && typeof value.then === "function";
27
- }
28
- /**
29
- * Internal runner: executes a generator either synchronously (reactive) or asynchronously (imperative).
30
- *
31
- * - In sync mode, yielding a Promise is a programming error and throws.
32
- * - In async mode, yielded Promises are awaited.
33
- * @param thisArgument The `this` value to bind when invoking `gen`.
34
- * @param mode Execution mode options.
35
- * @param gen The generator workflow to run.
36
- * @returns The workflow result (a plain value in sync mode, or a Promise in async mode).
37
- */
38
- function runReactiveOrAsync(thisArgument, mode, gen) {
39
- const a = !!mode?.async;
40
- const it = gen.call(thisArgument, a);
41
- if (!a) {
42
- let step = it.next();
43
- while (!step.done) {
44
- const y = step.value;
45
- if (isThenable(y)) throw new Error("Promise yielded in sync flow");
46
- step = it.next(y);
19
+ var Collection = class Collection extends require_EventEmitter.default {
20
+ static collections = [];
21
+ static debugMode = false;
22
+ static batchOperationInProgress = false;
23
+ static fieldTracking = false;
24
+ static onCreationCallbacks = [];
25
+ static onDisposeCallbacks = [];
26
+ static getCollections() {
27
+ return Collection.collections;
28
+ }
29
+ static onCreation(callback) {
30
+ Collection.onCreationCallbacks.push(callback);
31
+ }
32
+ static onDispose(callback) {
33
+ Collection.onDisposeCallbacks.push(callback);
34
+ }
35
+ /**
36
+ * Enables debug mode for all collections.
37
+ */
38
+ static enableDebugMode = () => {
39
+ Collection.debugMode = true;
40
+ Collection.collections.forEach((collection) => {
41
+ collection.setDebugMode(true);
42
+ });
43
+ };
44
+ /**
45
+ * Enables field tracking for all collections.
46
+ * @param enable - A boolean indicating whether to enable field tracking.
47
+ */
48
+ static setFieldTracking = (enable) => {
49
+ Collection.fieldTracking = enable;
50
+ Collection.collections.forEach((collection) => {
51
+ collection.setFieldTracking(enable);
52
+ });
53
+ };
54
+ static batch(callback) {
55
+ Collection.batchOperationInProgress = true;
56
+ const execute = () => Collection.collections.reduce((memo, collection) => () => {
57
+ return collection.batch(memo);
58
+ }, callback)();
59
+ const afterBatch = () => {
60
+ Collection.batchOperationInProgress = false;
61
+ };
62
+ let maybePromise;
63
+ try {
64
+ maybePromise = execute();
65
+ } catch (error) {
66
+ afterBatch();
67
+ throw error;
47
68
  }
48
- return step.value;
49
- }
50
- return (async function() {
51
- let step = it.next();
52
- while (!step.done) {
53
- const y = step.value;
54
- const v = isThenable(y) ? await y : y;
55
- step = it.next(v);
69
+ if (maybePromise && typeof maybePromise.then === "function") return maybePromise.then(() => afterBatch(), (error) => {
70
+ afterBatch();
71
+ throw error;
72
+ });
73
+ else afterBatch();
74
+ }
75
+ name;
76
+ backend;
77
+ options;
78
+ isPullingSignal;
79
+ isPushingSignal;
80
+ readySignal;
81
+ debugMode;
82
+ batchOperationInProgress = false;
83
+ isDisposed = false;
84
+ postBatchCallbacks = /* @__PURE__ */ new Set();
85
+ fieldTracking = false;
86
+ queryListenersMap = /* @__PURE__ */ new Map();
87
+ settledQueriesSet = /* @__PURE__ */ new Set();
88
+ constructor(nameOrOptions, maybeDataAdapter, maybeOptions) {
89
+ super();
90
+ const name = typeof nameOrOptions === "string" ? nameOrOptions : nameOrOptions?.name || `${this.constructor.name}-${require_randomId.default()}`;
91
+ const options = typeof nameOrOptions === "string" ? maybeOptions || {} : nameOrOptions || {};
92
+ const persistence = options.persistence;
93
+ const dataAdapter = maybeDataAdapter || new require_DefaultDataAdapter.default({ ...persistence ? { storage: () => persistence } : {} });
94
+ Collection.collections.push(this);
95
+ this.name = name;
96
+ this.options = { ...options };
97
+ this.fieldTracking = this.options.fieldTracking ?? Collection.fieldTracking;
98
+ this.debugMode = this.options.enableDebugMode ?? Collection.debugMode;
99
+ this.isPullingSignal = require_createSignal.default(this.options.reactivity, false);
100
+ this.isPushingSignal = require_createSignal.default(this.options.reactivity, false);
101
+ this.readySignal = require_createSignal.default(this.options.reactivity, false);
102
+ this.backend = dataAdapter.createCollectionBackend(this, this.options.indices ?? []);
103
+ this.backend.isReady().then(() => {
104
+ this.readySignal.set(true);
105
+ }).catch(() => {});
106
+ Collection.onCreationCallbacks.forEach((callback) => callback(this));
107
+ }
108
+ isBatchOperationInProgress() {
109
+ return Collection.batchOperationInProgress || this.batchOperationInProgress;
110
+ }
111
+ /**
112
+ * Checks whether the collection is currently performing a pull operation
113
+ * ⚡️ this function is reactive!
114
+ * (loading data from the persistence adapter).
115
+ * @returns A boolean indicating if the collection is in the process of pulling data.
116
+ */
117
+ isPulling() {
118
+ return this.isPullingSignal.get() ?? false;
119
+ }
120
+ /**
121
+ * Checks whether the collection is currently performing a push operation
122
+ * ⚡️ this function is reactive!
123
+ * (saving data to the persistence adapter).
124
+ * @returns A boolean indicating if the collection is in the process of pushing data.
125
+ */
126
+ isPushing() {
127
+ return this.isPushingSignal.get() ?? false;
128
+ }
129
+ /**
130
+ * Checks whether the collection is currently performing either a pull or push operation,
131
+ * ⚡️ this function is reactive!
132
+ * indicating that it is loading or saving data.
133
+ * @returns A boolean indicating if the collection is in the process of loading or saving data.
134
+ */
135
+ isLoading() {
136
+ const isPulling = this.isPulling();
137
+ const isPushing = this.isPushing();
138
+ return isPulling || isPushing;
139
+ }
140
+ /**
141
+ * Retrieves the current debug mode status of the collection.
142
+ * @returns A boolean indicating whether debug mode is enabled for the collection.
143
+ */
144
+ getDebugMode() {
145
+ return this.debugMode;
146
+ }
147
+ /**
148
+ * Enables or disables debug mode for the collection.
149
+ * When debug mode is enabled, additional debugging information and events are emitted.
150
+ * @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) debug mode.
151
+ */
152
+ setDebugMode(enable) {
153
+ this.debugMode = enable;
154
+ }
155
+ /**
156
+ * Enables or disables field tracking for the collection.
157
+ * @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) field tracking.
158
+ */
159
+ setFieldTracking(enable) {
160
+ this.fieldTracking = enable;
161
+ }
162
+ /**
163
+ * Resolves when the persistence adapter finished initializing
164
+ * and the collection is ready to be used.
165
+ * @returns A promise that resolves when the collection is ready.
166
+ * @example
167
+ * ```ts
168
+ * const collection = new Collection({
169
+ * persistence: // ...
170
+ * })
171
+ * await collection.isReady()
172
+ *
173
+ * collection.insert({ name: 'Item 1' })
174
+ */
175
+ async ready() {
176
+ return this.backend.isReady();
177
+ }
178
+ /**
179
+ * Checks if the collection is ready.
180
+ * ⚡️ this function is reactive!
181
+ * @returns A boolean indicating whether the collection is ready.
182
+ */
183
+ isReady() {
184
+ return this.readySignal.get() ?? false;
185
+ }
186
+ profile(fn, measureFunction) {
187
+ if (!this.debugMode) return fn();
188
+ const startTime = performance.now();
189
+ const handleProfileEnd = (result) => {
190
+ measureFunction(performance.now() - startTime);
191
+ return result;
192
+ };
193
+ const maybePromise = fn();
194
+ return maybePromise instanceof Promise ? maybePromise.then(handleProfileEnd) : handleProfileEnd(maybePromise);
195
+ }
196
+ executeInDebugMode(fn) {
197
+ if (!this.debugMode) return;
198
+ fn((/* @__PURE__ */ new Error()).stack || "");
199
+ }
200
+ transform(item) {
201
+ if (!this.options.transform) return item;
202
+ return this.options.transform(item);
203
+ }
204
+ transformAll(items, fields) {
205
+ if (!this.options.transformAll) return items;
206
+ return this.options.transformAll(require_deepClone.default(items), fields);
207
+ }
208
+ getItem(selector, options) {
209
+ const itemsOrPromise = this.getItems(selector, {
210
+ ...options,
211
+ limit: 1
212
+ });
213
+ if (itemsOrPromise instanceof Promise) return itemsOrPromise.then((items) => {
214
+ return items[0] || void 0;
215
+ });
216
+ return itemsOrPromise[0];
217
+ }
218
+ getItems(selector, options) {
219
+ this.emit("getItems", selector);
220
+ return this.profile(() => {
221
+ if (!options?.async) return this.backend.getQueryResult(selector, options);
222
+ this.isPullingSignal.set(true);
223
+ return this.backend.executeQuery(selector, options).finally(() => {
224
+ this.isPullingSignal.set(false);
225
+ });
226
+ }, (measuredTime) => this.executeInDebugMode((callstack) => this.emit("_debug.getItems", callstack, selector, measuredTime)));
227
+ }
228
+ async withPushState(asyncFunction) {
229
+ this.isPushingSignal.set(true);
230
+ try {
231
+ return await asyncFunction();
232
+ } finally {
233
+ this.isPushingSignal.set(false);
56
234
  }
57
- return step.value;
58
- })();
59
- }
60
- /**
61
- * Factory that turns a generator workflow into a callable method that can run in sync (reactive) or async mode.
62
- *
63
- * Call style:
64
- * fn(a, b) -> sync/reactive return
65
- * await fn(a, b, { async: true }) -> async return
66
- * @param gen Generator workflow. Receives `(a)` which indicates async mode and should `yield`/`yield* unwrap(...)`
67
- * any values that may be Promises.
68
- * @returns A callable method with overloads plus a `.generator` property for composition.
69
- */
70
- function reactiveOrAsync(gen) {
71
- /**
72
- * The generated method wrapper.
73
- * @param allArguments Method arguments, optionally ending with a `ModeOptions` object.
74
- * @returns The workflow result (sync) or a Promise of the result (async).
75
- */
76
- function method(...allArguments) {
77
- const last = allArguments.length > 0 ? allArguments.at(-1) : void 0;
78
- const hasMode = typeof last === "object" && last !== null && "async" in last;
79
- const mode = hasMode ? last : void 0;
80
- const parameters = hasMode ? allArguments.slice(0, -1) : allArguments;
81
- return runReactiveOrAsync(this, mode, function* (a) {
82
- return yield* gen.call(this, a, ...parameters);
235
+ }
236
+ queryListeners(query, listeners) {
237
+ const id = require_queryId.default(query.selector, query.options);
238
+ if (listeners != null) return this.queryListenersMap.set(id, listeners);
239
+ return this.queryListenersMap.get(id) ?? 0;
240
+ }
241
+ /**
242
+ * Disposes the collection, unregisters persistence adapters, clears memory, and
243
+ * cleans up all resources used by the collection.
244
+ * @returns A promise that resolves when the collection is disposed.
245
+ */
246
+ async dispose() {
247
+ await this.backend.dispose();
248
+ this.isDisposed = true;
249
+ this.removeAllListeners();
250
+ Collection.collections = Collection.collections.filter((collection) => collection !== this);
251
+ Collection.onDisposeCallbacks.forEach((callback) => callback(this));
252
+ }
253
+ find(selector = {}, options) {
254
+ if (this.isDisposed) throw new Error("Collection is disposed");
255
+ if (selector !== void 0 && (!selector || typeof selector !== "object")) throw new Error("Invalid selector");
256
+ const getTransformedItems = () => {
257
+ const itemsOrPromise = this.getItems(selector, options || {});
258
+ if (itemsOrPromise instanceof Promise) return itemsOrPromise.then((items) => {
259
+ return this.transformAll(items, options?.fields);
260
+ });
261
+ const items = itemsOrPromise;
262
+ return this.transformAll(items, options?.fields);
263
+ };
264
+ const cursor = new require_Cursor.default(getTransformedItems, {
265
+ reactive: this.options.reactivity,
266
+ fieldTracking: this.fieldTracking,
267
+ ...options,
268
+ transform: this.transform.bind(this),
269
+ queryState: {
270
+ hasSettled: () => {
271
+ if (this.settledQueriesSet.has(require_queryId.default(selector, options))) return true;
272
+ const state = this.backend.getQueryState(selector, options || {});
273
+ return state === "complete" || state === "error";
274
+ },
275
+ onSettled: (callback) => this.backend.onQueryStateChange(selector, options || {}, (state) => {
276
+ if (state !== "complete" && state !== "error") return;
277
+ this.settledQueriesSet.add(require_queryId.default(selector, options));
278
+ callback();
279
+ })
280
+ },
281
+ bindEvents: (requery, applyDelta) => {
282
+ const handleRequery = () => {
283
+ if (this.batchOperationInProgress) {
284
+ this.postBatchCallbacks.add(requery);
285
+ return;
286
+ }
287
+ requery();
288
+ };
289
+ const canApplyDeltas = !this.options.transformAll && !options?.async;
290
+ const listeners = this.queryListeners({
291
+ selector,
292
+ options
293
+ });
294
+ const didRegister = listeners === 0;
295
+ if (didRegister) this.backend.registerQuery(selector, options || {});
296
+ this.queryListeners({
297
+ selector,
298
+ options
299
+ }, listeners + 1);
300
+ const queryStateChangeCleanup = this.backend.onQueryStateChange(selector, options || {}, (state, delta) => {
301
+ if (state === "error") {
302
+ const queryError = this.backend.getQueryError(selector, options || {}) || /* @__PURE__ */ new Error(`Query on "${this.name}" failed`);
303
+ this.emit("query.error", queryError, selector, options);
304
+ return;
305
+ }
306
+ if (state !== "complete") return;
307
+ if (delta != null && canApplyDeltas && !this.batchOperationInProgress) {
308
+ applyDelta(delta);
309
+ return;
310
+ }
311
+ handleRequery();
312
+ });
313
+ this.emit("observer.created", selector, options);
314
+ return () => {
315
+ queueMicrotask(() => {
316
+ const newListeners = Math.max(0, this.queryListeners({
317
+ selector,
318
+ options
319
+ }) - 1);
320
+ if (newListeners === 0 && didRegister) {
321
+ this.backend.unregisterQuery(selector, options || {});
322
+ this.settledQueriesSet.delete(require_queryId.default(selector, options));
323
+ }
324
+ this.queryListeners({
325
+ selector,
326
+ options
327
+ }, newListeners);
328
+ queryStateChangeCleanup();
329
+ this.emit("observer.disposed", selector, options);
330
+ });
331
+ };
332
+ }
333
+ });
334
+ this.emit("find", selector, options, cursor);
335
+ this.executeInDebugMode((callstack) => this.emit("_debug.find", callstack, selector, options, cursor));
336
+ return cursor;
337
+ }
338
+ findOne(selector, options) {
339
+ if (this.isDisposed) throw new Error("Collection is disposed");
340
+ const cursor = this.find(selector, {
341
+ limit: 1,
342
+ ...options
343
+ });
344
+ const handleItems = (items) => {
345
+ const returnValue = items[0] || void 0;
346
+ this.emit("findOne", selector, options, returnValue);
347
+ this.executeInDebugMode((callstack) => this.emit("_debug.findOne", callstack, selector, options, returnValue));
348
+ return returnValue;
349
+ };
350
+ const maybePromise = cursor.fetch();
351
+ return maybePromise instanceof Promise ? maybePromise.then(handleItems) : handleItems(maybePromise);
352
+ }
353
+ batch(callback) {
354
+ if (this.batchOperationInProgress) return callback();
355
+ this.batchOperationInProgress = true;
356
+ const afterBatch = () => {
357
+ this.batchOperationInProgress = false;
358
+ this.postBatchCallbacks.forEach((callback_) => callback_());
359
+ this.postBatchCallbacks.clear();
360
+ };
361
+ let maybePromise;
362
+ try {
363
+ maybePromise = callback();
364
+ } catch (error) {
365
+ afterBatch();
366
+ throw error;
367
+ }
368
+ if (maybePromise && typeof maybePromise.then === "function") return maybePromise.then(() => afterBatch(), (error) => {
369
+ afterBatch();
370
+ throw error;
371
+ });
372
+ else afterBatch();
373
+ }
374
+ onPostBatch(callback) {
375
+ if (this.isDisposed) throw new Error("Collection is disposed");
376
+ if (this.batchOperationInProgress) {
377
+ this.postBatchCallbacks.add(callback);
378
+ return;
379
+ }
380
+ return callback();
381
+ }
382
+ /**
383
+ * Inserts a single item into the collection. Generates a unique ID if not provided.
384
+ * @param item - The item to insert.
385
+ * @returns The ID of the inserted item.
386
+ * @throws {Error} If the collection is disposed or the item has an invalid ID.
387
+ */
388
+ async insert(item) {
389
+ if (this.isDisposed) throw new Error("Collection is disposed");
390
+ if (!item) throw new Error("Invalid item");
391
+ const itemWithId = {
392
+ id: (this.options.primaryKeyGenerator ?? require_randomId.default)(item),
393
+ ...item
394
+ };
395
+ this.emit("validate", itemWithId);
396
+ const newItem = await this.withPushState(() => this.backend.insert(itemWithId));
397
+ this.emit("added", newItem);
398
+ this.emit("insert", newItem);
399
+ this.executeInDebugMode((callstack) => this.emit("_debug.insert", callstack, newItem));
400
+ return newItem.id;
401
+ }
402
+ /**
403
+ * Inserts multiple items into the collection. Generates unique IDs for items if not provided.
404
+ * @param items - The items to insert.
405
+ * @returns An array of IDs of the inserted items.
406
+ * @throws {Error} If the collection is disposed or the items are invalid.
407
+ */
408
+ async insertMany(items) {
409
+ if (this.isDisposed) throw new Error("Collection is disposed");
410
+ if (!items) throw new Error("Invalid items");
411
+ if (items.length === 0) return [];
412
+ const ids = [];
413
+ await this.batch(async () => {
414
+ await Promise.all(items.map(async (item) => {
415
+ ids.push(await this.insert(item));
416
+ }));
417
+ });
418
+ return ids;
419
+ }
420
+ /**
421
+ * Updates a single item in the collection that matches the given selector.
422
+ * @param selector - The criteria to select the item to update.
423
+ * @param modifier - The modifications to apply to the item.
424
+ * @param [options] - Optional settings for the update operation.
425
+ * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
426
+ * @returns The number of items updated (0 or 1).
427
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
428
+ */
429
+ async updateOne(selector, modifier, options) {
430
+ if (this.isDisposed) throw new Error("Collection is disposed");
431
+ if (!selector) throw new Error("Invalid selector");
432
+ if (!modifier) throw new Error("Invalid modifier");
433
+ const { $setOnInsert, ...restModifier } = modifier;
434
+ if (this.listenerCount("validate") > 0) {
435
+ const item = await this.getItem(selector, { async: true });
436
+ if (item != null) this.emit("validate", require_modify.default(require_deepClone.default(item), restModifier));
437
+ }
438
+ const changes = await this.withPushState(() => this.backend.updateOne(selector, modifier));
439
+ if (changes.length === 0) {
440
+ if (!options?.upsert) return 0;
441
+ const newItem = require_modify.default({}, {
442
+ ...restModifier,
443
+ $set: {
444
+ ...$setOnInsert,
445
+ ...restModifier.$set
446
+ }
447
+ });
448
+ await this.insert(newItem);
449
+ return 1;
450
+ }
451
+ changes.forEach((item) => this.emit("changed", item, restModifier));
452
+ this.emit("updateOne", selector, modifier);
453
+ this.executeInDebugMode((callstack) => this.emit("_debug.updateOne", callstack, selector, modifier));
454
+ return changes.length;
455
+ }
456
+ /**
457
+ * Updates multiple items in the collection that match the given selector.
458
+ * @param selector - The criteria to select the items to update.
459
+ * @param modifier - The modifications to apply to the items.
460
+ * @param [options] - Optional settings for the update operation.
461
+ * @param [options.upsert] - If `true`, creates new items if no items match the selector.
462
+ * @returns The number of items updated.
463
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
464
+ */
465
+ async updateMany(selector, modifier, options) {
466
+ if (this.isDisposed) throw new Error("Collection is disposed");
467
+ if (!selector) throw new Error("Invalid selector");
468
+ if (!modifier) throw new Error("Invalid modifier");
469
+ const { $setOnInsert, ...restModifier } = modifier;
470
+ if (this.listenerCount("validate") > 0) (await this.getItems(selector, { async: true })).forEach((item) => {
471
+ this.emit("validate", require_modify.default(require_deepClone.default(item), restModifier));
472
+ });
473
+ const changes = await this.withPushState(() => this.backend.updateMany(selector, modifier));
474
+ if (changes.length === 0) {
475
+ if (!options?.upsert) return 0;
476
+ const newItem = require_modify.default({}, {
477
+ ...restModifier,
478
+ $set: {
479
+ ...$setOnInsert,
480
+ ...restModifier.$set
481
+ }
482
+ });
483
+ await this.insert(newItem);
484
+ return 1;
485
+ }
486
+ changes.forEach((item) => {
487
+ this.emit("changed", item, restModifier);
488
+ });
489
+ this.emit("updateMany", selector, modifier);
490
+ this.executeInDebugMode((callstack) => this.emit("_debug.updateMany", callstack, selector, modifier));
491
+ return changes.length;
492
+ }
493
+ /**
494
+ * Replaces a single item in the collection that matches the given selector.
495
+ * @param selector - The criteria to select the item to replace.
496
+ * @param replacement - The item to replace the selected item with.
497
+ * @param [options] - Optional settings for the replace operation.
498
+ * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
499
+ * @returns The number of items replaced (0 or 1).
500
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
501
+ */
502
+ async replaceOne(selector, replacement, options) {
503
+ if (this.isDisposed) throw new Error("Collection is disposed");
504
+ if (!selector) throw new Error("Invalid selector");
505
+ if (this.listenerCount("validate") > 0) {
506
+ const item = await this.getItem(selector, { async: true });
507
+ if (item != null) this.emit("validate", {
508
+ id: item.id,
509
+ ...replacement
510
+ });
511
+ }
512
+ const changes = await this.withPushState(() => this.backend.replaceOne(selector, replacement));
513
+ if (changes.length === 0) {
514
+ if (!options?.upsert) return 0;
515
+ await this.insert(replacement);
516
+ return 1;
517
+ }
518
+ changes.forEach((item) => this.emit("changed", item, replacement));
519
+ this.emit("replaceOne", selector, replacement);
520
+ this.executeInDebugMode((callstack) => this.emit("_debug.replaceOne", callstack, selector, replacement));
521
+ return changes.length;
522
+ }
523
+ /**
524
+ * Removes a single item from the collection that matches the given selector.
525
+ * @param selector - The criteria to select the item to remove.
526
+ * @returns The number of items removed (0 or 1).
527
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
528
+ */
529
+ async removeOne(selector) {
530
+ if (this.isDisposed) throw new Error("Collection is disposed");
531
+ if (!selector) throw new Error("Invalid selector");
532
+ const removedItems = await this.withPushState(() => this.backend.removeOne(selector));
533
+ this.emit("removed", removedItems[0]);
534
+ this.emit("removeOne", selector);
535
+ this.executeInDebugMode((callstack) => this.emit("_debug.removeOne", callstack, selector));
536
+ return removedItems.length;
537
+ }
538
+ /**
539
+ * Removes multiple items from the collection that match the given selector.
540
+ * @param selector - The criteria to select the items to remove.
541
+ * @returns The number of items removed.
542
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
543
+ */
544
+ async removeMany(selector) {
545
+ if (this.isDisposed) throw new Error("Collection is disposed");
546
+ if (!selector) throw new Error("Invalid selector");
547
+ const removedItems = await this.withPushState(() => this.backend.removeMany(selector));
548
+ removedItems.forEach((item) => {
549
+ this.emit("removed", item);
83
550
  });
551
+ this.emit("removeMany", selector);
552
+ this.executeInDebugMode((callstack) => this.emit("_debug.removeMany", callstack, selector));
553
+ return removedItems.length;
84
554
  }
85
- method.generator = gen;
86
- return method;
87
- }
555
+ };
88
556
  //#endregion
89
- exports.default = reactiveOrAsync;
90
- exports.unwrap = unwrap;
557
+ exports.default = Collection;