@signaldb/core 1.5.4 → 1.7.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 (68) hide show
  1. package/dist/.vite/manifest.json +29 -28
  2. package/dist/Collection/createIndex.d.ts +1 -1
  3. package/dist/Collection/index.d.ts +1 -0
  4. package/dist/index.cjs.js +14 -12
  5. package/dist/index.cjs10.js +27 -10
  6. package/dist/index.cjs11.js +10 -3
  7. package/dist/index.cjs12.js +3 -136
  8. package/dist/index.cjs13.js +131 -61
  9. package/dist/index.cjs14.js +72 -5
  10. package/dist/index.cjs15.js +8 -41
  11. package/dist/index.cjs16.js +25 -11
  12. package/dist/index.cjs17.js +143 -23
  13. package/dist/index.cjs18.js +5 -262
  14. package/dist/index.cjs19.js +36 -63
  15. package/dist/index.cjs2.js +210 -732
  16. package/dist/index.cjs20.js +13 -85
  17. package/dist/index.cjs21.js +24 -8
  18. package/dist/index.cjs22.js +67 -24
  19. package/dist/index.cjs23.js +72 -131
  20. package/dist/index.cjs24.js +16 -5
  21. package/dist/index.cjs25.js +22 -11
  22. package/dist/index.cjs26.js +7 -27
  23. package/dist/index.cjs27.js +5 -40
  24. package/dist/index.cjs28.js +26 -26
  25. package/dist/index.cjs29.js +40 -7
  26. package/dist/index.cjs3.js +757 -136
  27. package/dist/index.cjs4.js +165 -62
  28. package/dist/index.cjs5.js +67 -3
  29. package/dist/index.cjs6.js +2 -2
  30. package/dist/index.cjs7.js +2 -2
  31. package/dist/index.cjs8.js +2 -2
  32. package/dist/index.cjs9.js +3 -27
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.mjs +14 -12
  35. package/dist/index10.mjs +27 -10
  36. package/dist/index11.mjs +10 -3
  37. package/dist/index12.mjs +3 -136
  38. package/dist/index13.mjs +131 -60
  39. package/dist/index14.mjs +71 -5
  40. package/dist/index15.mjs +8 -40
  41. package/dist/index16.mjs +25 -11
  42. package/dist/index17.mjs +143 -23
  43. package/dist/index18.mjs +5 -261
  44. package/dist/index19.mjs +36 -63
  45. package/dist/index2.mjs +210 -733
  46. package/dist/index20.mjs +13 -84
  47. package/dist/index21.mjs +24 -8
  48. package/dist/index22.mjs +66 -24
  49. package/dist/index23.mjs +71 -131
  50. package/dist/index24.mjs +16 -5
  51. package/dist/index25.mjs +22 -11
  52. package/dist/index26.mjs +7 -27
  53. package/dist/index27.mjs +5 -40
  54. package/dist/index28.mjs +26 -26
  55. package/dist/index29.mjs +40 -7
  56. package/dist/index3.mjs +757 -136
  57. package/dist/index4.mjs +165 -61
  58. package/dist/index5.mjs +66 -3
  59. package/dist/index6.mjs +2 -2
  60. package/dist/index7.mjs +2 -2
  61. package/dist/index8.mjs +2 -2
  62. package/dist/index9.mjs +3 -27
  63. package/dist/types/IndexProvider.d.ts +2 -0
  64. package/dist/types/Modifier.d.ts +17 -10
  65. package/dist/types/Selector.d.ts +1 -7
  66. package/dist/utils/getMatchingKeys.d.ts +2 -2
  67. package/dist/utils/serializeValue.d.ts +1 -1
  68. package/package.json +1 -1
