@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/esm/collection.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { withArrayChangeTracking, withChangeTracking } from "./proxy.js";
|
|
2
|
-
import { getActiveTransaction, createTransaction } from "./transactions.js";
|
|
3
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";
|
|
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
|
}
|
|
@@ -926,24 +920,147 @@ class CollectionImpl {
|
|
|
926
920
|
return result;
|
|
927
921
|
}
|
|
928
922
|
ensureStandardSchema(schema) {
|
|
929
|
-
if (schema &&
|
|
923
|
+
if (schema && `~standard` in schema) {
|
|
930
924
|
return schema;
|
|
931
925
|
}
|
|
932
|
-
throw new
|
|
933
|
-
`Schema must either implement the standard-schema interface or be a Zod schema`
|
|
934
|
-
);
|
|
926
|
+
throw new InvalidSchemaError();
|
|
935
927
|
}
|
|
936
928
|
getKeyFromItem(item) {
|
|
937
929
|
return this.config.getKey(item);
|
|
938
930
|
}
|
|
939
931
|
generateGlobalKey(key, item) {
|
|
940
932
|
if (typeof key === `undefined`) {
|
|
941
|
-
throw new
|
|
942
|
-
`An object was created without a defined key: ${JSON.stringify(item)}`
|
|
943
|
-
);
|
|
933
|
+
throw new UndefinedKeyError(item);
|
|
944
934
|
}
|
|
945
935
|
return `KEY::${this.id}/${key}`;
|
|
946
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
|
+
}
|
|
947
1064
|
deepEqual(a, b) {
|
|
948
1065
|
if (a === b) return true;
|
|
949
1066
|
if (a == null || b == null) return false;
|
|
@@ -971,7 +1088,7 @@ class CollectionImpl {
|
|
|
971
1088
|
const mergedData = Object.assign({}, existingData, data);
|
|
972
1089
|
const result2 = standardSchema[`~standard`].validate(mergedData);
|
|
973
1090
|
if (result2 instanceof Promise) {
|
|
974
|
-
throw new
|
|
1091
|
+
throw new SchemaMustBeSynchronousError();
|
|
975
1092
|
}
|
|
976
1093
|
if (`issues` in result2 && result2.issues) {
|
|
977
1094
|
const typedIssues = result2.issues.map((issue) => {
|
|
@@ -988,7 +1105,7 @@ class CollectionImpl {
|
|
|
988
1105
|
}
|
|
989
1106
|
const result = standardSchema[`~standard`].validate(data);
|
|
990
1107
|
if (result instanceof Promise) {
|
|
991
|
-
throw new
|
|
1108
|
+
throw new SchemaMustBeSynchronousError();
|
|
992
1109
|
}
|
|
993
1110
|
if (`issues` in result && result.issues) {
|
|
994
1111
|
const typedIssues = result.issues.map((issue) => {
|
|
@@ -1004,28 +1121,24 @@ class CollectionImpl {
|
|
|
1004
1121
|
}
|
|
1005
1122
|
update(keys, configOrCallback, maybeCallback) {
|
|
1006
1123
|
if (typeof keys === `undefined`) {
|
|
1007
|
-
throw new
|
|
1124
|
+
throw new MissingUpdateArgumentError();
|
|
1008
1125
|
}
|
|
1009
1126
|
this.validateCollectionUsable(`update`);
|
|
1010
1127
|
const ambientTransaction = getActiveTransaction();
|
|
1011
1128
|
if (!ambientTransaction && !this.config.onUpdate) {
|
|
1012
|
-
throw new
|
|
1013
|
-
`Collection.update called directly (not within an explicit transaction) but no 'onUpdate' handler is configured.`
|
|
1014
|
-
);
|
|
1129
|
+
throw new MissingUpdateHandlerError();
|
|
1015
1130
|
}
|
|
1016
1131
|
const isArray = Array.isArray(keys);
|
|
1017
1132
|
const keysArray = isArray ? keys : [keys];
|
|
1018
1133
|
if (isArray && keysArray.length === 0) {
|
|
1019
|
-
throw new
|
|
1134
|
+
throw new NoKeysPassedToUpdateError();
|
|
1020
1135
|
}
|
|
1021
1136
|
const callback = typeof configOrCallback === `function` ? configOrCallback : maybeCallback;
|
|
1022
1137
|
const config = typeof configOrCallback === `function` ? {} : configOrCallback;
|
|
1023
1138
|
const currentObjects = keysArray.map((key) => {
|
|
1024
1139
|
const item = this.get(key);
|
|
1025
1140
|
if (!item) {
|
|
1026
|
-
throw new
|
|
1027
|
-
`The key "${key}" was passed to update but an object for this key was not found in the collection`
|
|
1028
|
-
);
|
|
1141
|
+
throw new UpdateKeyNotFoundError(key);
|
|
1029
1142
|
}
|
|
1030
1143
|
return item;
|
|
1031
1144
|
});
|
|
@@ -1061,9 +1174,7 @@ class CollectionImpl {
|
|
|
1061
1174
|
const originalItemId = this.getKeyFromItem(originalItem);
|
|
1062
1175
|
const modifiedItemId = this.getKeyFromItem(modifiedItem);
|
|
1063
1176
|
if (originalItemId !== modifiedItemId) {
|
|
1064
|
-
throw new
|
|
1065
|
-
`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.`
|
|
1066
|
-
);
|
|
1177
|
+
throw new KeyUpdateNotAllowedError(originalItemId, modifiedItemId);
|
|
1067
1178
|
}
|
|
1068
1179
|
const globalKey = this.generateGlobalKey(modifiedItemId, modifiedItem);
|
|
1069
1180
|
return {
|
|
@@ -1175,19 +1286,29 @@ class CollectionImpl {
|
|
|
1175
1286
|
}
|
|
1176
1287
|
/**
|
|
1177
1288
|
* Returns the current state of the collection as an array of changes
|
|
1289
|
+
* @param options - Options including optional where filter
|
|
1178
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
|
+
* })
|
|
1179
1304
|
*/
|
|
1180
|
-
currentStateAsChanges() {
|
|
1181
|
-
return
|
|
1182
|
-
type: `insert`,
|
|
1183
|
-
key,
|
|
1184
|
-
value
|
|
1185
|
-
}));
|
|
1305
|
+
currentStateAsChanges(options = {}) {
|
|
1306
|
+
return currentStateAsChanges(this, options);
|
|
1186
1307
|
}
|
|
1187
1308
|
/**
|
|
1188
1309
|
* Subscribe to changes in the collection
|
|
1189
1310
|
* @param callback - Function called when items change
|
|
1190
|
-
* @param options
|
|
1311
|
+
* @param options - Subscription options including includeInitialState and where filter
|
|
1191
1312
|
* @returns Unsubscribe function - Call this to stop listening for changes
|
|
1192
1313
|
* @example
|
|
1193
1314
|
* // Basic subscription
|
|
@@ -1204,15 +1325,41 @@ class CollectionImpl {
|
|
|
1204
1325
|
* const unsubscribe = collection.subscribeChanges((changes) => {
|
|
1205
1326
|
* updateUI(changes)
|
|
1206
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
|
+
* })
|
|
1207
1346
|
*/
|
|
1208
|
-
subscribeChanges(callback,
|
|
1347
|
+
subscribeChanges(callback, options = {}) {
|
|
1209
1348
|
this.addSubscriber();
|
|
1210
|
-
if (
|
|
1211
|
-
|
|
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);
|
|
1212
1359
|
}
|
|
1213
|
-
this.changeListeners.add(
|
|
1360
|
+
this.changeListeners.add(filteredCallback);
|
|
1214
1361
|
return () => {
|
|
1215
|
-
this.changeListeners.delete(
|
|
1362
|
+
this.changeListeners.delete(filteredCallback);
|
|
1216
1363
|
this.removeSubscriber();
|
|
1217
1364
|
};
|
|
1218
1365
|
}
|
|
@@ -1280,7 +1427,6 @@ class CollectionImpl {
|
|
|
1280
1427
|
}
|
|
1281
1428
|
export {
|
|
1282
1429
|
CollectionImpl,
|
|
1283
|
-
SchemaValidationError,
|
|
1284
1430
|
collectionsStore,
|
|
1285
1431
|
createCollection
|
|
1286
1432
|
};
|