@signaldb/svelte 2.0.0-beta.6 → 2.0.0-beta.7

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 (89) hide show
  1. package/dist/base/core/src/AsyncDataAdapter.d.ts +64 -0
  2. package/dist/base/core/src/AsyncDataAdapter.js +450 -0
  3. package/dist/base/core/src/AutoFetchDataAdapter.d.ts +112 -0
  4. package/dist/base/core/src/AutoFetchDataAdapter.js +652 -0
  5. package/dist/base/core/src/Collection/Cursor.d.ts +113 -0
  6. package/dist/base/core/src/Collection/Cursor.js +293 -0
  7. package/dist/base/core/src/Collection/Observer.d.ts +64 -0
  8. package/dist/base/core/src/Collection/Observer.js +181 -0
  9. package/dist/base/core/src/Collection/index.d.ts +294 -0
  10. package/dist/base/core/src/Collection/index.js +580 -0
  11. package/dist/base/core/src/Collection/types.d.ts +28 -0
  12. package/dist/base/core/src/Collection/types.js +2 -0
  13. package/dist/base/core/src/DataAdapter.d.ts +35 -0
  14. package/dist/base/core/src/DataAdapter.js +2 -0
  15. package/dist/base/core/src/DefaultDataAdapter.d.ts +35 -0
  16. package/dist/base/core/src/DefaultDataAdapter.js +398 -0
  17. package/dist/base/core/src/WorkerDataAdapter.d.ts +25 -0
  18. package/dist/base/core/src/WorkerDataAdapter.js +211 -0
  19. package/dist/base/core/src/WorkerDataAdapterHost.d.ts +62 -0
  20. package/dist/base/core/src/WorkerDataAdapterHost.js +409 -0
  21. package/dist/base/core/src/createIndex.d.ts +7 -0
  22. package/dist/base/core/src/createIndex.js +120 -0
  23. package/dist/base/core/src/createIndexProvider.d.ts +8 -0
  24. package/dist/base/core/src/createIndexProvider.js +11 -0
  25. package/dist/base/core/src/createReactivityAdapter.d.ts +8 -0
  26. package/dist/base/core/src/createReactivityAdapter.js +11 -0
  27. package/dist/base/core/src/createStorageAdapter.d.ts +9 -0
  28. package/dist/base/core/src/createStorageAdapter.js +11 -0
  29. package/dist/base/core/src/getIndexInfo.d.ts +39 -0
  30. package/dist/base/core/src/getIndexInfo.js +176 -0
  31. package/dist/base/core/src/index.d.ts +22 -0
  32. package/dist/base/core/src/index.js +39 -0
  33. package/dist/base/core/src/types/Dependency.d.ts +4 -0
  34. package/dist/base/core/src/types/Dependency.js +2 -0
  35. package/dist/base/core/src/types/IndexProvider.d.ts +26 -0
  36. package/dist/base/core/src/types/IndexProvider.js +2 -0
  37. package/dist/base/core/src/types/Modifier.d.ts +46 -0
  38. package/dist/base/core/src/types/Modifier.js +3 -0
  39. package/dist/base/core/src/types/ReactivityAdapter.d.ts +6 -0
  40. package/dist/base/core/src/types/ReactivityAdapter.js +2 -0
  41. package/dist/base/core/src/types/Selector.d.ts +46 -0
  42. package/dist/base/core/src/types/Selector.js +3 -0
  43. package/dist/base/core/src/types/Signal.d.ts +4 -0
  44. package/dist/base/core/src/types/Signal.js +2 -0
  45. package/dist/base/core/src/types/StorageAdapter.d.ts +20 -0
  46. package/dist/base/core/src/types/StorageAdapter.js +2 -0
  47. package/dist/base/core/src/utils/EventEmitter.d.ts +71 -0
  48. package/dist/base/core/src/utils/EventEmitter.js +144 -0
  49. package/dist/base/core/src/utils/batchOnNextTick.d.ts +16 -0
  50. package/dist/base/core/src/utils/batchOnNextTick.js +72 -0
  51. package/dist/base/core/src/utils/compact.d.ts +9 -0
  52. package/dist/base/core/src/utils/compact.js +21 -0
  53. package/dist/base/core/src/utils/createSignal.d.ts +14 -0
  54. package/dist/base/core/src/utils/createSignal.js +38 -0
  55. package/dist/base/core/src/utils/deepClone.d.ts +17 -0
  56. package/dist/base/core/src/utils/deepClone.js +68 -0
  57. package/dist/base/core/src/utils/get.d.ts +9 -0
  58. package/dist/base/core/src/utils/get.js +27 -0
  59. package/dist/base/core/src/utils/getMatchingKeys.d.ts +19 -0
  60. package/dist/base/core/src/utils/getMatchingKeys.js +49 -0
  61. package/dist/base/core/src/utils/intersection.d.ts +9 -0
  62. package/dist/base/core/src/utils/intersection.js +16 -0
  63. package/dist/base/core/src/utils/isEqual.d.ts +14 -0
  64. package/dist/base/core/src/utils/isEqual.js +43 -0
  65. package/dist/base/core/src/utils/isFieldExpression.d.ts +11 -0
  66. package/dist/base/core/src/utils/isFieldExpression.js +52 -0
  67. package/dist/base/core/src/utils/match.d.ts +12 -0
  68. package/dist/base/core/src/utils/match.js +16 -0
  69. package/dist/base/core/src/utils/modify.d.ts +14 -0
  70. package/dist/base/core/src/utils/modify.js +29 -0
  71. package/dist/base/core/src/utils/project.d.ts +15 -0
  72. package/dist/base/core/src/utils/project.js +43 -0
  73. package/dist/base/core/src/utils/queryId.d.ts +9 -0
  74. package/dist/base/core/src/utils/queryId.js +30 -0
  75. package/dist/base/core/src/utils/randomId.d.ts +7 -0
  76. package/dist/base/core/src/utils/randomId.js +12 -0
  77. package/dist/base/core/src/utils/reactiveOrAsync.d.ts +59 -0
  78. package/dist/base/core/src/utils/reactiveOrAsync.js +93 -0
  79. package/dist/base/core/src/utils/serializeValue.d.ts +12 -0
  80. package/dist/base/core/src/utils/serializeValue.js +27 -0
  81. package/dist/base/core/src/utils/set.d.ts +13 -0
  82. package/dist/base/core/src/utils/set.js +43 -0
  83. package/dist/base/core/src/utils/sortItems.d.ts +12 -0
  84. package/dist/base/core/src/utils/sortItems.js +23 -0
  85. package/dist/base/core/src/utils/uniqueBy.d.ts +10 -0
  86. package/dist/base/core/src/utils/uniqueBy.js +19 -0
  87. package/dist/reactivity-adapters/svelte/index.svelte.d.ts +10 -0
  88. package/dist/reactivity-adapters/svelte/index.svelte.js +36 -0
  89. package/package.json +3 -3
