@tanstack/db 0.0.26 → 0.0.29
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.
- package/dist/cjs/change-events.cjs +141 -0
- package/dist/cjs/change-events.cjs.map +1 -0
- package/dist/cjs/change-events.d.cts +49 -0
- package/dist/cjs/collection.cjs +236 -90
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +95 -20
- package/dist/cjs/errors.cjs +509 -1
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +225 -1
- package/dist/cjs/index.cjs +82 -3
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +5 -1
- package/dist/cjs/indexes/auto-index.cjs +64 -0
- package/dist/cjs/indexes/auto-index.cjs.map +1 -0
- package/dist/cjs/indexes/auto-index.d.cts +9 -0
- package/dist/cjs/indexes/base-index.cjs +46 -0
- package/dist/cjs/indexes/base-index.cjs.map +1 -0
- package/dist/cjs/indexes/base-index.d.cts +54 -0
- package/dist/cjs/indexes/index-options.d.cts +13 -0
- package/dist/cjs/indexes/lazy-index.cjs +193 -0
- package/dist/cjs/indexes/lazy-index.cjs.map +1 -0
- package/dist/cjs/indexes/lazy-index.d.cts +96 -0
- package/dist/cjs/indexes/ordered-index.cjs +227 -0
- package/dist/cjs/indexes/ordered-index.cjs.map +1 -0
- package/dist/cjs/indexes/ordered-index.d.cts +72 -0
- package/dist/cjs/local-storage.cjs +9 -15
- package/dist/cjs/local-storage.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs +11 -0
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.d.cts +4 -0
- package/dist/cjs/query/builder/index.cjs +6 -7
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.cjs +37 -0
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.d.cts +12 -0
- package/dist/cjs/query/compiler/evaluators.cjs +83 -58
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.d.cts +8 -0
- package/dist/cjs/query/compiler/expressions.cjs +61 -0
- package/dist/cjs/query/compiler/expressions.cjs.map +1 -0
- package/dist/cjs/query/compiler/expressions.d.cts +25 -0
- package/dist/cjs/query/compiler/group-by.cjs +5 -10
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs +23 -17
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +12 -3
- package/dist/cjs/query/compiler/joins.cjs +61 -12
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.cjs +4 -34
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/types.d.cts +2 -2
- package/dist/cjs/query/live-query-collection.cjs +54 -12
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/optimizer.cjs +45 -7
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/query/optimizer.d.cts +13 -3
- package/dist/cjs/transactions.cjs +5 -4
- package/dist/cjs/transactions.cjs.map +1 -1
- package/dist/cjs/types.d.cts +31 -0
- package/dist/cjs/utils/array-utils.cjs +18 -0
- package/dist/cjs/utils/array-utils.cjs.map +1 -0
- package/dist/cjs/utils/array-utils.d.cts +8 -0
- package/dist/cjs/utils/comparison.cjs +52 -0
- package/dist/cjs/utils/comparison.cjs.map +1 -0
- package/dist/cjs/utils/comparison.d.cts +11 -0
- package/dist/cjs/utils/index-optimization.cjs +270 -0
- package/dist/cjs/utils/index-optimization.cjs.map +1 -0
- package/dist/cjs/utils/index-optimization.d.cts +29 -0
- package/dist/esm/change-events.d.ts +49 -0
- package/dist/esm/change-events.js +141 -0
- package/dist/esm/change-events.js.map +1 -0
- package/dist/esm/collection.d.ts +95 -20
- package/dist/esm/collection.js +234 -88
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/errors.d.ts +225 -1
- package/dist/esm/errors.js +510 -2
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +5 -1
- package/dist/esm/index.js +81 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes/auto-index.d.ts +9 -0
- package/dist/esm/indexes/auto-index.js +64 -0
- package/dist/esm/indexes/auto-index.js.map +1 -0
- package/dist/esm/indexes/base-index.d.ts +54 -0
- package/dist/esm/indexes/base-index.js +46 -0
- package/dist/esm/indexes/base-index.js.map +1 -0
- package/dist/esm/indexes/index-options.d.ts +13 -0
- package/dist/esm/indexes/lazy-index.d.ts +96 -0
- package/dist/esm/indexes/lazy-index.js +193 -0
- package/dist/esm/indexes/lazy-index.js.map +1 -0
- package/dist/esm/indexes/ordered-index.d.ts +72 -0
- package/dist/esm/indexes/ordered-index.js +227 -0
- package/dist/esm/indexes/ordered-index.js.map +1 -0
- package/dist/esm/local-storage.js +9 -15
- package/dist/esm/local-storage.js.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +4 -0
- package/dist/esm/query/builder/functions.js +11 -0
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.js +6 -7
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.d.ts +12 -0
- package/dist/esm/query/builder/ref-proxy.js +37 -0
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/compiler/evaluators.d.ts +8 -0
- package/dist/esm/query/compiler/evaluators.js +84 -59
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/expressions.d.ts +25 -0
- package/dist/esm/query/compiler/expressions.js +61 -0
- package/dist/esm/query/compiler/expressions.js.map +1 -0
- package/dist/esm/query/compiler/group-by.js +5 -10
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +12 -3
- package/dist/esm/query/compiler/index.js +23 -17
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.js +61 -12
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.js +1 -31
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/types.d.ts +2 -2
- package/dist/esm/query/live-query-collection.js +54 -12
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.d.ts +13 -3
- package/dist/esm/query/optimizer.js +40 -2
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/transactions.js +5 -4
- package/dist/esm/transactions.js.map +1 -1
- package/dist/esm/types.d.ts +31 -0
- package/dist/esm/utils/array-utils.d.ts +8 -0
- package/dist/esm/utils/array-utils.js +18 -0
- package/dist/esm/utils/array-utils.js.map +1 -0
- package/dist/esm/utils/comparison.d.ts +11 -0
- package/dist/esm/utils/comparison.js +52 -0
- package/dist/esm/utils/comparison.js.map +1 -0
- package/dist/esm/utils/index-optimization.d.ts +29 -0
- package/dist/esm/utils/index-optimization.js +270 -0
- package/dist/esm/utils/index-optimization.js.map +1 -0
- package/package.json +3 -2
- package/src/change-events.ts +257 -0
- package/src/collection.ts +321 -110
- package/src/errors.ts +545 -1
- package/src/index.ts +7 -1
- package/src/indexes/auto-index.ts +108 -0
- package/src/indexes/base-index.ts +119 -0
- package/src/indexes/index-options.ts +42 -0
- package/src/indexes/lazy-index.ts +251 -0
- package/src/indexes/ordered-index.ts +305 -0
- package/src/local-storage.ts +16 -17
- package/src/query/builder/functions.ts +14 -0
- package/src/query/builder/index.ts +12 -7
- package/src/query/builder/ref-proxy.ts +65 -0
- package/src/query/compiler/evaluators.ts +114 -62
- package/src/query/compiler/expressions.ts +92 -0
- package/src/query/compiler/group-by.ts +10 -10
- package/src/query/compiler/index.ts +52 -22
- package/src/query/compiler/joins.ts +114 -15
- package/src/query/compiler/order-by.ts +1 -45
- package/src/query/compiler/types.ts +2 -2
- package/src/query/live-query-collection.ts +95 -15
- package/src/query/optimizer.ts +94 -5
- package/src/transactions.ts +10 -4
- package/src/types.ts +38 -0
- package/src/utils/array-utils.ts +28 -0
- package/src/utils/comparison.ts +79 -0
- package/src/utils/index-optimization.ts +546 -0
package/dist/cjs/collection.cjs
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const proxy = require("./proxy.cjs");
|
|
4
|
-
const transactions = require("./transactions.cjs");
|
|
5
4
|
const SortedMap = require("./SortedMap.cjs");
|
|
5
|
+
const refProxy = require("./query/builder/ref-proxy.cjs");
|
|
6
|
+
const orderedIndex = require("./indexes/ordered-index.cjs");
|
|
7
|
+
const lazyIndex = require("./indexes/lazy-index.cjs");
|
|
8
|
+
const autoIndex = require("./indexes/auto-index.cjs");
|
|
9
|
+
const transactions = require("./transactions.cjs");
|
|
10
|
+
const errors = require("./errors.cjs");
|
|
11
|
+
const changeEvents = require("./change-events.cjs");
|
|
6
12
|
const collectionsStore = /* @__PURE__ */ new Map();
|
|
7
13
|
function createCollection(options) {
|
|
8
14
|
const collection = new CollectionImpl(options);
|
|
@@ -13,16 +19,6 @@ function createCollection(options) {
|
|
|
13
19
|
}
|
|
14
20
|
return collection;
|
|
15
21
|
}
|
|
16
|
-
class SchemaValidationError extends Error {
|
|
17
|
-
constructor(type, issues, message) {
|
|
18
|
-
const defaultMessage = `${type === `insert` ? `Insert` : `Update`} validation failed: ${issues.map((issue) => `
|
|
19
|
-
- ${issue.message} - path: ${issue.path}`).join(``)}`;
|
|
20
|
-
super(message || defaultMessage);
|
|
21
|
-
this.name = `SchemaValidationError`;
|
|
22
|
-
this.type = type;
|
|
23
|
-
this.issues = issues;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
22
|
class CollectionImpl {
|
|
27
23
|
/**
|
|
28
24
|
* Creates a new Collection instance
|
|
@@ -36,6 +32,10 @@ class CollectionImpl {
|
|
|
36
32
|
this.optimisticUpserts = /* @__PURE__ */ new Map();
|
|
37
33
|
this.optimisticDeletes = /* @__PURE__ */ new Set();
|
|
38
34
|
this._size = 0;
|
|
35
|
+
this.lazyIndexes = /* @__PURE__ */ new Map();
|
|
36
|
+
this.resolvedIndexes = /* @__PURE__ */ new Map();
|
|
37
|
+
this.isIndexesResolved = false;
|
|
38
|
+
this.indexCounter = 0;
|
|
39
39
|
this.changeListeners = /* @__PURE__ */ new Set();
|
|
40
40
|
this.changeKeyListeners = /* @__PURE__ */ new Map();
|
|
41
41
|
this.utils = {};
|
|
@@ -194,6 +194,9 @@ class CollectionImpl {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
this._size = this.calculateSize();
|
|
197
|
+
if (events.length > 0) {
|
|
198
|
+
this.updateIndexes(events);
|
|
199
|
+
}
|
|
197
200
|
this.emitEvents(events, true);
|
|
198
201
|
this.pendingSyncedTransactions = [];
|
|
199
202
|
this.preSyncVisibleState.clear();
|
|
@@ -212,9 +215,7 @@ class CollectionImpl {
|
|
|
212
215
|
this.validateCollectionUsable(`insert`);
|
|
213
216
|
const ambientTransaction = transactions.getActiveTransaction();
|
|
214
217
|
if (!ambientTransaction && !this.config.onInsert) {
|
|
215
|
-
throw new
|
|
216
|
-
`Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured.`
|
|
217
|
-
);
|
|
218
|
+
throw new errors.MissingInsertHandlerError();
|
|
218
219
|
}
|
|
219
220
|
const items = Array.isArray(data) ? data : [data];
|
|
220
221
|
const mutations = [];
|
|
@@ -223,7 +224,7 @@ class CollectionImpl {
|
|
|
223
224
|
const validatedData = this.validateData(item, `insert`);
|
|
224
225
|
const key = this.getKeyFromItem(validatedData);
|
|
225
226
|
if (this.has(key)) {
|
|
226
|
-
throw
|
|
227
|
+
throw new errors.DuplicateKeyError(key);
|
|
227
228
|
}
|
|
228
229
|
const globalKey = this.generateGlobalKey(key, item);
|
|
229
230
|
const mutation = {
|
|
@@ -276,20 +277,16 @@ class CollectionImpl {
|
|
|
276
277
|
this.validateCollectionUsable(`delete`);
|
|
277
278
|
const ambientTransaction = transactions.getActiveTransaction();
|
|
278
279
|
if (!ambientTransaction && !this.config.onDelete) {
|
|
279
|
-
throw new
|
|
280
|
-
`Collection.delete called directly (not within an explicit transaction) but no 'onDelete' handler is configured.`
|
|
281
|
-
);
|
|
280
|
+
throw new errors.MissingDeleteHandlerError();
|
|
282
281
|
}
|
|
283
282
|
if (Array.isArray(keys) && keys.length === 0) {
|
|
284
|
-
throw new
|
|
283
|
+
throw new errors.NoKeysPassedToDeleteError();
|
|
285
284
|
}
|
|
286
285
|
const keysArray = Array.isArray(keys) ? keys : [keys];
|
|
287
286
|
const mutations = [];
|
|
288
287
|
for (const key of keysArray) {
|
|
289
288
|
if (!this.has(key)) {
|
|
290
|
-
throw new
|
|
291
|
-
`Collection.delete was called with key '${key}' but there is no item in the collection with this key`
|
|
292
|
-
);
|
|
289
|
+
throw new errors.DeleteKeyNotFoundError(key);
|
|
293
290
|
}
|
|
294
291
|
const globalKey = this.generateGlobalKey(key, this.get(key));
|
|
295
292
|
const mutation = {
|
|
@@ -331,7 +328,7 @@ class CollectionImpl {
|
|
|
331
328
|
return directOpTransaction;
|
|
332
329
|
};
|
|
333
330
|
if (!config) {
|
|
334
|
-
throw new
|
|
331
|
+
throw new errors.CollectionRequiresConfigError();
|
|
335
332
|
}
|
|
336
333
|
if (config.id) {
|
|
337
334
|
this.id = config.id;
|
|
@@ -339,12 +336,15 @@ class CollectionImpl {
|
|
|
339
336
|
this.id = crypto.randomUUID();
|
|
340
337
|
}
|
|
341
338
|
if (!config.sync) {
|
|
342
|
-
throw new
|
|
339
|
+
throw new errors.CollectionRequiresSyncConfigError();
|
|
343
340
|
}
|
|
344
341
|
this.transactions = new SortedMap.SortedMap(
|
|
345
342
|
(a, b) => a.compareCreatedAt(b)
|
|
346
343
|
);
|
|
347
|
-
this.config =
|
|
344
|
+
this.config = {
|
|
345
|
+
...config,
|
|
346
|
+
autoIndex: config.autoIndex ?? `eager`
|
|
347
|
+
};
|
|
348
348
|
collectionsStore.set(this.id, this);
|
|
349
349
|
if (this.config.compare) {
|
|
350
350
|
this.syncedData = new SortedMap.SortedMap(this.config.compare);
|
|
@@ -420,13 +420,10 @@ class CollectionImpl {
|
|
|
420
420
|
validateCollectionUsable(operation) {
|
|
421
421
|
switch (this._status) {
|
|
422
422
|
case `error`:
|
|
423
|
-
throw new
|
|
424
|
-
`Cannot perform ${operation} on collection "${this.id}" - collection is in error state. Try calling cleanup() and restarting the collection.`
|
|
425
|
-
);
|
|
423
|
+
throw new errors.CollectionInErrorStateError(operation, this.id);
|
|
426
424
|
case `cleaned-up`:
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
);
|
|
425
|
+
this.startSync();
|
|
426
|
+
break;
|
|
430
427
|
}
|
|
431
428
|
}
|
|
432
429
|
/**
|
|
@@ -446,9 +443,7 @@ class CollectionImpl {
|
|
|
446
443
|
"cleaned-up": [`loading`, `error`]
|
|
447
444
|
};
|
|
448
445
|
if (!validTransitions[from].includes(to)) {
|
|
449
|
-
throw new
|
|
450
|
-
`Invalid collection status transition from "${from}" to "${to}" for collection "${this.id}"`
|
|
451
|
-
);
|
|
446
|
+
throw new errors.InvalidCollectionStatusTransitionError(from, to, this.id);
|
|
452
447
|
}
|
|
453
448
|
}
|
|
454
449
|
/**
|
|
@@ -458,6 +453,11 @@ class CollectionImpl {
|
|
|
458
453
|
setStatus(newStatus) {
|
|
459
454
|
this.validateStatusTransition(this._status, newStatus);
|
|
460
455
|
this._status = newStatus;
|
|
456
|
+
if (newStatus === `ready` && !this.isIndexesResolved) {
|
|
457
|
+
this.resolveAllIndexes().catch((error) => {
|
|
458
|
+
console.warn(`Failed to resolve indexes:`, error);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
461
|
}
|
|
462
462
|
/**
|
|
463
463
|
* Start sync immediately - internal method for compiled queries
|
|
@@ -487,21 +487,17 @@ class CollectionImpl {
|
|
|
487
487
|
write: (messageWithoutKey) => {
|
|
488
488
|
const pendingTransaction = this.pendingSyncedTransactions[this.pendingSyncedTransactions.length - 1];
|
|
489
489
|
if (!pendingTransaction) {
|
|
490
|
-
throw new
|
|
490
|
+
throw new errors.NoPendingSyncTransactionWriteError();
|
|
491
491
|
}
|
|
492
492
|
if (pendingTransaction.committed) {
|
|
493
|
-
throw new
|
|
494
|
-
`The pending sync transaction is already committed, you can't still write to it.`
|
|
495
|
-
);
|
|
493
|
+
throw new errors.SyncTransactionAlreadyCommittedWriteError();
|
|
496
494
|
}
|
|
497
495
|
const key = this.getKeyFromItem(messageWithoutKey.value);
|
|
498
496
|
if (messageWithoutKey.type === `insert`) {
|
|
499
497
|
if (this.syncedData.has(key) && !pendingTransaction.operations.some(
|
|
500
498
|
(op) => op.key === key && op.type === `delete`
|
|
501
499
|
)) {
|
|
502
|
-
throw new
|
|
503
|
-
`Cannot insert document with key "${key}" from sync because it already exists in the collection "${this.id}"`
|
|
504
|
-
);
|
|
500
|
+
throw new errors.DuplicateKeySyncError(key, this.id);
|
|
505
501
|
}
|
|
506
502
|
}
|
|
507
503
|
const message = {
|
|
@@ -513,12 +509,10 @@ class CollectionImpl {
|
|
|
513
509
|
commit: () => {
|
|
514
510
|
const pendingTransaction = this.pendingSyncedTransactions[this.pendingSyncedTransactions.length - 1];
|
|
515
511
|
if (!pendingTransaction) {
|
|
516
|
-
throw new
|
|
512
|
+
throw new errors.NoPendingSyncTransactionCommitError();
|
|
517
513
|
}
|
|
518
514
|
if (pendingTransaction.committed) {
|
|
519
|
-
throw new
|
|
520
|
-
`The pending sync transaction is already committed, you can't commit it again.`
|
|
521
|
-
);
|
|
515
|
+
throw new errors.SyncTransactionAlreadyCommittedError();
|
|
522
516
|
}
|
|
523
517
|
pendingTransaction.committed = true;
|
|
524
518
|
if (this._status === `loading`) {
|
|
@@ -550,7 +544,7 @@ class CollectionImpl {
|
|
|
550
544
|
return;
|
|
551
545
|
}
|
|
552
546
|
if (this._status === `error`) {
|
|
553
|
-
reject(new
|
|
547
|
+
reject(new errors.CollectionIsInErrorStateError());
|
|
554
548
|
return;
|
|
555
549
|
}
|
|
556
550
|
this.onFirstReady(() => {
|
|
@@ -584,16 +578,12 @@ class CollectionImpl {
|
|
|
584
578
|
} catch (error) {
|
|
585
579
|
queueMicrotask(() => {
|
|
586
580
|
if (error instanceof Error) {
|
|
587
|
-
const wrappedError = new
|
|
588
|
-
`Collection "${this.id}" sync cleanup function threw an error: ${error.message}`
|
|
589
|
-
);
|
|
581
|
+
const wrappedError = new errors.SyncCleanupError(this.id, error);
|
|
590
582
|
wrappedError.cause = error;
|
|
591
583
|
wrappedError.stack = error.stack;
|
|
592
584
|
throw wrappedError;
|
|
593
585
|
} else {
|
|
594
|
-
throw new
|
|
595
|
-
`Collection "${this.id}" sync cleanup function threw an error: ${String(error)}`
|
|
596
|
-
);
|
|
586
|
+
throw new errors.SyncCleanupError(this.id, error);
|
|
597
587
|
}
|
|
598
588
|
});
|
|
599
589
|
}
|
|
@@ -657,9 +647,7 @@ class CollectionImpl {
|
|
|
657
647
|
this.activeSubscribersCount = 0;
|
|
658
648
|
this.startGCTimer();
|
|
659
649
|
} else if (this.activeSubscribersCount < 0) {
|
|
660
|
-
throw new
|
|
661
|
-
`Active subscribers count is negative - this should never happen`
|
|
662
|
-
);
|
|
650
|
+
throw new errors.NegativeActiveSubscribersError();
|
|
663
651
|
}
|
|
664
652
|
}
|
|
665
653
|
/**
|
|
@@ -733,8 +721,14 @@ class CollectionImpl {
|
|
|
733
721
|
}
|
|
734
722
|
return true;
|
|
735
723
|
});
|
|
724
|
+
if (filteredEvents.length > 0) {
|
|
725
|
+
this.updateIndexes(filteredEvents);
|
|
726
|
+
}
|
|
736
727
|
this.emitEvents(filteredEvents);
|
|
737
728
|
} else {
|
|
729
|
+
if (filteredEventsBySyncStatus.length > 0) {
|
|
730
|
+
this.updateIndexes(filteredEventsBySyncStatus);
|
|
731
|
+
}
|
|
738
732
|
this.emitEvents(filteredEventsBySyncStatus);
|
|
739
733
|
}
|
|
740
734
|
}
|
|
@@ -928,24 +922,147 @@ class CollectionImpl {
|
|
|
928
922
|
return result;
|
|
929
923
|
}
|
|
930
924
|
ensureStandardSchema(schema) {
|
|
931
|
-
if (schema &&
|
|
925
|
+
if (schema && `~standard` in schema) {
|
|
932
926
|
return schema;
|
|
933
927
|
}
|
|
934
|
-
throw new
|
|
935
|
-
`Schema must either implement the standard-schema interface or be a Zod schema`
|
|
936
|
-
);
|
|
928
|
+
throw new errors.InvalidSchemaError();
|
|
937
929
|
}
|
|
938
930
|
getKeyFromItem(item) {
|
|
939
931
|
return this.config.getKey(item);
|
|
940
932
|
}
|
|
941
933
|
generateGlobalKey(key, item) {
|
|
942
934
|
if (typeof key === `undefined`) {
|
|
943
|
-
throw new
|
|
944
|
-
`An object was created without a defined key: ${JSON.stringify(item)}`
|
|
945
|
-
);
|
|
935
|
+
throw new errors.UndefinedKeyError(item);
|
|
946
936
|
}
|
|
947
937
|
return `KEY::${this.id}/${key}`;
|
|
948
938
|
}
|
|
939
|
+
/**
|
|
940
|
+
* Creates an index on a collection for faster queries.
|
|
941
|
+
* Indexes significantly improve query performance by allowing binary search
|
|
942
|
+
* and range queries instead of full scans.
|
|
943
|
+
*
|
|
944
|
+
* @template TResolver - The type of the index resolver (constructor or async loader)
|
|
945
|
+
* @param indexCallback - Function that extracts the indexed value from each item
|
|
946
|
+
* @param config - Configuration including index type and type-specific options
|
|
947
|
+
* @returns An index proxy that provides access to the index when ready
|
|
948
|
+
*
|
|
949
|
+
* @example
|
|
950
|
+
* // Create a default ordered index
|
|
951
|
+
* const ageIndex = collection.createIndex((row) => row.age)
|
|
952
|
+
*
|
|
953
|
+
* // Create a ordered index with custom options
|
|
954
|
+
* const ageIndex = collection.createIndex((row) => row.age, {
|
|
955
|
+
* indexType: OrderedIndex,
|
|
956
|
+
* options: { compareFn: customComparator },
|
|
957
|
+
* name: 'age_btree'
|
|
958
|
+
* })
|
|
959
|
+
*
|
|
960
|
+
* // Create an async-loaded index
|
|
961
|
+
* const textIndex = collection.createIndex((row) => row.content, {
|
|
962
|
+
* indexType: async () => {
|
|
963
|
+
* const { FullTextIndex } = await import('./indexes/fulltext.js')
|
|
964
|
+
* return FullTextIndex
|
|
965
|
+
* },
|
|
966
|
+
* options: { language: 'en' }
|
|
967
|
+
* })
|
|
968
|
+
*/
|
|
969
|
+
createIndex(indexCallback, config = {}) {
|
|
970
|
+
this.validateCollectionUsable(`createIndex`);
|
|
971
|
+
const indexId = ++this.indexCounter;
|
|
972
|
+
const singleRowRefProxy = refProxy.createSingleRowRefProxy();
|
|
973
|
+
const indexExpression = indexCallback(singleRowRefProxy);
|
|
974
|
+
const expression = refProxy.toExpression(indexExpression);
|
|
975
|
+
const resolver = config.indexType ?? orderedIndex.OrderedIndex;
|
|
976
|
+
const lazyIndex$1 = new lazyIndex.LazyIndexWrapper(
|
|
977
|
+
indexId,
|
|
978
|
+
expression,
|
|
979
|
+
config.name,
|
|
980
|
+
resolver,
|
|
981
|
+
config.options,
|
|
982
|
+
this.entries()
|
|
983
|
+
);
|
|
984
|
+
this.lazyIndexes.set(indexId, lazyIndex$1);
|
|
985
|
+
if (resolver === orderedIndex.OrderedIndex) {
|
|
986
|
+
try {
|
|
987
|
+
const resolvedIndex = lazyIndex$1.getResolved();
|
|
988
|
+
this.resolvedIndexes.set(indexId, resolvedIndex);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
console.warn(`Failed to resolve OrderedIndex:`, error);
|
|
991
|
+
}
|
|
992
|
+
} else if (typeof resolver === `function` && resolver.prototype) {
|
|
993
|
+
try {
|
|
994
|
+
const resolvedIndex = lazyIndex$1.getResolved();
|
|
995
|
+
this.resolvedIndexes.set(indexId, resolvedIndex);
|
|
996
|
+
} catch {
|
|
997
|
+
this.resolveSingleIndex(indexId, lazyIndex$1).catch((error) => {
|
|
998
|
+
console.warn(`Failed to resolve single index:`, error);
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
} else if (this.isIndexesResolved) {
|
|
1002
|
+
this.resolveSingleIndex(indexId, lazyIndex$1).catch((error) => {
|
|
1003
|
+
console.warn(`Failed to resolve single index:`, error);
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
return new lazyIndex.IndexProxy(indexId, lazyIndex$1);
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Resolve all lazy indexes (called when collection first syncs)
|
|
1010
|
+
* @private
|
|
1011
|
+
*/
|
|
1012
|
+
async resolveAllIndexes() {
|
|
1013
|
+
if (this.isIndexesResolved) return;
|
|
1014
|
+
const resolutionPromises = Array.from(this.lazyIndexes.entries()).map(
|
|
1015
|
+
async ([indexId, lazyIndex2]) => {
|
|
1016
|
+
const resolvedIndex = await lazyIndex2.resolve();
|
|
1017
|
+
resolvedIndex.build(this.entries());
|
|
1018
|
+
this.resolvedIndexes.set(indexId, resolvedIndex);
|
|
1019
|
+
return { indexId, resolvedIndex };
|
|
1020
|
+
}
|
|
1021
|
+
);
|
|
1022
|
+
await Promise.all(resolutionPromises);
|
|
1023
|
+
this.isIndexesResolved = true;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Resolve a single index immediately
|
|
1027
|
+
* @private
|
|
1028
|
+
*/
|
|
1029
|
+
async resolveSingleIndex(indexId, lazyIndex2) {
|
|
1030
|
+
const resolvedIndex = await lazyIndex2.resolve();
|
|
1031
|
+
resolvedIndex.build(this.entries());
|
|
1032
|
+
this.resolvedIndexes.set(indexId, resolvedIndex);
|
|
1033
|
+
return resolvedIndex;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Get resolved indexes for query optimization
|
|
1037
|
+
*/
|
|
1038
|
+
get indexes() {
|
|
1039
|
+
return this.resolvedIndexes;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Updates all indexes when the collection changes
|
|
1043
|
+
* @private
|
|
1044
|
+
*/
|
|
1045
|
+
updateIndexes(changes) {
|
|
1046
|
+
for (const index of this.resolvedIndexes.values()) {
|
|
1047
|
+
for (const change of changes) {
|
|
1048
|
+
switch (change.type) {
|
|
1049
|
+
case `insert`:
|
|
1050
|
+
index.add(change.key, change.value);
|
|
1051
|
+
break;
|
|
1052
|
+
case `update`:
|
|
1053
|
+
if (change.previousValue) {
|
|
1054
|
+
index.update(change.key, change.previousValue, change.value);
|
|
1055
|
+
} else {
|
|
1056
|
+
index.add(change.key, change.value);
|
|
1057
|
+
}
|
|
1058
|
+
break;
|
|
1059
|
+
case `delete`:
|
|
1060
|
+
index.remove(change.key, change.value);
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
949
1066
|
deepEqual(a, b) {
|
|
950
1067
|
if (a === b) return true;
|
|
951
1068
|
if (a == null || b == null) return false;
|
|
@@ -973,7 +1090,7 @@ class CollectionImpl {
|
|
|
973
1090
|
const mergedData = Object.assign({}, existingData, data);
|
|
974
1091
|
const result2 = standardSchema[`~standard`].validate(mergedData);
|
|
975
1092
|
if (result2 instanceof Promise) {
|
|
976
|
-
throw new
|
|
1093
|
+
throw new errors.SchemaMustBeSynchronousError();
|
|
977
1094
|
}
|
|
978
1095
|
if (`issues` in result2 && result2.issues) {
|
|
979
1096
|
const typedIssues = result2.issues.map((issue) => {
|
|
@@ -983,14 +1100,14 @@ class CollectionImpl {
|
|
|
983
1100
|
path: (_a = issue.path) == null ? void 0 : _a.map((p) => String(p))
|
|
984
1101
|
};
|
|
985
1102
|
});
|
|
986
|
-
throw new SchemaValidationError(type, typedIssues);
|
|
1103
|
+
throw new errors.SchemaValidationError(type, typedIssues);
|
|
987
1104
|
}
|
|
988
1105
|
return data;
|
|
989
1106
|
}
|
|
990
1107
|
}
|
|
991
1108
|
const result = standardSchema[`~standard`].validate(data);
|
|
992
1109
|
if (result instanceof Promise) {
|
|
993
|
-
throw new
|
|
1110
|
+
throw new errors.SchemaMustBeSynchronousError();
|
|
994
1111
|
}
|
|
995
1112
|
if (`issues` in result && result.issues) {
|
|
996
1113
|
const typedIssues = result.issues.map((issue) => {
|
|
@@ -1000,34 +1117,30 @@ class CollectionImpl {
|
|
|
1000
1117
|
path: (_a = issue.path) == null ? void 0 : _a.map((p) => String(p))
|
|
1001
1118
|
};
|
|
1002
1119
|
});
|
|
1003
|
-
throw new SchemaValidationError(type, typedIssues);
|
|
1120
|
+
throw new errors.SchemaValidationError(type, typedIssues);
|
|
1004
1121
|
}
|
|
1005
1122
|
return result.value;
|
|
1006
1123
|
}
|
|
1007
1124
|
update(keys, configOrCallback, maybeCallback) {
|
|
1008
1125
|
if (typeof keys === `undefined`) {
|
|
1009
|
-
throw new
|
|
1126
|
+
throw new errors.MissingUpdateArgumentError();
|
|
1010
1127
|
}
|
|
1011
1128
|
this.validateCollectionUsable(`update`);
|
|
1012
1129
|
const ambientTransaction = transactions.getActiveTransaction();
|
|
1013
1130
|
if (!ambientTransaction && !this.config.onUpdate) {
|
|
1014
|
-
throw new
|
|
1015
|
-
`Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`
|
|
1016
|
-
);
|
|
1131
|
+
throw new errors.MissingUpdateHandlerError();
|
|
1017
1132
|
}
|
|
1018
1133
|
const isArray = Array.isArray(keys);
|
|
1019
1134
|
const keysArray = isArray ? keys : [keys];
|
|
1020
1135
|
if (isArray && keysArray.length === 0) {
|
|
1021
|
-
throw new
|
|
1136
|
+
throw new errors.NoKeysPassedToUpdateError();
|
|
1022
1137
|
}
|
|
1023
1138
|
const callback = typeof configOrCallback === `function` ? configOrCallback : maybeCallback;
|
|
1024
1139
|
const config = typeof configOrCallback === `function` ? {} : configOrCallback;
|
|
1025
1140
|
const currentObjects = keysArray.map((key) => {
|
|
1026
1141
|
const item = this.get(key);
|
|
1027
1142
|
if (!item) {
|
|
1028
|
-
throw new
|
|
1029
|
-
`The key "${key}" was passed to update but an object for this key was not found in the collection`
|
|
1030
|
-
);
|
|
1143
|
+
throw new errors.UpdateKeyNotFoundError(key);
|
|
1031
1144
|
}
|
|
1032
1145
|
return item;
|
|
1033
1146
|
});
|
|
@@ -1063,9 +1176,7 @@ class CollectionImpl {
|
|
|
1063
1176
|
const originalItemId = this.getKeyFromItem(originalItem);
|
|
1064
1177
|
const modifiedItemId = this.getKeyFromItem(modifiedItem);
|
|
1065
1178
|
if (originalItemId !== modifiedItemId) {
|
|
1066
|
-
throw new
|
|
1067
|
-
`Updating the key of an item is not allowed. Original key: "${originalItemId}", Attempted new key: "${modifiedItemId}". Please delete the old item and create a new one if a key change is necessary.`
|
|
1068
|
-
);
|
|
1179
|
+
throw new errors.KeyUpdateNotAllowedError(originalItemId, modifiedItemId);
|
|
1069
1180
|
}
|
|
1070
1181
|
const globalKey = this.generateGlobalKey(modifiedItemId, modifiedItem);
|
|
1071
1182
|
return {
|
|
@@ -1177,19 +1288,29 @@ class CollectionImpl {
|
|
|
1177
1288
|
}
|
|
1178
1289
|
/**
|
|
1179
1290
|
* Returns the current state of the collection as an array of changes
|
|
1291
|
+
* @param options - Options including optional where filter
|
|
1180
1292
|
* @returns An array of changes
|
|
1293
|
+
* @example
|
|
1294
|
+
* // Get all items as changes
|
|
1295
|
+
* const allChanges = collection.currentStateAsChanges()
|
|
1296
|
+
*
|
|
1297
|
+
* // Get only items matching a condition
|
|
1298
|
+
* const activeChanges = collection.currentStateAsChanges({
|
|
1299
|
+
* where: (row) => row.status === 'active'
|
|
1300
|
+
* })
|
|
1301
|
+
*
|
|
1302
|
+
* // Get only items using a pre-compiled expression
|
|
1303
|
+
* const activeChanges = collection.currentStateAsChanges({
|
|
1304
|
+
* whereExpression: eq(row.status, 'active')
|
|
1305
|
+
* })
|
|
1181
1306
|
*/
|
|
1182
|
-
currentStateAsChanges() {
|
|
1183
|
-
return
|
|
1184
|
-
type: `insert`,
|
|
1185
|
-
key,
|
|
1186
|
-
value
|
|
1187
|
-
}));
|
|
1307
|
+
currentStateAsChanges(options = {}) {
|
|
1308
|
+
return changeEvents.currentStateAsChanges(this, options);
|
|
1188
1309
|
}
|
|
1189
1310
|
/**
|
|
1190
1311
|
* Subscribe to changes in the collection
|
|
1191
1312
|
* @param callback - Function called when items change
|
|
1192
|
-
* @param options
|
|
1313
|
+
* @param options - Subscription options including includeInitialState and where filter
|
|
1193
1314
|
* @returns Unsubscribe function - Call this to stop listening for changes
|
|
1194
1315
|
* @example
|
|
1195
1316
|
* // Basic subscription
|
|
@@ -1206,15 +1327,41 @@ class CollectionImpl {
|
|
|
1206
1327
|
* const unsubscribe = collection.subscribeChanges((changes) => {
|
|
1207
1328
|
* updateUI(changes)
|
|
1208
1329
|
* }, { includeInitialState: true })
|
|
1330
|
+
*
|
|
1331
|
+
* @example
|
|
1332
|
+
* // Subscribe only to changes matching a condition
|
|
1333
|
+
* const unsubscribe = collection.subscribeChanges((changes) => {
|
|
1334
|
+
* updateUI(changes)
|
|
1335
|
+
* }, {
|
|
1336
|
+
* includeInitialState: true,
|
|
1337
|
+
* where: (row) => row.status === 'active'
|
|
1338
|
+
* })
|
|
1339
|
+
*
|
|
1340
|
+
* @example
|
|
1341
|
+
* // Subscribe using a pre-compiled expression
|
|
1342
|
+
* const unsubscribe = collection.subscribeChanges((changes) => {
|
|
1343
|
+
* updateUI(changes)
|
|
1344
|
+
* }, {
|
|
1345
|
+
* includeInitialState: true,
|
|
1346
|
+
* whereExpression: eq(row.status, 'active')
|
|
1347
|
+
* })
|
|
1209
1348
|
*/
|
|
1210
|
-
subscribeChanges(callback,
|
|
1349
|
+
subscribeChanges(callback, options = {}) {
|
|
1211
1350
|
this.addSubscriber();
|
|
1212
|
-
if (
|
|
1213
|
-
|
|
1351
|
+
if (options.whereExpression) {
|
|
1352
|
+
autoIndex.ensureIndexForExpression(options.whereExpression, this);
|
|
1353
|
+
}
|
|
1354
|
+
const filteredCallback = options.where || options.whereExpression ? changeEvents.createFilteredCallback(callback, options) : callback;
|
|
1355
|
+
if (options.includeInitialState) {
|
|
1356
|
+
const initialChanges = this.currentStateAsChanges({
|
|
1357
|
+
where: options.where,
|
|
1358
|
+
whereExpression: options.whereExpression
|
|
1359
|
+
});
|
|
1360
|
+
filteredCallback(initialChanges);
|
|
1214
1361
|
}
|
|
1215
|
-
this.changeListeners.add(
|
|
1362
|
+
this.changeListeners.add(filteredCallback);
|
|
1216
1363
|
return () => {
|
|
1217
|
-
this.changeListeners.delete(
|
|
1364
|
+
this.changeListeners.delete(filteredCallback);
|
|
1218
1365
|
this.removeSubscriber();
|
|
1219
1366
|
};
|
|
1220
1367
|
}
|
|
@@ -1281,7 +1428,6 @@ class CollectionImpl {
|
|
|
1281
1428
|
}
|
|
1282
1429
|
}
|
|
1283
1430
|
exports.CollectionImpl = CollectionImpl;
|
|
1284
|
-
exports.SchemaValidationError = SchemaValidationError;
|
|
1285
1431
|
exports.collectionsStore = collectionsStore;
|
|
1286
1432
|
exports.createCollection = createCollection;
|
|
1287
1433
|
//# sourceMappingURL=collection.cjs.map
|