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

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 (128) hide show
  1. package/dist/.vite/manifest.json +128 -75
  2. package/dist/AsyncDataAdapter.d.ts +44 -7
  3. package/dist/AutoFetchDataAdapter.d.ts +5 -8
  4. package/dist/Collection/Cursor.d.ts +6 -5
  5. package/dist/Collection/Observer.d.ts +2 -0
  6. package/dist/Collection/index.d.ts +36 -25
  7. package/dist/Collection/types.d.ts +10 -2
  8. package/dist/DataAdapter.d.ts +18 -11
  9. package/dist/DefaultDataAdapter.d.ts +7 -6
  10. package/dist/WorkerDataAdapter.d.ts +7 -6
  11. package/dist/WorkerDataAdapterHost.d.ts +14 -11
  12. package/dist/createIndex.d.ts +2 -2
  13. package/dist/createIndexProvider.d.ts +2 -2
  14. package/dist/createReactivityAdapter.d.ts +2 -2
  15. package/dist/createStorageAdapter.d.ts +1 -1
  16. package/dist/getIndexInfo.d.ts +3 -4
  17. package/dist/index.cjs.js +33 -31
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.mjs +17 -32
  20. package/dist/index10.cjs.js +23 -0
  21. package/dist/index10.mjs +21 -17
  22. package/dist/index11.cjs.js +45 -0
  23. package/dist/index11.mjs +44 -15
  24. package/dist/index12.cjs.js +22 -0
  25. package/dist/index12.mjs +21 -339
  26. package/dist/index13.cjs.js +45 -0
  27. package/dist/index13.mjs +44 -407
  28. package/dist/index14.cjs.js +86 -0
  29. package/dist/index14.mjs +85 -190
  30. package/dist/index15.cjs.js +15 -0
  31. package/dist/index15.mjs +14 -341
  32. package/dist/index16.cjs.js +136 -0
  33. package/dist/index16.mjs +134 -569
  34. package/dist/index17.cjs.js +47 -0
  35. package/dist/index17.mjs +46 -150
  36. package/dist/index18.cjs.js +15 -0
  37. package/dist/index18.mjs +14 -26
  38. package/dist/index19.cjs.js +23 -0
  39. package/dist/index19.mjs +22 -42
  40. package/dist/index2.cjs.js +33 -0
  41. package/dist/index2.mjs +32 -254
  42. package/dist/index20.cjs.js +37 -0
  43. package/dist/index20.mjs +36 -104
  44. package/dist/index21.cjs.js +35 -0
  45. package/dist/index21.mjs +34 -128
  46. package/dist/index22.cjs.js +25 -0
  47. package/dist/index22.mjs +24 -7
  48. package/dist/index23.cjs.js +19 -0
  49. package/dist/index23.mjs +17 -26
  50. package/dist/index24.cjs.js +342 -0
  51. package/dist/index24.mjs +340 -6
  52. package/dist/index25.cjs.js +542 -0
  53. package/dist/index25.mjs +541 -10
  54. package/dist/index26.cjs.js +11 -0
  55. package/dist/index26.mjs +10 -29
  56. package/dist/index27.cjs.js +11 -0
  57. package/dist/index27.mjs +10 -5
  58. package/dist/index28.cjs.js +90 -0
  59. package/dist/index28.mjs +89 -8
  60. package/dist/index29.cjs.js +424 -0
  61. package/dist/index29.mjs +423 -29
  62. package/dist/index3.cjs.js +19 -0
  63. package/dist/index3.mjs +18 -537
  64. package/dist/index30.cjs.js +72 -0
  65. package/dist/index30.mjs +71 -42
  66. package/dist/index31.cjs.js +191 -0
  67. package/dist/index31.mjs +190 -9
  68. package/dist/index32.cjs.js +21 -0
  69. package/dist/index32.mjs +21 -0
  70. package/dist/index33.cjs.js +336 -0
  71. package/dist/index33.mjs +336 -0
  72. package/dist/index34.cjs.js +539 -0
  73. package/dist/index34.mjs +539 -0
  74. package/dist/index4.cjs.js +150 -0
  75. package/dist/index4.mjs +149 -5
  76. package/dist/index5.cjs.js +254 -0
  77. package/dist/index5.mjs +252 -4
  78. package/dist/index6.cjs.js +125 -0
  79. package/dist/index6.mjs +124 -29
  80. package/dist/index7.cjs.js +33 -0
  81. package/dist/index7.mjs +32 -12
  82. package/dist/index8.cjs.js +12 -0
  83. package/dist/index8.mjs +10 -4
  84. package/dist/index9.cjs.js +11 -0
  85. package/dist/index9.mjs +10 -133
  86. package/dist/types/IndexProvider.d.ts +2 -2
  87. package/dist/types/Modifier.d.ts +1 -1
  88. package/dist/types/ReactivityAdapter.d.ts +1 -1
  89. package/dist/utils/batchOnNextTick.d.ts +16 -0
  90. package/dist/utils/createSignal.d.ts +2 -2
  91. package/dist/utils/deepClone.d.ts +1 -1
  92. package/dist/utils/getMatchingKeys.d.ts +2 -2
  93. package/dist/utils/isFieldExpression.d.ts +1 -1
  94. package/dist/utils/match.d.ts +1 -1
  95. package/dist/utils/modify.d.ts +1 -1
  96. package/dist/utils/queryId.d.ts +2 -2
  97. package/dist/utils/reactiveOrAsync.d.ts +59 -0
  98. package/package.json +3 -3
  99. package/dist/index.cjs10.js +0 -18
  100. package/dist/index.cjs11.js +0 -15
  101. package/dist/index.cjs12.js +0 -339
  102. package/dist/index.cjs13.js +0 -407
  103. package/dist/index.cjs14.js +0 -190
  104. package/dist/index.cjs15.js +0 -341
  105. package/dist/index.cjs16.js +0 -570
  106. package/dist/index.cjs17.js +0 -150
  107. package/dist/index.cjs18.js +0 -26
  108. package/dist/index.cjs19.js +0 -43
  109. package/dist/index.cjs2.js +0 -255
  110. package/dist/index.cjs20.js +0 -104
  111. package/dist/index.cjs21.js +0 -129
  112. package/dist/index.cjs22.js +0 -7
  113. package/dist/index.cjs23.js +0 -27
  114. package/dist/index.cjs24.js +0 -7
  115. package/dist/index.cjs25.js +0 -10
  116. package/dist/index.cjs26.js +0 -29
  117. package/dist/index.cjs27.js +0 -5
  118. package/dist/index.cjs28.js +0 -7
  119. package/dist/index.cjs29.js +0 -29
  120. package/dist/index.cjs3.js +0 -538
  121. package/dist/index.cjs30.js +0 -42
  122. package/dist/index.cjs31.js +0 -9
  123. package/dist/index.cjs4.js +0 -5
  124. package/dist/index.cjs5.js +0 -5
  125. package/dist/index.cjs6.js +0 -29
  126. package/dist/index.cjs7.js +0 -12
  127. package/dist/index.cjs8.js +0 -5
  128. package/dist/index.cjs9.js +0 -133