@@ -0,0 +1,580 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createIndex = void 0;
7
+ const EventEmitter_1 = __importDefault(require("../utils/EventEmitter"));
8
+ const createSignal_1 = __importDefault(require("../utils/createSignal"));
9
+ const randomId_1 = __importDefault(require("../utils/randomId"));
10
+ const DefaultDataAdapter_1 = __importDefault(require("../DefaultDataAdapter"));
11
+ const modify_1 = __importDefault(require("../utils/modify"));
12
+ const deepClone_1 = __importDefault(require("../utils/deepClone"));
13
+ const queryId_1 = __importDefault(require("../utils/queryId"));
14
+ const Cursor_1 = __importDefault(require("./Cursor"));
15
+ var createIndex_1 = require("../createIndex");
16
+ Object.defineProperty(exports, "createIndex", { enumerable: true, get: function () { return __importDefault(createIndex_1).default; } });
17
+ /**
18
+ * Represents a collection of data items with support for in-memory operations,
19
+ * persistence, reactivity, and event-based notifications. The collection provides
20
+ * CRUD operations, observer patterns, and batch operations.
21
+ * @template T - The type of the items stored in the collection.
22
+ * @template I - The type of the unique identifier for the items.
23
+ * @template U - The transformed item type after applying transformations (default is T).
24
+ */
25
+ class Collection extends EventEmitter_1.default {
26
+ static collections = [];
27
+ static debugMode = false;
28
+ static batchOperationInProgress = false;
29
+ static fieldTracking = false;
30
+ static onCreationCallbacks = [];
31
+ static onDisposeCallbacks = [];
32
+ static getCollections() {
33
+ return Collection.collections;
34
+ }
35
+ static onCreation(callback) {
36
+ Collection.onCreationCallbacks.push(callback);
37
+ }
38
+ static onDispose(callback) {
39
+ Collection.onDisposeCallbacks.push(callback);
40
+ }
41
+ /**
42
+ * Enables debug mode for all collections.
43
+ */
44
+ static enableDebugMode = () => {
45
+ Collection.debugMode = true;
46
+ Collection.collections.forEach((collection) => {
47
+ collection.setDebugMode(true);
48
+ });
49
+ };
50
+ /**
51
+ * Enables field tracking for all collections.
52
+ * @param enable - A boolean indicating whether to enable field tracking.
53
+ */
54
+ static setFieldTracking = (enable) => {
55
+ Collection.fieldTracking = enable;
56
+ Collection.collections.forEach((collection) => {
57
+ collection.setFieldTracking(enable);
58
+ });
59
+ };
60
+ static batch(callback) {
61
+ Collection.batchOperationInProgress = true;
62
+ const execute = () => Collection.collections.reduce((memo, collection) => () => {
63
+ return collection.batch(memo);
64
+ }, callback)();
65
+ const maybePromise = execute();
66
+ const afterBatch = () => {
67
+ Collection.batchOperationInProgress = false;
68
+ };
69
+ if (maybePromise && typeof maybePromise.then === 'function') {
70
+ return maybePromise
71
+ .then(() => afterBatch());
72
+ }
73
+ else {
74
+ afterBatch();
75
+ }
76
+ }
77
+ name;
78
+ backend;
79
+ options;
80
+ isPullingSignal;
81
+ isPushingSignal;
82
+ readySignal;
83
+ debugMode;
84
+ batchOperationInProgress = false;
85
+ isDisposed = false;
86
+ postBatchCallbacks = new Set();
87
+ fieldTracking = false;
88
+ queryListenersMap = new Map();
89
+ constructor(nameOrOptions, maybeDataAdapter, maybeOptions) {
90
+ super();
91
+ const name = typeof nameOrOptions === 'string'
92
+ ? nameOrOptions
93
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
94
+ : nameOrOptions?.name || `${this.constructor.name}-${(0, randomId_1.default)()}`;
95
+ const options = typeof nameOrOptions === 'string'
96
+ ? maybeOptions || {}
97
+ : nameOrOptions || {};
98
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
99
+ const persistence = options.persistence;
100
+ const dataAdapter = maybeDataAdapter || new DefaultDataAdapter_1.default({
101
+ ...persistence ? { storage: () => persistence } : {},
102
+ });
103
+ Collection.collections.push(this);
104
+ this.name = name;
105
+ this.options = { ...options };
106
+ this.fieldTracking = this.options.fieldTracking ?? Collection.fieldTracking;
107
+ this.debugMode = this.options.enableDebugMode ?? Collection.debugMode;
108
+ this.isPullingSignal = (0, createSignal_1.default)(this.options.reactivity, false);
109
+ this.isPushingSignal = (0, createSignal_1.default)(this.options.reactivity, false);
110
+ this.readySignal = (0, createSignal_1.default)(this.options.reactivity, false);
111
+ this.backend = dataAdapter.createCollectionBackend(this, this.options.indices ?? []);
112
+ void this.backend.isReady()
113
+ .then(() => {
114
+ this.readySignal.set(true);
115
+ })
116
+ .catch(() => { });
117
+ Collection.onCreationCallbacks.forEach(callback => callback(this));
118
+ }
119
+ isBatchOperationInProgress() {
120
+ return Collection.batchOperationInProgress || this.batchOperationInProgress;
121
+ }
122
+ /**
123
+ * Checks whether the collection is currently performing a pull operation
124
+ * ⚡️ this function is reactive!
125
+ * (loading data from the persistence adapter).
126
+ * @returns A boolean indicating if the collection is in the process of pulling data.
127
+ */
128
+ isPulling() {
129
+ return this.isPullingSignal.get() ?? false;
130
+ }
131
+ /**
132
+ * Checks whether the collection is currently performing a push operation
133
+ * ⚡️ this function is reactive!
134
+ * (saving data to the persistence adapter).
135
+ * @returns A boolean indicating if the collection is in the process of pushing data.
136
+ */
137
+ isPushing() {
138
+ return this.isPushingSignal.get() ?? false;
139
+ }
140
+ /**
141
+ * Checks whether the collection is currently performing either a pull or push operation,
142
+ * ⚡️ this function is reactive!
143
+ * indicating that it is loading or saving data.
144
+ * @returns A boolean indicating if the collection is in the process of loading or saving data.
145
+ */
146
+ isLoading() {
147
+ const isPulling = this.isPulling();
148
+ const isPushing = this.isPushing();
149
+ return isPulling || isPushing;
150
+ }
151
+ /**
152
+ * Retrieves the current debug mode status of the collection.
153
+ * @returns A boolean indicating whether debug mode is enabled for the collection.
154
+ */
155
+ getDebugMode() {
156
+ return this.debugMode;
157
+ }
158
+ /**
159
+ * Enables or disables debug mode for the collection.
160
+ * When debug mode is enabled, additional debugging information and events are emitted.
161
+ * @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) debug mode.
162
+ */
163
+ setDebugMode(enable) {
164
+ this.debugMode = enable;
165
+ }
166
+ /**
167
+ * Enables or disables field tracking for the collection.
168
+ * @param enable - A boolean indicating whether to enable (`true`) or disable (`false`) field tracking.
169
+ */
170
+ setFieldTracking(enable) {
171
+ this.fieldTracking = enable;
172
+ }
173
+ /**
174
+ * Resolves when the persistence adapter finished initializing
175
+ * and the collection is ready to be used.
176
+ * @returns A promise that resolves when the collection is ready.
177
+ * @example
178
+ * ```ts
179
+ * const collection = new Collection({
180
+ * persistence: // ...
181
+ * })
182
+ * await collection.isReady()
183
+ *
184
+ * collection.insert({ name: 'Item 1' })
185
+ */
186
+ async ready() {
187
+ return this.backend.isReady();
188
+ }
189
+ /**
190
+ * Checks if the collection is ready.
191
+ * ⚡️ this function is reactive!
192
+ * @returns A boolean indicating whether the collection is ready.
193
+ */
194
+ isReady() {
195
+ return this.readySignal.get() ?? false;
196
+ }
197
+ profile(fn, measureFunction) {
198
+ if (!this.debugMode)
199
+ return fn();
200
+ const startTime = performance.now();
201
+ const handleProfileEnd = (result) => {
202
+ const endTime = performance.now();
203
+ measureFunction(endTime - startTime);
204
+ return result;
205
+ };
206
+ const maybePromise = fn();
207
+ return maybePromise instanceof Promise
208
+ ? maybePromise.then(handleProfileEnd)
209
+ : handleProfileEnd(maybePromise);
210
+ }
211
+ executeInDebugMode(fn) {
212
+ if (!this.debugMode)
213
+ return;
214
+ // eslint-disable-next-line unicorn/error-message
215
+ const callstack = new Error().stack || '';
216
+ fn(callstack);
217
+ }
218
+ transform(item) {
219
+ if (!this.options.transform)
220
+ return item;
221
+ return this.options.transform(item);
222
+ }
223
+ transformAll(items, fields) {
224
+ if (!this.options.transformAll)
225
+ return items;
226
+ return this.options.transformAll((0, deepClone_1.default)(items), fields);
227
+ }
228
+ getItem(selector, options) {
229
+ const itemsOrPromise = this.getItems(selector, { ...options, limit: 1 });
230
+ if (itemsOrPromise instanceof Promise) {
231
+ return itemsOrPromise.then((items) => {
232
+ return items[0] || undefined;
233
+ });
234
+ }
235
+ return itemsOrPromise[0];
236
+ }
237
+ getItems(selector, options) {
238
+ this.emit('getItems', selector);
239
+ return this.profile(() => {
240
+ if (!options?.async)
241
+ return this.backend.getQueryResult(selector, options);
242
+ this.isPullingSignal.set(true);
243
+ return this.backend.executeQuery(selector, options)
244
+ .finally(() => {
245
+ this.isPullingSignal.set(false);
246
+ });
247
+ }, measuredTime => this.executeInDebugMode(callstack => this.emit('_debug.getItems', callstack, selector, measuredTime)));
248
+ }
249
+ async withPushState(asyncFunction) {
250
+ this.isPushingSignal.set(true);
251
+ try {
252
+ return await asyncFunction();
253
+ }
254
+ finally {
255
+ this.isPushingSignal.set(false);
256
+ }
257
+ }
258
+ queryListeners(query, listeners) {
259
+ const id = (0, queryId_1.default)(query.selector, query.options);
260
+ if (listeners != null) {
261
+ return this.queryListenersMap.set(id, listeners);
262
+ }
263
+ return this.queryListenersMap.get(id) ?? 0;
264
+ }
265
+ /**
266
+ * Disposes the collection, unregisters persistence adapters, clears memory, and
267
+ * cleans up all resources used by the collection.
268
+ * @returns A promise that resolves when the collection is disposed.
269
+ */
270
+ async dispose() {
271
+ await this.backend.dispose();
272
+ this.isDisposed = true;
273
+ this.removeAllListeners();
274
+ Collection.collections = Collection.collections.filter(collection => collection !== this);
275
+ Collection.onDisposeCallbacks.forEach(callback => callback(this));
276
+ }
277
+ find(selector = {}, options) {
278
+ if (this.isDisposed)
279
+ throw new Error('Collection is disposed');
280
+ if (selector !== undefined && (!selector || typeof selector !== 'object'))
281
+ throw new Error('Invalid selector');
282
+ const getTransformedItems = () => {
283
+ const itemsOrPromise = this.getItems(selector, options || {});
284
+ if (itemsOrPromise instanceof Promise) {
285
+ return itemsOrPromise.then((items) => {
286
+ return this.transformAll(items, options?.fields);
287
+ });
288
+ }
289
+ const items = itemsOrPromise;
290
+ return this.transformAll(items, options?.fields);
291
+ };
292
+ const cursor = new Cursor_1.default(getTransformedItems, {
293
+ reactive: this.options.reactivity,
294
+ fieldTracking: this.fieldTracking,
295
+ ...options,
296
+ transform: this.transform.bind(this),
297
+ bindEvents: (requery) => {
298
+ const handleRequery = () => {
299
+ if (this.batchOperationInProgress) {
300
+ this.postBatchCallbacks.add(requery);
301
+ return;
302
+ }
303
+ requery();
304
+ };
305
+ // register query if not yet registered
306
+ const listeners = this.queryListeners({ selector, options });
307
+ const didRegister = listeners === 0;
308
+ if (didRegister)
309
+ this.backend.registerQuery(selector, options || {});
310
+ this.queryListeners({ selector, options }, listeners + 1);
311
+ const queryStateChangeCleanup = this.backend.onQueryStateChange(selector, options || {}, (state) => {
312
+ if (state !== 'complete')
313
+ return;
314
+ handleRequery();
315
+ });
316
+ this.emit('observer.created', selector, options);
317
+ return () => {
318
+ // Use queueMicrotask instead of setTimeout to avoid race conditions
319
+ // while still allowing batching of quick register/unregister calls
320
+ queueMicrotask(() => {
321
+ // unregister query if no more listeners
322
+ const newListeners = Math.max(0, this.queryListeners({ selector, options }) - 1);
323
+ // Only unregister if this observer was the one that registered
324
+ // This prevents race conditions where a new observer registers
325
+ // before the old one's cleanup runs
326
+ if (newListeners === 0 && didRegister)
327
+ this.backend.unregisterQuery(selector, options || {});
328
+ this.queryListeners({ selector, options }, newListeners);
329
+ queryStateChangeCleanup();
330
+ this.emit('observer.disposed', selector, options);
331
+ });
332
+ };
333
+ },
334
+ });
335
+ this.emit('find', selector, options, cursor);
336
+ this.executeInDebugMode(callstack => this.emit('_debug.find', callstack, selector, options, cursor));
337
+ return cursor;
338
+ }
339
+ findOne(selector, options) {
340
+ if (this.isDisposed)
341
+ throw new Error('Collection is disposed');
342
+ const cursor = this.find(selector, {
343
+ limit: 1,
344
+ ...options,
345
+ });
346
+ const handleItems = (items) => {
347
+ const returnValue = items[0] || undefined;
348
+ this.emit('findOne', selector, options, returnValue);
349
+ this.executeInDebugMode(callstack => this.emit('_debug.findOne', callstack, selector, options, returnValue));
350
+ return returnValue;
351
+ };
352
+ const maybePromise = cursor.fetch();
353
+ return (maybePromise instanceof Promise
354
+ ? maybePromise.then(handleItems)
355
+ : handleItems(maybePromise));
356
+ }
357
+ batch(callback) {
358
+ if (this.batchOperationInProgress)
359
+ return callback();
360
+ this.batchOperationInProgress = true;
361
+ const maybePromise = callback();
362
+ const afterBatch = () => {
363
+ this.batchOperationInProgress = false;
364
+ this.postBatchCallbacks.forEach(callback_ => callback_());
365
+ this.postBatchCallbacks.clear();
366
+ };
367
+ if (maybePromise && typeof maybePromise.then === 'function') {
368
+ return maybePromise.then(() => afterBatch());
369
+ }
370
+ else {
371
+ afterBatch();
372
+ }
373
+ }
374
+ onPostBatch(callback) {
375
+ if (this.isDisposed)
376
+ throw new Error('Collection is disposed');
377
+ if (this.batchOperationInProgress) {
378
+ this.postBatchCallbacks.add(callback);
379
+ return;
380
+ }
381
+ return callback();
382
+ }
383
+ /**
384
+ * Inserts a single item into the collection. Generates a unique ID if not provided.
385
+ * @param item - The item to insert.
386
+ * @returns The ID of the inserted item.
387
+ * @throws {Error} If the collection is disposed or the item has an invalid ID.
388
+ */
389
+ async insert(item) {
390
+ if (this.isDisposed)
391
+ throw new Error('Collection is disposed');
392
+ if (!item)
393
+ throw new Error('Invalid item');
394
+ const primaryKeyGenerator = this.options.primaryKeyGenerator ?? randomId_1.default;
395
+ const itemWithId = {
396
+ id: primaryKeyGenerator(item),
397
+ ...item,
398
+ };
399
+ this.emit('validate', itemWithId);
400
+ const newItem = await this.withPushState(() => this.backend.insert(itemWithId));
401
+ this.emit('added', newItem);
402
+ this.emit('insert', newItem);
403
+ this.executeInDebugMode(callstack => this.emit('_debug.insert', callstack, newItem));
404
+ return newItem.id;
405
+ }
406
+ /**
407
+ * Inserts multiple items into the collection. Generates unique IDs for items if not provided.
408
+ * @param items - The items to insert.
409
+ * @returns An array of IDs of the inserted items.
410
+ * @throws {Error} If the collection is disposed or the items are invalid.
411
+ */
412
+ async insertMany(items) {
413
+ if (this.isDisposed)
414
+ throw new Error('Collection is disposed');
415
+ if (!items)
416
+ throw new Error('Invalid items');
417
+ if (items.length === 0)
418
+ return [];
419
+ const ids = [];
420
+ await this.batch(async () => {
421
+ await Promise.all(items.map(async (item) => {
422
+ ids.push(await this.insert(item));
423
+ }));
424
+ });
425
+ return ids;
426
+ }
427
+ /**
428
+ * Updates a single item in the collection that matches the given selector.
429
+ * @param selector - The criteria to select the item to update.
430
+ * @param modifier - The modifications to apply to the item.
431
+ * @param [options] - Optional settings for the update operation.
432
+ * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
433
+ * @returns The number of items updated (0 or 1).
434
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
435
+ */
436
+ async updateOne(selector, modifier, options) {
437
+ if (this.isDisposed)
438
+ throw new Error('Collection is disposed');
439
+ if (!selector)
440
+ throw new Error('Invalid selector');
441
+ if (!modifier)
442
+ throw new Error('Invalid modifier');
443
+ const { $setOnInsert, ...restModifier } = modifier;
444
+ const item = await this.getItem(selector, { async: true });
445
+ if (item == null) {
446
+ if (options?.upsert) {
447
+ // if upsert is enabled, insert a new item
448
+ const newItem = (0, modify_1.default)({}, {
449
+ ...restModifier,
450
+ $set: {
451
+ ...$setOnInsert,
452
+ ...restModifier.$set,
453
+ },
454
+ });
455
+ await this.insert(newItem);
456
+ return 1;
457
+ }
458
+ return 0; // no item found, and upsert is not enabled
459
+ }
460
+ const modifiedItem = (0, modify_1.default)((0, deepClone_1.default)(item), restModifier);
461
+ this.emit('validate', modifiedItem);
462
+ const changes = await this.withPushState(() => this.backend.updateOne(selector, modifier));
463
+ this.emit('changed', modifiedItem, restModifier);
464
+ this.emit('updateOne', selector, modifier);
465
+ this.executeInDebugMode(callstack => this.emit('_debug.updateOne', callstack, selector, modifier));
466
+ return changes.length;
467
+ }
468
+ /**
469
+ * Updates multiple items in the collection that match the given selector.
470
+ * @param selector - The criteria to select the items to update.
471
+ * @param modifier - The modifications to apply to the items.
472
+ * @param [options] - Optional settings for the update operation.
473
+ * @param [options.upsert] - If `true`, creates new items if no items match the selector.
474
+ * @returns The number of items updated.
475
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
476
+ */
477
+ async updateMany(selector, modifier, options) {
478
+ if (this.isDisposed)
479
+ throw new Error('Collection is disposed');
480
+ if (!selector)
481
+ throw new Error('Invalid selector');
482
+ if (!modifier)
483
+ throw new Error('Invalid modifier');
484
+ const { $setOnInsert, ...restModifier } = modifier;
485
+ const items = await this.getItems(selector, { async: true });
486
+ if (items.length === 0) {
487
+ if (options?.upsert) {
488
+ // if upsert is enabled, insert a new item
489
+ const newItem = (0, modify_1.default)({}, {
490
+ ...restModifier,
491
+ $set: {
492
+ ...$setOnInsert,
493
+ ...restModifier.$set,
494
+ },
495
+ });
496
+ await this.insert(newItem);
497
+ return 1;
498
+ }
499
+ return 0; // no items found, and upsert is not enabled
500
+ }
501
+ items.forEach((item) => {
502
+ const modifiedItem = (0, modify_1.default)((0, deepClone_1.default)(item), restModifier);
503
+ this.emit('validate', modifiedItem);
504
+ });
505
+ const changes = await this.withPushState(() => this.backend.updateMany(selector, modifier));
506
+ changes.forEach((item) => {
507
+ this.emit('changed', item, restModifier);
508
+ });
509
+ this.emit('updateMany', selector, modifier);
510
+ this.executeInDebugMode(callstack => this.emit('_debug.updateMany', callstack, selector, modifier));
511
+ return changes.length;
512
+ }
513
+ /**
514
+ * Replaces a single item in the collection that matches the given selector.
515
+ * @param selector - The criteria to select the item to replace.
516
+ * @param replacement - The item to replace the selected item with.
517
+ * @param [options] - Optional settings for the replace operation.
518
+ * @param [options.upsert] - If `true`, creates a new item if no item matches the selector.
519
+ * @returns The number of items replaced (0 or 1).
520
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
521
+ */
522
+ async replaceOne(selector, replacement, options) {
523
+ if (this.isDisposed)
524
+ throw new Error('Collection is disposed');
525
+ if (!selector)
526
+ throw new Error('Invalid selector');
527
+ const item = await this.getItem(selector, { async: true });
528
+ if (item == null) {
529
+ if (options?.upsert) {
530
+ await this.insert(replacement);
531
+ return 1;
532
+ }
533
+ return 0; // no item found, and upsert is not enabled
534
+ }
535
+ const modifiedItem = { id: item.id, ...replacement };
536
+ this.emit('validate', modifiedItem);
537
+ const changes = await this.withPushState(() => this.backend.replaceOne(selector, replacement));
538
+ this.emit('changed', modifiedItem, replacement);
539
+ this.emit('replaceOne', selector, replacement);
540
+ this.executeInDebugMode(callstack => this.emit('_debug.replaceOne', callstack, selector, replacement));
541
+ return changes.length;
542
+ }
543
+ /**
544
+ * Removes a single item from the collection that matches the given selector.
545
+ * @param selector - The criteria to select the item to remove.
546
+ * @returns The number of items removed (0 or 1).
547
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
548
+ */
549
+ async removeOne(selector) {
550
+ if (this.isDisposed)
551
+ throw new Error('Collection is disposed');
552
+ if (!selector)
553
+ throw new Error('Invalid selector');
554
+ const removedItems = await this.withPushState(() => this.backend.removeOne(selector));
555
+ this.emit('removed', removedItems[0]);
556
+ this.emit('removeOne', selector);
557
+ this.executeInDebugMode(callstack => this.emit('_debug.removeOne', callstack, selector));
558
+ return removedItems.length;
559
+ }
560
+ /**
561
+ * Removes multiple items from the collection that match the given selector.
562
+ * @param selector - The criteria to select the items to remove.
563
+ * @returns The number of items removed.
564
+ * @throws {Error} If the collection is disposed or invalid arguments are provided.
565
+ */
566
+ async removeMany(selector) {
567
+ if (this.isDisposed)
568
+ throw new Error('Collection is disposed');
569
+ if (!selector)
570
+ throw new Error('Invalid selector');
571
+ const removedItems = await this.withPushState(() => this.backend.removeMany(selector));
572
+ removedItems.forEach((item) => {
573
+ this.emit('removed', item);
574
+ });
575
+ this.emit('removeMany', selector);
576
+ this.executeInDebugMode(callstack => this.emit('_debug.removeMany', callstack, selector));
577
+ return removedItems.length;
578
+ }
579
+ }
580
+ exports.default = Collection;
@@ -0,0 +1,28 @@
1
+ import type { QueryOptions } from '../DataAdapter';
2
+ import type ReactivityAdapter from '../types/ReactivityAdapter';
3
+ export type BaseItem<I = any> = {
4
+ id: I;
5
+ } & Record<string, any>;
6
+ export type Transform<T, U = T> = ((document: T) => U) | null | undefined;
7
+ export type TransformAll<T extends BaseItem, O extends BaseItem = T> = ((items: T[], fields: FieldSpecifier<O> | undefined) => O[]) | null | undefined;
8
+ export type SortSpecifier<T> = {
9
+ [P in keyof T]?: -1 | 1;
10
+ } & Record<string, -1 | 1>;
11
+ export type FieldSpecifier<T> = {
12
+ [P in keyof T]?: 0 | 1;
13
+ } & Record<string, 0 | 1>;
14
+ export interface FindOptions<T extends BaseItem, Async extends boolean> extends QueryOptions<T> {
15
+ /** pass `false` to disable reactivity */
16
+ reactive?: ReactivityAdapter | false;
17
+ /** pass `true` to enable automatic field-level reactitivy */
18
+ fieldTracking?: boolean;
19
+ /** pass `true` to execute the query asynchronously */
20
+ async?: Async;
21
+ }
22
+ export type AsyncFindOptions<T extends BaseItem> = Omit<FindOptions<T, true>, 'async'> & {
23
+ async: true;
24
+ };
25
+ export type SyncFindOptions<T extends BaseItem> = Omit<FindOptions<T, false>, 'async'> & {
26
+ async?: false;
27
+ };
28
+ export type AnyFindOptions<T extends BaseItem> = AsyncFindOptions<T> | SyncFindOptions<T>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,35 @@
1
+ import type { BaseItem, FieldSpecifier, SortSpecifier } from './Collection';
2
+ import type Collection from './Collection';
3
+ import type Modifier from './types/Modifier';
4
+ import type Selector from './types/Selector';
5
+ export interface QueryOptions<T extends BaseItem> {
6
+ /** Sort order (default: natural order) */
7
+ sort?: SortSpecifier<T> | undefined;
8
+ /** Number of results to skip at the beginning */
9
+ skip?: number | undefined;
10
+ /** Maximum number of results to return */
11
+ limit?: number | undefined;
12
+ /** Dictionary of fields to return or exclude. */
13
+ fields?: FieldSpecifier<T> | undefined;
14
+ }
15
+ export type StateChangeCallback = (state: 'active' | 'complete' | 'error') => void;
16
+ export interface CollectionBackend<T extends BaseItem<I>, I> {
17
+ insert(item: T): Promise<T>;
18
+ updateOne(selector: Selector<T>, modifier: Modifier<T>): Promise<T[]>;
19
+ updateMany(selector: Selector<T>, modifier: Modifier<T>): Promise<T[]>;
20
+ replaceOne(selector: Selector<T>, replacement: Omit<T, 'id'> & Partial<Pick<T, 'id'>>): Promise<T[]>;
21
+ removeOne(selector: Selector<T>): Promise<T[]>;
22
+ removeMany(selector: Selector<T>): Promise<T[]>;
23
+ registerQuery<O extends QueryOptions<T>>(selector: Selector<T>, options: O): void;
24
+ unregisterQuery<O extends QueryOptions<T>>(selector: Selector<T>, options: O): void;
25
+ getQueryState<O extends QueryOptions<T>>(selector: Selector<T>, options: O): 'active' | 'complete' | 'error';
26
+ getQueryError<O extends QueryOptions<T>>(selector: Selector<T>, options: O): Error | null;
27
+ getQueryResult<O extends QueryOptions<T>>(selector: Selector<T>, options: O): T[];
28
+ executeQuery<O extends QueryOptions<T>>(selector: Selector<T>, options: O): Promise<T[]>;
29
+ onQueryStateChange<O extends QueryOptions<T>>(selector: Selector<T>, options: O, callback: StateChangeCallback): () => void;
30
+ dispose(): Promise<void>;
31
+ isReady(): Promise<void>;
32
+ }
33
+ export default interface DataAdapter {
34
+ createCollectionBackend<T extends BaseItem<I>, I = any, E extends BaseItem = T, U = E>(collection: Collection<T, I, E, U>, indices: string[]): CollectionBackend<T, I>;
35
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,35 @@
1
+ import type { BaseItem } from './Collection';
2
+ import type Collection from './Collection';
3
+ import type DataAdapter from './DataAdapter';
4
+ import type { CollectionBackend } from './DataAdapter';
5
+ import type StorageAdapter from './types/StorageAdapter';
6
+ interface DefaultDataAdapterOptions {
7
+ storage?: (name: string) => StorageAdapter<any, any> | undefined;
8
+ onError?: (name: string, error: Error) => void;
9
+ }
10
+ export default class DefaultDataAdapter implements DataAdapter {
11
+ private items;
12
+ private options;
13
+ private storageAdapters;
14
+ private collections;
15
+ private indices;
16
+ private activeQueries;
17
+ private queryEmitters;
18
+ private queuedQueryUpdates;
19
+ private cachedQueryResults;
20
+ constructor(options?: DefaultDataAdapterOptions);
21
+ private ensureStorageAdapter;
22
+ private rebuildIndices;
23
+ private setupStorageAdapter;
24
+ private getIndexInfo;
25
+ private applyIndexDeltas;
26
+ private getItem;
27
+ private queryItems;
28
+ private executeQuery;
29
+ private flushQueuedQueryUpdates;
30
+ private executeAndCacheQuery;
31
+ private updateQueries;
32
+ createCollectionBackend<T extends BaseItem<I>, I = any, E extends BaseItem = T, U = E>(collection: Collection<T, I, E, U>, indices: string[]): CollectionBackend<T, I>;
33
+ fetchItemsFromStorage<T extends BaseItem<I>, I = any, E extends BaseItem = T, U = E>(collection?: Collection<T, I, E, U>): Promise<void>;
34
+ }
35
+ export {};