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