package/dist/index3.mjs CHANGED
@@ -1,538 +1,19 @@
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
- import deepClone from "./index19.mjs";
7
- import Cursor from "./index2.mjs";
8
- class Collection extends EventEmitter {
9
- static collections = [];
10
- static debugMode = false;
11
- static batchOperationInProgress = false;
12
- static fieldTracking = false;
13
- static onCreationCallbacks = [];
14
- static onDisposeCallbacks = [];
15
- static getCollections() {
16
- return Collection.collections;
17
- }
18
- static onCreation(callback) {
19
- Collection.onCreationCallbacks.push(callback);
20
- }
21
- static onDispose(callback) {
22
- Collection.onDisposeCallbacks.push(callback);
23
- }
24
- /**
25
- * Enables debug mode for all collections.
26
- */
27
- static enableDebugMode = () => {
28
- Collection.debugMode = true;
29
- Collection.collections.forEach((collection) => {
30
- collection.setDebugMode(true);
31
- });
32
- };
33
- /**
34
- * Enables field tracking for all collections.
35
- * @param enable - A boolean indicating whether to enable field tracking.
36
- */
37
- static setFieldTracking = (enable) => {
38
- Collection.fieldTracking = enable;
39
- Collection.collections.forEach((collection) => {
40
- collection.setFieldTracking(enable);
41
- });
42
- };
43
- static batch(callback) {
44
- Collection.batchOperationInProgress = true;
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
- }
57
- }
58
- name;
59
- backend;
60
- options;
61
- isPullingSignal;
62
- isPushingSignal;
63
- readySignal;
64
- debugMode;
65
- batchOperationInProgress = false;
66
- isDisposed = false;
67
- postBatchCallbacks = /* @__PURE__ */ new Set();
68
- fieldTracking = false;
69
- constructor(nameOrOptions, maybeDataAdapter, maybeOptions) {
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
- });
77
- Collection.collections.push(this);
78
- this.name = name;
79
- this.options = { ...options };
80
- this.fieldTracking = this.options.fieldTracking ?? Collection.fieldTracking;
81
- this.debugMode = this.options.enableDebugMode ?? Collection.debugMode;
82
- this.isPullingSignal = createSignal(this.options.reactivity, false);
83
- this.isPushingSignal = createSignal(this.options.reactivity, false);
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(() => {
89
- });
90
- Collection.onCreationCallbacks.forEach((callback) => callback(this));
91
- }
92
- isBatchOperationInProgress() {
93
- return Collection.batchOperationInProgress || this.batchOperationInProgress;
94
- }
95
- /**
96
- * Checks whether the collection is currently performing a pull operation
97
- * ⚡️ this function is reactive!
98
- * (loading data from the persistence adapter).
99
- * @returns A boolean indicating if the collection is in the process of pulling data.
100
- */
101
- isPulling() {
102
- return this.isPullingSignal.get() ?? false;
103
- }
104
- /**
105
- * Checks whether the collection is currently performing a push operation
106
- * ⚡️ this function is reactive!
107
- * (saving data to the persistence adapter).
108
- * @returns A boolean indicating if the collection is in the process of pushing data.
109
- */
110
- isPushing() {
111
- return this.isPushingSignal.get() ?? false;
112
- }
113
- /**
114
- * Checks whether the collection is currently performing either a pull or push operation,
115
- * ⚡️ this function is reactive!
116
- * indicating that it is loading or saving data.
117
- * @returns A boolean indicating if the collection is in the process of loading or saving data.
118
- */
119
- isLoading() {
120
- const isPulling = this.isPulling();
121
- const isPushing = this.isPushing();
122
- return isPulling || isPushing;
123
- }
124
- /**
125
- * Retrieves the current debug mode status of the collection.
126
- * @returns A boolean indicating whether debug mode is enabled for the collection.
127
- */
128
- getDebugMode() {
129
- return this.debugMode;
130
- }
131
- /**
132
- * Enables or disables debug mode for the collection.
133
- * When debug mode is enabled, additional debugging information and events are emitted.
134
- * @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) debug mode.
135
- */
136
- setDebugMode(enable) {
137
- this.debugMode = enable;
138
- }
139
- /**
140
- * Enables or disables field tracking for the collection.
141
- * @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) field tracking.
142
- */
143
- setFieldTracking(enable) {
144
- this.fieldTracking = enable;
145
- }
146
- /**
147
- * Resolves when the persistence adapter finished initializing
148
- * and the collection is ready to be used.
149
- * @returns A promise that resolves when the collection is ready.
150
- * @example
151
- * ```ts
152
- * const collection = new Collection({
153
- * persistence: // ...
154
- * })
155
- * await collection.isReady()
156
- *
157
- * collection.insert({ name: 'Item 1' })
158
- */
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;
169
- }
170
- profile(fn, measureFunction) {
171
- if (!this.debugMode)
172
- return fn();
173
- const startTime = performance.now();
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);
181
- }
182
- executeInDebugMode(fn) {
183
- if (!this.debugMode)
184
- return;
185
- const callstack = new Error().stack || "";
186
- fn(callstack);
187
- }
188
- transform(item) {
189
- if (!this.options.transform)
190
- return item;
191
- return this.options.transform(item);
192
- }
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);
204
- return this.profile(() => {
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
- });
223
- }, (measuredTime) => this.executeInDebugMode((callstack) => this.emit("_debug.getItems", callstack, selector, measuredTime)));
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
- }
233
- /**
234
- * Disposes the collection, unregisters persistence adapters, clears memory, and
235
- * cleans up all resources used by the collection.
236
- * @returns A promise that resolves when the collection is disposed.
237
- */
238
- async dispose() {
239
- await this.backend.dispose();
240
- this.isDisposed = true;
241
- this.removeAllListeners();
242
- Collection.collections = Collection.collections.filter((collection) => collection !== this);
243
- Collection.onDisposeCallbacks.forEach((callback) => callback(this));
244
- }
245
- /**
246
- * Finds multiple items in the collection based on a selector and optional options.
247
- * Returns a cursor for reactive data queries.
248
- * @template O - The options type for the find operation.
249
- * @param [selector] - The criteria to select items.
250
- * @param [options] - Options for the find operation, such as limit and sort.
251
- * @returns A cursor to fetch and observe the matching items.
252
- */
253
- find(selector = {}, options) {
254
- if (this.isDisposed)
255
- throw new Error("Collection is disposed");
256
- if (selector !== void 0 && (!selector || typeof selector !== "object"))
257
- throw new Error("Invalid selector");
258
- const cursor = new Cursor((() => this.getItems(selector, options || {})), {
259
- reactive: this.options.reactivity,
260
- fieldTracking: this.fieldTracking,
261
- ...options,
262
- transform: this.transform.bind(this),
263
- bindEvents: (requery) => {
264
- const handleRequery = () => {
265
- if (this.batchOperationInProgress) {
266
- this.postBatchCallbacks.add(requery);
267
- return;
268
- }
269
- requery();
270
- };
271
- this.backend.registerQuery(selector, options);
272
- const queryStateChangeCleanup = this.backend.onQueryStateChange(selector, options, (state) => {
273
- if (state !== "complete")
274
- return;
275
- handleRequery();
276
- });
277
- this.emit("observer.created", selector, options);
278
- return () => {
279
- this.backend.unregisterQuery(selector, options);
280
- queryStateChangeCleanup();
281
- this.emit("observer.disposed", selector, options);
282
- };
283
- }
284
- });
285
- this.emit("find", selector, options, cursor);
286
- this.executeInDebugMode((callstack) => this.emit("_debug.find", callstack, selector, options, cursor));
287
- return cursor;
288
- }
289
- /**
290
- * Finds a single item in the collection based on a selector and optional options.
291
- * ⚡️ this function is reactive!
292
- * Returns the found item or undefined if no item matches.
293
- * @template Async - Whether to perform the operation asynchronously.
294
- * @template O - The options type for the find operation.
295
- * @param selector - The criteria to select the item.
296
- * @param [options] - Options for the find operation, such as projection.
297
- * @returns The found item or `undefined`.
298
- */
299
- findOne(selector, options) {
300
- if (this.isDisposed)
301
- throw new Error("Collection is disposed");
302
- const cursor = this.find(selector, {
303
- limit: 1,
304
- ...options
305
- });
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);
314
- }
315
- batch(callback) {
316
- if (this.batchOperationInProgress)
317
- return callback();
318
- this.batchOperationInProgress = true;
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();
339
- }
340
- /**
341
- * Inserts a single item into the collection. Generates a unique ID if not provided.
342
- * @param item - The item to insert.
343
- * @returns The ID of the inserted item.
344
- * @throws {Error} If the collection is disposed or the item has an invalid ID.
345
- */
346
- async insert(item) {
347
- if (this.isDisposed)
348
- throw new Error("Collection is disposed");
349
- if (!item)
350
- throw new Error("Invalid item");
351
- const primaryKeyGenerator = this.options.primaryKeyGenerator ?? randomId;
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));
358
- this.emit("added", newItem);
359
- this.emit("insert", newItem);
360
- this.executeInDebugMode((callstack) => this.emit("_debug.insert", callstack, newItem));
361
- return newItem.id;
362
- }
363
- /**
364
- * Inserts multiple items into the collection. Generates unique IDs for items if not provided.
365
- * @param items - The items to insert.
366
- * @returns An array of IDs of the inserted items.
367
- * @throws {Error} If the collection is disposed or the items are invalid.
368
- */
369
- async insertMany(items) {
370
- if (this.isDisposed)
371
- throw new Error("Collection is disposed");
372
- if (!items)
373
- throw new Error("Invalid items");
374
- if (items.length === 0) {
375
- return [];
376
- }
377
- const ids = [];
378
- await this.batch(async () => {
379
- await Promise.all(items.map(async (item) => {
380
- ids.push(await this.insert(item));
381
- }));
382
- });
383
- return ids;
384
- }
385
- /**
386
- * Updates a single item in the collection that matches the given selector.
387
- * @param selector - The criteria to select the item to update.
388
- * @param modifier - The modifications to apply to the item.
389
- * @param [options] - Optional settings for the update operation.
390
- * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
391
- * @returns The number of items updated (0 or 1).
392
- * @throws {Error} If the collection is disposed or invalid arguments are provided.
393
- */
394
- async updateOne(selector, modifier, options) {
395
- if (this.isDisposed)
396
- throw new Error("Collection is disposed");
397
- if (!selector)
398
- throw new Error("Invalid selector");
399
- if (!modifier)
400
- throw new Error("Invalid modifier");
401
- const { $setOnInsert, ...restModifier } = modifier;
402
- const item = await this.getItem(selector, { async: true });
403
- if (item == null) {
404
- if (options?.upsert) {
405
- const newItem = modify({}, {
406
- ...restModifier,
407
- $set: {
408
- ...$setOnInsert,
409
- ...restModifier.$set
410
- }
411
- });
412
- await this.insert(newItem);
413
- return 1;
414
- }
415
- return 0;
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);
421
- this.emit("updateOne", selector, modifier);
422
- this.executeInDebugMode((callstack) => this.emit("_debug.updateOne", callstack, selector, modifier));
423
- return changes.length;
424
- }
425
- /**
426
- * Updates multiple items in the collection that match the given selector.
427
- * @param selector - The criteria to select the items to update.
428
- * @param modifier - The modifications to apply to the items.
429
- * @param [options] - Optional settings for the update operation.
430
- * @param [options.upsert] - If `true`, creates new items if no items match the selector.
431
- * @returns The number of items updated.
432
- * @throws {Error} If the collection is disposed or invalid arguments are provided.
433
- */
434
- async updateMany(selector, modifier, options) {
435
- if (this.isDisposed)
436
- throw new Error("Collection is disposed");
437
- if (!selector)
438
- throw new Error("Invalid selector");
439
- if (!modifier)
440
- throw new Error("Invalid modifier");
441
- const { $setOnInsert, ...restModifier } = modifier;
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;
454
- }
455
- return 0;
456
- }
457
- items.forEach((item) => {
458
- const modifiedItem = modify(deepClone(item), restModifier);
459
- this.emit("validate", modifiedItem);
460
- });
461
- const changes = await this.withPushState(() => this.backend.updateMany(selector, modifier));
462
- changes.forEach((item) => {
463
- this.emit("changed", item, restModifier);
464
- });
465
- this.emit("updateMany", selector, modifier);
466
- this.executeInDebugMode((callstack) => this.emit("_debug.updateMany", callstack, selector, modifier));
467
- return changes.length;
468
- }
469
- /**
470
- * Replaces a single item in the collection that matches the given selector.
471
- * @param selector - The criteria to select the item to replace.
472
- * @param replacement - The item to replace the selected item with.
473
- * @param [options] - Optional settings for the replace operation.
474
- * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
475
- * @returns The number of items replaced (0 or 1).
476
- * @throws {Error} If the collection is disposed or invalid arguments are provided.
477
- */
478
- async replaceOne(selector, replacement, options) {
479
- if (this.isDisposed)
480
- throw new Error("Collection is disposed");
481
- if (!selector)
482
- throw new Error("Invalid selector");
483
- const item = await this.getItem(selector, { async: true });
484
- if (item == null) {
485
- if (options?.upsert) {
486
- await this.insert(replacement);
487
- return 1;
488
- }
489
- return 0;
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);
495
- this.emit("replaceOne", selector, replacement);
496
- this.executeInDebugMode((callstack) => this.emit("_debug.replaceOne", callstack, selector, replacement));
497
- return changes.length;
498
- }
499
- /**
500
- * Removes a single item from the collection that matches the given selector.
501
- * @param selector - The criteria to select the item to remove.
502
- * @returns The number of items removed (0 or 1).
503
- * @throws {Error} If the collection is disposed or invalid arguments are provided.
504
- */
505
- async removeOne(selector) {
506
- if (this.isDisposed)
507
- throw new Error("Collection is disposed");
508
- if (!selector)
509
- throw new Error("Invalid selector");
510
- const removedItems = await this.withPushState(() => this.backend.removeOne(selector));
511
- this.emit("removed", removedItems[0]);
512
- this.emit("removeOne", selector);
513
- this.executeInDebugMode((callstack) => this.emit("_debug.removeOne", callstack, selector));
514
- return removedItems.length;
515
- }
516
- /**
517
- * Removes multiple items from the collection that match the given selector.
518
- * @param selector - The criteria to select the items to remove.
519
- * @returns The number of items removed.
520
- * @throws {Error} If the collection is disposed or invalid arguments are provided.
521
- */
522
- async removeMany(selector) {
523
- if (this.isDisposed)
524
- throw new Error("Collection is disposed");
525
- if (!selector)
526
- throw new Error("Invalid selector");
527
- const removedItems = await this.withPushState(() => this.backend.removeMany(selector));
528
- removedItems.forEach((item) => {
529
- this.emit("removed", item);
530
- });
531
- this.emit("removeMany", selector);
532
- this.executeInDebugMode((callstack) => this.emit("_debug.removeMany", callstack, selector));
533
- return removedItems.length;
534
- }
1
+ //#region src/utils/uniqueBy.ts
2
+ /**
3
+ * Filters an array to ensure unique values based on a specified key or transformation function.
4
+ * @template T - The type of the elements in the array.
5
+ * @param array - The array to filter for unique values.
6
+ * @param fn - A key or transformation function to determine uniqueness.
7
+ * - If a key is provided, it will use the corresponding property of each element for uniqueness.
8
+ * - If a function is provided, it will use the return value of the function applied to each element for uniqueness.
9
+ * @returns A new array containing only unique elements based on the specified key or transformation.
10
+ */
11
+ function uniqueBy(array, fn) {
12
+ const set = /* @__PURE__ */ new Set();
13
+ return array.filter((element) => {
14
+ const value = typeof fn === "function" ? fn(element) : element[fn];
15
+ return !set.has(value) && set.add(value);
16
+ });
535
17
  }