@@ -3,784 +3,262 @@ var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
6
- const EventEmitter = require("./index.cjs12.js");
7
- const match = require("./index.cjs14.js");
8
- const modify = require("./index.cjs10.js");
9
- const isEqual = require("./index.cjs9.js");
10
- const randomId = require("./index.cjs11.js");
11
- const deepClone = require("./index.cjs15.js");
12
- const serializeValue = require("./index.cjs16.js");
13
- const createSignal = require("./index.cjs17.js");
14
- const Cursor = require("./index.cjs18.js");
15
- const getIndexInfo = require("./index.cjs19.js");
16
- const createIndex = require("./index.cjs13.js");
17
- function hasPendingUpdates(pendingUpdates) {
18
- return pendingUpdates.added.length > 0 || pendingUpdates.modified.length > 0 || pendingUpdates.removed.length > 0;
6
+ const sortItems = require("./index.cjs15.js");
7
+ const project = require("./index.cjs16.js");
8
+ const Observer = require("./index.cjs17.js");
9
+ function isInReactiveScope(reactivity) {
10
+ if (!reactivity)
11
+ return false;
12
+ if (!reactivity.isInScope)
13
+ return true;
14
+ return reactivity.isInScope();
19
15
  }
20
- function applyUpdates(currentItems, { added, modified, removed }) {
21
- const items = [...currentItems];
22
- added.forEach((item) => {
23
- items.push(item);
24
- });
25
- modified.forEach((item) => {
26
- const index = items.findIndex(({ id }) => id === item.id);
27
- if (index === -1)
28
- return;
29
- items[index] = item;
30
- });
31
- removed.forEach((item) => {
32
- const index = items.findIndex(({ id }) => id === item.id);
33
- if (index === -1)
34
- return;
35
- items.splice(index, 1);
36
- });
37
- return items;
38
- }
39
- const _Collection = class _Collection extends EventEmitter {
16
+ class Cursor {
40
17
  /**
41
- * Initializes a new instance of the `Collection` class with optional configuration.
42
- * Sets up memory, persistence, reactivity, and indices as specified in the options.
43
- * @template T - The type of the items stored in the collection.
44
- * @template I - The type of the unique identifier for the items.
18
+ * Creates a new instance of the `Cursor` class.
19
+ * Provides utilities for querying, observing, and transforming items from a collection.
20
+ * @template T - The type of the items in the collection.
45
21
  * @template U - The transformed item type after applying transformations (default is T).
46
- * @param options - Optional configuration for the collection.
47
- * @param options.name - An optional name for the collection.
48
- * @param options.memory - The in-memory adapter for storing items.
49
- * @param options.reactivity - The reactivity adapter for observing changes in the collection.
50
- * @param options.transform - A transformation function to apply to items when retrieving them.
51
- * @param options.persistence - The persistence adapter for saving and loading items.
52
- * @param options.indices - An array of index providers for optimized querying.
53
- * @param options.enableDebugMode - A boolean to enable or disable debug mode.
54
- * @param options.fieldTracking - A boolean to enable or disable field tracking by default.
22
+ * @param getItems - A function that retrieves the filtered list of items.
23
+ * @param options - Optional configuration for the cursor.
24
+ * @param options.transform - A transformation function to apply to each item when retrieving them.
25
+ * @param options.bindEvents - A function to bind reactivity events for the cursor, which should return a cleanup function.
26
+ * @param options.fields - A projection object defining which fields of the item should be included or excluded.
27
+ * @param options.sort - A sort specifier to determine the order of the items.
28
+ * @param options.skip - The number of items to skip from the beginning of the result set.
29
+ * @param options.limit - The maximum number of items to return in the result set.
30
+ * @param options.reactive - A reactivity adapter to enable observing changes in the cursor's result set.
31
+ * @param options.fieldTracking - A boolean to enable fine-grained field tracking for reactivity.
55
32
  */
56
- constructor(options) {
57
- super();
58
- __publicField(this, "name");
33
+ constructor(getItems, options) {
34
+ __publicField(this, "observer");
35
+ __publicField(this, "getFilteredItems");
59
36
  __publicField(this, "options");
60
- __publicField(this, "persistenceAdapter", null);
61
- __publicField(this, "isPullingSignal");
62
- __publicField(this, "isPushingSignal");
63
- __publicField(this, "indexProviders", []);
64
- __publicField(this, "indicesOutdated", false);
65
- __publicField(this, "idIndex", /* @__PURE__ */ new Map());
66
- __publicField(this, "debugMode");
67
- __publicField(this, "batchOperationInProgress", false);
68
- __publicField(this, "isDisposed", false);
69
- __publicField(this, "postBatchCallbacks", /* @__PURE__ */ new Set());
70
- __publicField(this, "fieldTracking", false);
71
- __publicField(this, "persistenceReadyPromise");
72
- _Collection.collections.push(this);
73
- this.name = (options == null ? void 0 : options.name) ?? `${this.constructor.name}-${randomId()}`;
74
- this.options = {
75
- memory: [],
76
- ...options
77
- };
78
- this.fieldTracking = this.options.fieldTracking ?? _Collection.fieldTracking;
79
- this.debugMode = this.options.enableDebugMode ?? _Collection.debugMode;
80
- this.indexProviders = [
81
- createIndex.createExternalIndex("id", this.idIndex),
82
- ...this.options.indices || []
83
- ];
84
- this.rebuildIndices();
85
- this.isPullingSignal = createSignal(this.options.reactivity, !!(options == null ? void 0 : options.persistence));
86
- this.isPushingSignal = createSignal(this.options.reactivity, false);
87
- this.on("persistence.pullStarted", () => {
88
- this.isPullingSignal.set(true);
89
- });
90
- this.on("persistence.pullCompleted", () => {
91
- this.isPullingSignal.set(false);
92
- });
93
- this.on("persistence.pushStarted", () => {
94
- this.isPushingSignal.set(true);
95
- });
96
- this.on("persistence.pushCompleted", () => {
97
- this.isPushingSignal.set(false);
98
- });
99
- this.persistenceAdapter = this.options.persistence ?? null;
100
- if (this.persistenceAdapter) {
101
- let ongoingSaves = 0;
102
- let isInitialized = false;
103
- const pendingUpdates = { added: [], modified: [], removed: [] };
104
- const loadPersistentData = async (data) => {
105
- if (!this.persistenceAdapter)
106
- throw new Error("Persistence adapter not found");
107
- this.emit("persistence.pullStarted");
108
- const { items, changes } = data ?? await this.persistenceAdapter.load();
109
- if (items) {
110
- if (ongoingSaves > 0)
111
- return;
112
- this.memory().splice(0, this.memoryArray().length, ...items);
113
- this.idIndex.clear();
114
- this.memory().map((item, index) => {
115
- this.idIndex.set(serializeValue(item.id), /* @__PURE__ */ new Set([index]));
116
- });
117
- } else if (changes) {
118
- changes.added.forEach((item) => {
119
- const index = this.memory().findIndex((document) => document.id === item.id);
120
- if (index !== -1) {
121
- this.memory().splice(index, 1, item);
122
- return;
37
+ __publicField(this, "onCleanupCallbacks", []);
38
+ this.getFilteredItems = getItems;
39
+ this.options = options || {};
40
+ }
41
+ addGetters(item) {
42
+ if (!isInReactiveScope(this.options.reactive))
43
+ return item;
44
+ const depend = this.depend.bind(this);
45
+ return Object.entries(item).reduce((memo, [key, value]) => {
46
+ Object.defineProperty(memo, key, {
47
+ get() {
48
+ depend({
49
+ changedField: (notify) => (changedItem, changedFieldName) => {
50
+ if (changedFieldName !== key || changedItem.id !== item.id)
51
+ return;
52
+ notify();
123
53
  }
124
- this.memory().push(item);
125
- const itemIndex = this.memory().findIndex((document) => document === item);
126
- this.idIndex.set(serializeValue(item.id), /* @__PURE__ */ new Set([itemIndex]));
127
- });
128
- changes.modified.forEach((item) => {
129
- const index = this.memory().findIndex((document) => document.id === item.id);
130
- if (index === -1)
131
- throw new Error("Cannot resolve index for item");
132
- this.memory().splice(index, 1, item);
133
- });
134
- changes.removed.forEach((item) => {
135
- const index = this.memory().findIndex((document) => document.id === item.id);
136
- if (index === -1)
137
- throw new Error("Cannot resolve index for item");
138
- this.memory().splice(index, 1);
139
54
  });
140
- }
141
- this.rebuildIndices();
142
- this.emit("persistence.received");
143
- setTimeout(() => this.emit("persistence.pullCompleted"), 0);
144
- };
145
- const saveQueue = {
146
- added: [],
147
- modified: [],
148
- removed: []
149
- };
150
- let isFlushing = false;
151
- const flushQueue = () => {
152
- if (!this.persistenceAdapter)
153
- throw new Error("Persistence adapter not found");
154
- if (ongoingSaves <= 0)
155
- this.emit("persistence.pushStarted");
156
- if (isFlushing)
157
- return;
158
- if (!hasPendingUpdates(saveQueue))
159
- return;
160
- isFlushing = true;
161
- ongoingSaves += 1;
162
- const currentItems = this.memoryArray();
163
- const changes = { ...saveQueue };
164
- saveQueue.added = [];
165
- saveQueue.modified = [];
166
- saveQueue.removed = [];
167
- this.persistenceAdapter.save(currentItems, changes).then(() => {
168
- this.emit("persistence.transmitted");
169
- }).catch((error) => {
170
- this.emit("persistence.error", error instanceof Error ? error : new Error(error));
171
- }).finally(() => {
172
- ongoingSaves -= 1;
173
- isFlushing = false;
174
- flushQueue();
175
- if (ongoingSaves <= 0)
176
- this.emit("persistence.pushCompleted");
177
- });
178
- };
179
- this.on("added", (item) => {
180
- if (!isInitialized) {
181
- pendingUpdates.added.push(item);
182
- return;
183
- }
184
- saveQueue.added.push(item);
185
- flushQueue();
186
- });
187
- this.on("changed", (item) => {
188
- if (!isInitialized) {
189
- pendingUpdates.modified.push(item);
190
- return;
191
- }
192
- saveQueue.modified.push(item);
193
- flushQueue();
194
- });
195
- this.on("removed", (item) => {
196
- if (!isInitialized) {
197
- pendingUpdates.removed.push(item);
198
- return;
199
- }
200
- saveQueue.removed.push(item);
201
- flushQueue();
202
- });
203
- this.persistenceAdapter.register((data) => loadPersistentData(data)).then(async () => {
204
- if (!this.persistenceAdapter)
205
- throw new Error("Persistence adapter not found");
206
- let currentItems = this.memoryArray();
207
- await loadPersistentData();
208
- while (hasPendingUpdates(pendingUpdates)) {
209
- const added = pendingUpdates.added.splice(0);
210
- const modified = pendingUpdates.modified.splice(0);
211
- const removed = pendingUpdates.removed.splice(0);
212
- currentItems = applyUpdates(this.memoryArray(), { added, modified, removed });
213
- await this.persistenceAdapter.save(currentItems, { added, modified, removed }).then(() => {
214
- this.emit("persistence.transmitted");
215
- });
216
- }
217
- await loadPersistentData();
218
- isInitialized = true;
219
- setTimeout(() => this.emit("persistence.init"), 0);
220
- }).catch((error) => {
221
- this.emit("persistence.error", error instanceof Error ? error : new Error(error));
55
+ return value;
56
+ },
57
+ enumerable: true,
58
+ configurable: true
222
59
  });
223
- }
224
- this.persistenceReadyPromise = new Promise((resolve, reject) => {
225
- if (!this.persistenceAdapter)
226
- return resolve();
227
- this.once("persistence.init", resolve);
228
- this.once("persistence.error", reject);
229
- });
230
- _Collection.onCreationCallbacks.forEach((callback) => callback(this));
231
- }
232
- static getCollections() {
233
- return _Collection.collections;
60
+ return memo;
61
+ }, {});
234
62
  }
235
- static onCreation(callback) {
236
- _Collection.onCreationCallbacks.push(callback);
237
- }
238
- static onDispose(callback) {
239
- _Collection.onDisposeCallbacks.push(callback);
240
- }
241
- /**
242
- * Executes a batch operation, allowing multiple modifications to the collection
243
- * while deferring index rebuilding until all operations in the batch are completed.
244
- * This improves performance by avoiding repetitive index recalculations and
245
- * provides atomicity for the batch of operations.
246
- * @param callback - The batch operation to execute.
247
- */
248
- static batch(callback) {
249
- _Collection.batchOperationInProgress = true;
250
- _Collection.collections.reduce((memo, collection) => () => collection.batch(() => memo()), callback)();
251
- _Collection.batchOperationInProgress = false;
252
- }
253
- /**
254
- * Checks whether the collection is currently performing a pull operation
255
- * ⚡️ this function is reactive!
256
- * (loading data from the persistence adapter).
257
- * @returns A boolean indicating if the collection is in the process of pulling data.
258
- */
259
- isPulling() {
260
- return this.isPullingSignal.get() ?? false;
261
- }
262
- /**
263
- * Checks whether the collection is currently performing a push operation
264
- * ⚡️ this function is reactive!
265
- * (saving data to the persistence adapter).
266
- * @returns A boolean indicating if the collection is in the process of pushing data.
267
- */
268
- isPushing() {
269
- return this.isPushingSignal.get() ?? false;
270
- }
271
- /**
272
- * Checks whether the collection is currently performing either a pull or push operation,
273
- * ⚡️ this function is reactive!
274
- * indicating that it is loading or saving data.
275
- * @returns A boolean indicating if the collection is in the process of loading or saving data.
276
- */
277
- isLoading() {
278
- const isPulling = this.isPulling();
279
- const isPushing = this.isPushing();
280
- return isPulling || isPushing;
281
- }
282
- /**
283
- * Retrieves the current debug mode status of the collection.
284
- * @returns A boolean indicating whether debug mode is enabled for the collection.
285
- */
286
- getDebugMode() {
287
- return this.debugMode;
288
- }
289
- /**
290
- * Enables or disables debug mode for the collection.
291
- * When debug mode is enabled, additional debugging information and events are emitted.
292
- * @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) debug mode.
293
- */
294
- setDebugMode(enable) {
295
- this.debugMode = enable;
296
- }
297
- /**
298
- * Enables or disables field tracking for the collection.
299
- * @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) field tracking.
300
- */
301
- setFieldTracking(enable) {
302
- this.fieldTracking = enable;
303
- }
304
- /**
305
- * Resolves when the persistence adapter finished initializing
306
- * and the collection is ready to be used.
307
- * @returns A promise that resolves when the collection is ready.
308
- * @example
309
- * ```ts
310
- * const collection = new Collection({
311
- * persistence: // ...
312
- * })
313
- * await collection.isReady()
314
- *
315
- * collection.insert({ name: 'Item 1' })
316
- */
317
- async isReady() {
318
- return this.persistenceReadyPromise;
63
+ transform(rawItem) {
64
+ const item = this.options.fieldTracking ? this.addGetters(rawItem) : rawItem;
65
+ if (!this.options.transform)
66
+ return item;
67
+ return this.options.transform(item);
319
68
  }
320
- profile(fn, measureFunction) {
321
- if (!this.debugMode)
322
- return fn();
323
- const startTime = performance.now();
324
- const result = fn();
325
- const endTime = performance.now();
326
- measureFunction(endTime - startTime);
327
- return result;
69
+ getItems() {
70
+ const items = this.getFilteredItems();
71
+ const { sort, skip, limit } = this.options;
72
+ const sorted = sort ? sortItems(items, sort) : items;
73
+ const skipped = skip ? sorted.slice(skip) : sorted;
74
+ const limited = limit ? skipped.slice(0, limit) : skipped;
75
+ const idExcluded = this.options.fields && this.options.fields.id === 0;
76
+ return limited.map((item) => {
77
+ if (!this.options.fields)
78
+ return item;
79
+ return {
80
+ ...idExcluded ? {} : { id: item.id },
81
+ ...project(item, this.options.fields)
82
+ };
83
+ });
328
84
  }
329
- executeInDebugMode(fn) {
330
- if (!this.debugMode)
85
+ depend(changeEvents) {
86
+ if (!this.options.reactive)
331
87
  return;
332
- const callstack = new Error().stack || "";
333
- fn(callstack);
334
- }
335
- rebuildIndices() {
336
- this.indicesOutdated = true;
337
- if (this.batchOperationInProgress)
88
+ if (!isInReactiveScope(this.options.reactive))
338
89
  return;
339
- this.rebuildAllIndices();
340
- }
341
- rebuildAllIndices() {
342
- this.idIndex.clear();
343
- this.memory().map((item, index) => {
344
- this.idIndex.set(serializeValue(item.id), /* @__PURE__ */ new Set([index]));
345
- });
346
- this.indexProviders.forEach((index) => index.rebuild(this.memoryArray()));
347
- this.indicesOutdated = false;
348
- }
349
- getIndexInfo(selector) {
350
- const isIdOnlySelector = selector != null && Object.keys(selector).length === 1 && "id" in selector;
351
- if (selector == null) {
352
- return {
353
- matched: false,
354
- positions: [],
355
- optimizedSelector: {}
90
+ const signal = this.options.reactive.create();
91
+ signal.depend();
92
+ const notify = () => signal.notify();
93
+ function buildNotifier(event) {
94
+ const eventHandler = changeEvents[event];
95
+ return (...args) => {
96
+ if (eventHandler === true) {
97
+ notify();
98
+ return;
99
+ }
100
+ if (typeof eventHandler !== "function")
101
+ return;
102
+ eventHandler(notify)(...args);
356
103
  };
357
104
  }
358
- if (!isIdOnlySelector && this.indicesOutdated) {
359
- return {
360
- matched: false,
361
- positions: [],
362
- optimizedSelector: selector
363
- };
105
+ const stop = this.observeRawChanges({
106
+ added: buildNotifier("added"),
107
+ addedBefore: buildNotifier("addedBefore"),
108
+ changed: buildNotifier("changed"),
109
+ changedField: buildNotifier("changedField"),
110
+ movedBefore: buildNotifier("movedBefore"),
111
+ removed: buildNotifier("removed")
112
+ }, true);
113
+ if (this.options.reactive.onDispose) {
114
+ this.options.reactive.onDispose(() => stop(), signal);
364
115
  }
365
- return getIndexInfo.default(this.indexProviders, selector);
366
- }
367
- getItemAndIndex(selector) {
368
- const memory = this.memoryArray();
369
- const indexInfo = this.getIndexInfo(selector);
370
- const items = indexInfo.matched ? indexInfo.positions.map((index2) => memory[index2]) : memory;
371
- const item = items.find((document) => match(document, selector));
372
- const foundInIndex = indexInfo.matched && indexInfo.positions.find((itemIndex) => memory[itemIndex] === item);
373
- const index = foundInIndex || memory.findIndex((document) => document === item);
374
- if (item == null)
375
- return { item: null, index: -1 };
376
- if (index === -1)
377
- throw new Error("Cannot resolve index for item");
378
- return { item, index };
379
- }
380
- deleteFromIdIndex(id, index) {
381
- this.idIndex.delete(serializeValue(id));
382
- if (!this.batchOperationInProgress)
383
- return;
384
- this.idIndex.forEach(([currenIndex], key) => {
385
- if (currenIndex > index) {
386
- this.idIndex.set(key, /* @__PURE__ */ new Set([currenIndex - 1]));
387
- }
388
- });
389
- }
390
- memory() {
391
- return this.options.memory;
392
- }
393
- memoryArray() {
394
- return this.memory().map((item) => item);
116
+ this.onCleanup(stop);
395
117
  }
396
- transform(item) {
397
- if (!this.options.transform)
398
- return item;
399
- return this.options.transform(item);
400
- }
401
- getItems(selector) {
402
- return this.profile(() => {
403
- const indexInfo = this.getIndexInfo(selector);
404
- const matchItems = (item) => {
405
- if (indexInfo.optimizedSelector == null)
406
- return true;
407
- if (Object.keys(indexInfo.optimizedSelector).length <= 0)
408
- return true;
409
- const matches = match(item, indexInfo.optimizedSelector);
410
- return matches;
411
- };
412
- this.emit("getItems", selector);
413
- const memory = this.memoryArray();
414
- if (!indexInfo.matched) {
415
- if (isEqual(selector, {}))
416
- return memory;
417
- return memory.filter(matchItems);
418
- }
419
- const items = indexInfo.positions.map((index) => memory[index]);
420
- if (isEqual(indexInfo.optimizedSelector, {}))
421
- return items;
422
- return items.filter(matchItems);
423
- }, (measuredTime) => this.executeInDebugMode((callstack) => this.emit("_debug.getItems", callstack, selector, measuredTime)));
424
- }
425
- /**
426
- * Disposes the collection, unregisters persistence adapters, clears memory, and
427
- * cleans up all resources used by the collection.
428
- * @returns A promise that resolves when the collection is disposed.
429
- */
430
- async dispose() {
431
- var _a;
432
- if ((_a = this.persistenceAdapter) == null ? void 0 : _a.unregister)
433
- await this.persistenceAdapter.unregister();
434
- this.persistenceAdapter = null;
435
- this.memory().map(() => this.memory().pop());
436
- this.idIndex.clear();
437
- this.indexProviders = [];
438
- this.isDisposed = true;
439
- this.removeAllListeners();
440
- _Collection.collections = _Collection.collections.filter((collection) => collection !== this);
441
- _Collection.onDisposeCallbacks.forEach((callback) => callback(this));
442
- }
443
- /**
444
- * Finds multiple items in the collection based on a selector and optional options.
445
- * Returns a cursor for reactive data queries.
446
- * @template O - The options type for the find operation.
447
- * @param [selector] - The criteria to select items.
448
- * @param [options] - Options for the find operation, such as limit and sort.
449
- * @returns A cursor to fetch and observe the matching items.
450
- */
451
- find(selector, options) {
452
- if (this.isDisposed)
453
- throw new Error("Collection is disposed");
454
- if (selector !== void 0 && (!selector || typeof selector !== "object"))
455
- throw new Error("Invalid selector");
456
- const cursor = new Cursor.default(() => this.getItems(selector), {
457
- reactive: this.options.reactivity,
458
- fieldTracking: this.fieldTracking,
459
- ...options,
460
- transform: this.transform.bind(this),
461
- bindEvents: (requery) => {
462
- const handleRequery = () => {
463
- if (this.batchOperationInProgress) {
464
- this.postBatchCallbacks.add(requery);
465
- return;
466
- }
467
- requery();
118
+ ensureObserver() {
119
+ if (!this.observer) {
120
+ const observer = new Observer(() => {
121
+ const requery = () => {
122
+ observer.runChecks(this.getItems());
468
123
  };
469
- this.addListener("persistence.received", handleRequery);
470
- this.addListener("added", handleRequery);
471
- this.addListener("changed", handleRequery);
472
- this.addListener("removed", handleRequery);
473
- this.emit("observer.created", selector, options);
124
+ const cleanup = this.options.bindEvents && this.options.bindEvents(requery);
474
125
  return () => {
475
- this.removeListener("persistence.received", handleRequery);
476
- this.removeListener("added", handleRequery);
477
- this.removeListener("changed", handleRequery);
478
- this.removeListener("removed", handleRequery);
479
- this.emit("observer.disposed", selector, options);
126
+ if (cleanup)
127
+ cleanup();
480
128
  };
481
- }
482
- });
483
- this.emit("find", selector, options, cursor);
484
- this.executeInDebugMode((callstack) => this.emit("_debug.find", callstack, selector, options, cursor));
485
- return cursor;
129
+ });
130
+ this.onCleanup(() => observer.stop());
131
+ this.observer = observer;
132
+ }
133
+ return this.observer;
134
+ }
135
+ observeRawChanges(callbacks, skipInitial = false) {
136
+ const observer = this.ensureObserver();
137
+ observer.addCallbacks(callbacks, skipInitial);
138
+ observer.runChecks(this.getItems());
139
+ return () => {
140
+ observer.removeCallbacks(callbacks);
141
+ if (!observer.isEmpty())
142
+ return;
143
+ observer.stop();
144
+ this.observer = void 0;
145
+ };
486
146
  }
487
147
  /**
488
- * Finds a single item in the collection based on a selector and optional options.
489
- * ⚡️ this function is reactive!
490
- * Returns the found item or undefined if no item matches.
491
- * @template O - The options type for the find operation.
492
- * @param selector - The criteria to select the item.
493
- * @param [options] - Options for the find operation, such as projection.
494
- * @returns The found item or `undefined`.
148
+ * Cleans up all resources associated with the cursor, such as reactive bindings
149
+ * and event listeners. This method should be called when the cursor is no longer needed
150
+ * to prevent memory leaks.
495
151
  */
496
- findOne(selector, options) {
497
- if (this.isDisposed)
498
- throw new Error("Collection is disposed");
499
- const cursor = this.find(selector, {
500
- limit: 1,
501
- ...options
152
+ cleanup() {
153
+ this.onCleanupCallbacks.forEach((callback) => {
154
+ callback();
502
155
  });
503
- const returnValue = cursor.fetch()[0] || void 0;
504
- this.emit("findOne", selector, options, returnValue);
505
- this.executeInDebugMode((callstack) => this.emit("_debug.findOne", callstack, selector, options, returnValue));
506
- return returnValue;
156
+ this.onCleanupCallbacks = [];
507
157
  }
508
158
  /**
509
- * Performs a batch operation, deferring index rebuilds and allowing multiple
510
- * modifications to be made atomically. Executes any post-batch callbacks afterwards.
511
- * @param callback - The batch operation to execute.
159
+ * Registers a cleanup callback to be executed when the `cleanup` method is called.
160
+ * Useful for managing resources and ensuring proper cleanup of bindings or listeners.
161
+ * @param callback - A function to be executed during cleanup.
512
162
  */
513
- batch(callback) {
514
- this.batchOperationInProgress = true;
515
- callback();
516
- this.batchOperationInProgress = false;
517
- this.rebuildAllIndices();
518
- this.postBatchCallbacks.forEach((callback_) => callback_());
519
- this.postBatchCallbacks.clear();
163
+ onCleanup(callback) {
164
+ this.onCleanupCallbacks.push(callback);
520
165
  }
521
166
  /**
522
- * Inserts a single item into the collection. Generates a unique ID if not provided.
523
- * @param item - The item to insert.
524
- * @returns The ID of the inserted item.
525
- * @throws {Error} If the collection is disposed or the item has an invalid ID.
167
+ * Iterates over each item in the cursor's result set, applying the provided callback
168
+ * function to each transformed item.
169
+ * ⚡️ this function is reactive!
170
+ * @param callback - A function to execute for each item in the result set.
171
+ * @param callback.item - The transformed item.
526
172
  */
527
- insert(item) {
528
- if (this.isDisposed)
529
- throw new Error("Collection is disposed");
530
- if (!item)
531
- throw new Error("Invalid item");
532
- const newItem = { id: randomId(), ...item };
533
- this.emit("validate", newItem);
534
- if (this.idIndex.has(serializeValue(newItem.id)))
535
- throw new Error("Item with same id already exists");
536
- this.memory().push(newItem);
537
- const itemIndex = this.memory().findIndex((document) => document === newItem);
538
- this.idIndex.set(serializeValue(newItem.id), /* @__PURE__ */ new Set([itemIndex]));
539
- this.rebuildIndices();
540
- this.emit("added", newItem);
541
- this.emit("insert", newItem);
542
- this.executeInDebugMode((callstack) => this.emit("_debug.insert", callstack, newItem));
543
- return newItem.id;
173
+ forEach(callback) {
174
+ const items = this.getItems();
175
+ this.depend({
176
+ addedBefore: true,
177
+ removed: true,
178
+ movedBefore: true,
179
+ ...this.options.fieldTracking ? {} : { changed: true }
180
+ });
181
+ items.forEach((item) => {
182
+ callback(this.transform(item));
183
+ });
544
184
  }
545
185
  /**
546
- * Inserts multiple items into the collection. Generates unique IDs for items if not provided.
547
- * @param items - The items to insert.
548
- * @returns An array of IDs of the inserted items.
549
- * @throws {Error} If the collection is disposed or the items are invalid.
186
+ * Creates a new array populated with the results of applying the provided callback
187
+ * function to each transformed item in the cursor's result set.
188
+ * ⚡️ this function is reactive!
189
+ * @template V - The type of the items in the resulting array.
190
+ * @param callback - A function to execute for each item in the result set.
191
+ * @param callback.item - The transformed item.
192
+ * @returns An array of results after applying the callback to each item.
550
193
  */
551
- insertMany(items) {
552
- if (this.isDisposed)
553
- throw new Error("Collection is disposed");
554
- if (!items)
555
- throw new Error("Invalid items");
556
- if (items.length === 0) {
557
- return [];
558
- }
559
- const ids = [];
560
- this.batch(() => {
561
- items.forEach((item) => {
562
- ids.push(this.insert(item));
563
- });
194
+ map(callback) {
195
+ const results = [];
196
+ this.forEach((item) => {
197
+ results.push(callback(item));
564
198
  });
565
- return ids;
199
+ return results;
566
200
  }
567
201
  /**
568
- * Updates a single item in the collection that matches the given selector.
569
- * @param selector - The criteria to select the item to update.
570
- * @param modifier - The modifications to apply to the item.
571
- * @param [options] - Optional settings for the update operation.
572
- * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
573
- * @returns The number of items updated (0 or 1).
574
- * @throws {Error} If the collection is disposed or invalid arguments are provided.
202
+ * Fetches all transformed items from the cursor's result set as an array.
203
+ * Automatically applies filtering, sorting, and limiting as per the cursor's options.
204
+ * ⚡️ this function is reactive!
205
+ * @returns An array of transformed items in the result set.
575
206
  */
576
- updateOne(selector, modifier, options) {
577
- if (this.isDisposed)
578
- throw new Error("Collection is disposed");
579
- if (!selector)
580
- throw new Error("Invalid selector");
581
- if (!modifier)
582
- throw new Error("Invalid modifier");
583
- const { $setOnInsert, ...restModifier } = modifier;
584
- const { item, index } = this.getItemAndIndex(selector);
585
- if (item == null) {
586
- if (options == null ? void 0 : options.upsert) {
587
- const newItem = modify({}, {
588
- ...restModifier,
589
- $set: {
590
- ...$setOnInsert,
591
- ...restModifier.$set
592
- }
593
- });
594
- if (newItem.id != null && this.getItemAndIndex({ id: newItem.id }).item != null) {
595
- throw new Error("Item with same id already exists");
596
- }
597
- this.insert(newItem);
598
- }
599
- } else {
600
- const modifiedItem = modify(deepClone.default(item), restModifier);
601
- if (item.id !== modifiedItem.id && this.getItemAndIndex({ id: modifiedItem.id }).item != null) {
602
- throw new Error("Item with same id already exists");
603
- }
604
- this.emit("validate", modifiedItem);
605
- this.memory().splice(index, 1, modifiedItem);
606
- this.rebuildIndices();
607
- this.emit("changed", modifiedItem, restModifier);
608
- }
609
- this.emit("updateOne", selector, modifier);
610
- this.executeInDebugMode((callstack) => this.emit("_debug.updateOne", callstack, selector, modifier));
611
- if (item == null && !(options == null ? void 0 : options.upsert))
612
- return 0;
613
- return 1;
207
+ fetch() {
208
+ return this.map((item) => item);
614
209
  }
615
210
  /**
616
- * Updates multiple items in the collection that match the given selector.
617
- * @param selector - The criteria to select the items to update.
618
- * @param modifier - The modifications to apply to the items.
619
- * @param [options] - Optional settings for the update operation.
620
- * @param [options.upsert] - If `true`, creates new items if no items match the selector.
621
- * @returns The number of items updated.
622
- * @throws {Error} If the collection is disposed or invalid arguments are provided.
211
+ * Counts the total number of items in the cursor's result set after applying
212
+ * filtering and other criteria.
213
+ * ⚡️ this function is reactive!
214
+ * @returns The total number of items in the result set.
623
215
  */
624
- updateMany(selector, modifier, options) {
625
- if (this.isDisposed)
626
- throw new Error("Collection is disposed");
627
- if (!selector)
628
- throw new Error("Invalid selector");
629
- if (!modifier)
630
- throw new Error("Invalid modifier");
631
- const { $setOnInsert, ...restModifier } = modifier;
632
- const items = this.getItems(selector);
633
- if (items.length === 0 && (options == null ? void 0 : options.upsert)) {
634
- const newItem = modify({}, {
635
- ...restModifier,
636
- $set: {
637
- ...$setOnInsert,
638
- ...restModifier.$set
639
- }
640
- });
641
- if (newItem.id != null && this.getItemAndIndex({ id: newItem.id }).item != null) {
642
- throw new Error("Item with same id already exists");
643
- }
644
- this.insert(newItem);
645
- }
646
- const changes = items.map((item) => {
647
- const { index } = this.getItemAndIndex({ id: item.id });
648
- if (index === -1)
649
- throw new Error(`Cannot resolve index for item with id '${item.id}'`);
650
- const modifiedItem = modify(deepClone.default(item), restModifier);
651
- if (item.id !== modifiedItem.id && this.getItemAndIndex({ id: modifiedItem.id }).item != null) {
652
- throw new Error(`Item with same id '${modifiedItem.id}' already exists`);
653
- }
654
- this.emit("validate", modifiedItem);
655
- return {
656
- item: modifiedItem,
657
- index
658
- };
659
- });
660
- changes.forEach(({ item, index }) => {
661
- this.memory().splice(index, 1, item);
216
+ count() {
217
+ const items = this.getItems();
218
+ this.depend({
219
+ added: true,
220
+ removed: true
662
221
  });
663
- this.rebuildIndices();
664
- changes.forEach(({ item }) => {
665
- this.emit("changed", item, restModifier);
666
- });
667
- this.emit("updateMany", selector, modifier);
668
- this.executeInDebugMode((callstack) => this.emit("_debug.updateMany", callstack, selector, modifier));
669
- return changes.length === 0 && (options == null ? void 0 : options.upsert) ? 1 : changes.length;
222
+ return items.length;
670
223
  }
671
224
  /**
672
- * Replaces a single item in the collection that matches the given selector.
673
- * @param selector - The criteria to select the item to replace.
674
- * @param replacement - The item to replace the selected item with.
675
- * @param [options] - Optional settings for the replace operation.
676
- * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
677
- * @returns The number of items replaced (0 or 1).
678
- * @throws {Error} If the collection is disposed or invalid arguments are provided.
225
+ * Observes changes to the cursor's result set and triggers the specified callbacks
226
+ * when items are added, removed, or updated. Supports reactivity and transformation.
227
+ * @param callbacks - An object containing the callback functions to handle different change events.
228
+ * @param callbacks.added - Triggered when an item is added to the result set.
229
+ * @param callbacks.removed - Triggered when an item is removed from the result set.
230
+ * @param callbacks.changed - Triggered when an item in the result set is modified.
231
+ * @param callbacks.addedBefore - Triggered when an item is added before another item in the result set.
232
+ * @param callbacks.movedBefore - Triggered when an item is moved before another item in the result set.
233
+ * @param callbacks.changedField - Triggered when a specific field of an item changes.
234
+ * @param skipInitial - A boolean indicating whether to skip the initial notification of the current result set.
235
+ * @returns A function to stop observing changes.
679
236
  */
680
- replaceOne(selector, replacement, options) {
681
- if (this.isDisposed)
682
- throw new Error("Collection is disposed");
683
- if (!selector)
684
- throw new Error("Invalid selector");
685
- const { item, index } = this.getItemAndIndex(selector);
686
- if (item == null) {
687
- if (options == null ? void 0 : options.upsert) {
688
- if (replacement.id != null && this.getItemAndIndex({ id: replacement.id }).item != null) {
689
- throw new Error("Item with same id already exists");
237
+ observeChanges(callbacks, skipInitial = false) {
238
+ return this.observeRawChanges(Object.entries(callbacks).reduce((memo, [callbackName, callback]) => {
239
+ if (!callback)
240
+ return memo;
241
+ return {
242
+ ...memo,
243
+ [callbackName]: (item, before) => {
244
+ const transformedValue = this.transform(item);
245
+ const hasBeforeParameter = before !== void 0;
246
+ const transformedBeforeValue = hasBeforeParameter && before ? this.transform(before) : null;
247
+ return callback(transformedValue, ...hasBeforeParameter ? [transformedBeforeValue] : []);
690
248
  }
691
- this.insert(replacement);
692
- }
693
- } else {
694
- if (item.id !== replacement.id && this.getItemAndIndex({ id: replacement.id }).item != null) {
695
- throw new Error("Item with same id already exists");
696
- }
697
- const modifiedItem = { id: item.id, ...replacement };
698
- this.emit("validate", modifiedItem);
699
- this.memory().splice(index, 1, modifiedItem);
700
- this.rebuildIndices();
701
- this.emit("changed", modifiedItem, replacement);
702
- }
703
- this.emit("replaceOne", selector, replacement);
704
- this.executeInDebugMode((callstack) => this.emit("_debug.replaceOne", callstack, selector, replacement));
705
- if (item == null && !(options == null ? void 0 : options.upsert))
706
- return 0;
707
- return 1;
708
- }
709
- /**
710
- * Removes a single item from the collection that matches the given selector.
711
- * @param selector - The criteria to select the item to remove.
712
- * @returns The number of items removed (0 or 1).
713
- * @throws {Error} If the collection is disposed or invalid arguments are provided.
714
- */
715
- removeOne(selector) {
716
- if (this.isDisposed)
717
- throw new Error("Collection is disposed");
718
- if (!selector)
719
- throw new Error("Invalid selector");
720
- const { item, index } = this.getItemAndIndex(selector);
721
- if (item != null) {
722
- this.memory().splice(index, 1);
723
- this.deleteFromIdIndex(item.id, index);
724
- this.rebuildIndices();
725
- this.emit("removed", item);
726
- }
727
- this.emit("removeOne", selector);
728
- this.executeInDebugMode((callstack) => this.emit("_debug.removeOne", callstack, selector));
729
- return item == null ? 0 : 1;
249
+ };
250
+ }, {}), skipInitial);
730
251
  }
731
252
  /**
732
- * Removes multiple items from the collection that match the given selector.
733
- * @param selector - The criteria to select the items to remove.
734
- * @returns The number of items removed.
735
- * @throws {Error} If the collection is disposed or invalid arguments are provided.
253
+ * Forces the cursor to re-evaluate its result set by re-fetching items
254
+ * from the collection. This is useful when the underlying data or query
255
+ * criteria have changed, and you want to ensure the cursor reflects the latest state.
736
256
  */
737
- removeMany(selector) {
738
- if (this.isDisposed)
739
- throw new Error("Collection is disposed");
740
- if (!selector)
741
- throw new Error("Invalid selector");
742
- const items = this.getItems(selector);
743
- items.forEach((item) => {
744
- const index = this.memory().findIndex((document) => document === item);
745
- if (index === -1)
746
- throw new Error("Cannot resolve index for item");
747
- this.memory().splice(index, 1);
748
- this.deleteFromIdIndex(item.id, index);
749
- this.rebuildIndices();
750
- });
751
- items.forEach((item) => {
752
- this.emit("removed", item);
753
- });
754
- this.emit("removeMany", selector);
755
- this.executeInDebugMode((callstack) => this.emit("_debug.removeMany", callstack, selector));
756
- return items.length;
257
+ requery() {
258
+ if (!this.observer)
259
+ return;
260
+ this.observer.runChecks(this.getItems());
757
261
  }
758
- };
759
- __publicField(_Collection, "collections", []);
760
- __publicField(_Collection, "debugMode", false);
761
- __publicField(_Collection, "batchOperationInProgress", false);
762
- __publicField(_Collection, "fieldTracking", false);
763
- __publicField(_Collection, "onCreationCallbacks", []);
764
- __publicField(_Collection, "onDisposeCallbacks", []);
765
- /**
766
- * Enables debug mode for all collections.
767
- */
768
- __publicField(_Collection, "enableDebugMode", () => {
769
- _Collection.debugMode = true;
770
- _Collection.collections.forEach((collection) => {
771
- collection.setDebugMode(true);
772
- });
773
- });
774
- /**
775
- * Enables field tracking for all collections.
776
- * @param enable - A boolean indicating whether to enable field tracking.
777
- */
778
- __publicField(_Collection, "setFieldTracking", (enable) => {
779
- _Collection.fieldTracking = enable;
780
- _Collection.collections.forEach((collection) => {
781
- collection.setFieldTracking(enable);
782
- });
783
- });
784
- let Collection = _Collection;
785
- exports.createIndex = createIndex.default;
786
- exports.default = Collection;
262
+ }
263
+ exports.default = Cursor;
264
+ exports.isInReactiveScope = isInReactiveScope;