536
- export {
537
- Collection as default
538
- };
18
+ //#endregion
19
+ export { uniqueBy as default };
@@ -0,0 +1,72 @@
1
+ //#region src/utils/batchOnNextTick.ts
2
+ /**
3
+ * Groups multiple calls by key and flushes them on the next tick (macrotask).
4
+ * @param onFlush - Function that will be called with the key and all queued items when flushing.
5
+ * @returns An object with `enqueue` and `flush` methods.
6
+ * @example
7
+ * const batcher = batchOnNextTick<string>(async (key, items) => {
8
+ * // items is an array of { args, resolve, reject }
9
+ * // do something once with all args...
10
+ * })
11
+ *
12
+ * batcher.enqueue("my-key", [arg1, arg2])
13
+ */
14
+ function batchOnNextTick(onFlush) {
15
+ const queues = /* @__PURE__ */ new Map();
16
+ /**
17
+ * Enqueue a call with the given key and arguments.
18
+ * @param key key to group calls
19
+ * @param args arguments for the call
20
+ * @returns A promise that resolves or rejects when the call is flushed.
21
+ */
22
+ function enqueue(key, args) {
23
+ return new Promise((resolve, reject) => {
24
+ let q = queues.get(key);
25
+ if (!q) {
26
+ q = {
27
+ timer: null,
28
+ items: [],
29
+ flush: () => flush(key)
30
+ };
31
+ queues.set(key, q);
32
+ }
33
+ q.items.push({
34
+ args,
35
+ resolve,
36
+ reject
37
+ });
38
+ if (q.timer == null) q.timer = setTimeout(() => {
39
+ q.timer = null;
40
+ q.flush();
41
+ }, 0);
42
+ });
43
+ }
44
+ /**
45
+ * Flush the queue for the given key immediately.
46
+ * @param key key to flush
47
+ * @returns A promise that resolves when the flush is complete.
48
+ */
49
+ async function flush(key) {
50
+ const q = queues.get(key);
51
+ if (!q || q.items.length === 0) return;
52
+ if (q.timer != null) {
53
+ clearTimeout(q.timer);
54
+ q.timer = null;
55
+ }
56
+ const items = q.items.splice(0);
57
+ onFlush(key, items.map((i) => i.args)).then((results) => {
58
+ for (const [index, result] of results.entries()) {
59
+ const { resolve } = items[index];
60
+ resolve(result);
61
+ }
62
+ }).catch((error) => {
63
+ for (const { reject } of items) reject(error);
64
+ });
65
+ }
66
+ return {
67
+ enqueue,
68
+ flush
69
+ };
70
+ }
71
+ //#endregion
72
+ exports.default = batchOnNextTick;