@tanstack/db 0.5.32 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/change-events.d.cts +3 -2
- package/dist/cjs/collection/changes.cjs +13 -4
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/changes.d.cts +10 -1
- package/dist/cjs/collection/cleanup-queue.cjs +89 -0
- package/dist/cjs/collection/cleanup-queue.cjs.map +1 -0
- package/dist/cjs/collection/cleanup-queue.d.cts +30 -0
- package/dist/cjs/collection/events.cjs +14 -0
- package/dist/cjs/collection/events.cjs.map +1 -1
- package/dist/cjs/collection/events.d.cts +39 -1
- package/dist/cjs/collection/index.cjs +66 -28
- package/dist/cjs/collection/index.cjs.map +1 -1
- package/dist/cjs/collection/index.d.cts +49 -36
- package/dist/cjs/collection/indexes.cjs +211 -62
- package/dist/cjs/collection/indexes.cjs.map +1 -1
- package/dist/cjs/collection/indexes.d.cts +27 -17
- package/dist/cjs/collection/lifecycle.cjs +5 -22
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/lifecycle.d.cts +0 -1
- package/dist/cjs/collection/mutations.cjs +18 -0
- package/dist/cjs/collection/mutations.cjs.map +1 -1
- package/dist/cjs/collection/mutations.d.cts +1 -0
- package/dist/cjs/collection/state.cjs +381 -53
- package/dist/cjs/collection/state.cjs.map +1 -1
- package/dist/cjs/collection/state.d.cts +65 -1
- package/dist/cjs/collection/subscription.cjs +6 -0
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +4 -0
- package/dist/cjs/collection/sync.cjs +108 -1
- package/dist/cjs/collection/sync.cjs.map +1 -1
- package/dist/cjs/collection/sync.d.cts +2 -0
- package/dist/cjs/collection/transaction-metadata.cjs +5 -0
- package/dist/cjs/collection/transaction-metadata.cjs.map +1 -0
- package/dist/cjs/collection/transaction-metadata.d.cts +1 -0
- package/dist/cjs/errors.cjs +8 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/index.cjs +24 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +12 -3
- package/dist/cjs/indexes/auto-index.cjs +13 -6
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.cjs +0 -3
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +2 -6
- package/dist/cjs/indexes/basic-index.cjs +361 -0
- package/dist/cjs/indexes/basic-index.cjs.map +1 -0
- package/dist/cjs/indexes/basic-index.d.cts +102 -0
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +1 -1
- package/dist/cjs/indexes/index-options.d.cts +8 -9
- package/dist/cjs/indexes/index-registry.cjs +89 -0
- package/dist/cjs/indexes/index-registry.cjs.map +1 -0
- package/dist/cjs/indexes/index-registry.d.cts +61 -0
- package/dist/cjs/local-only.cjs +5 -0
- package/dist/cjs/local-only.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.cjs +27 -11
- package/dist/cjs/query/builder/functions.cjs.map +1 -1
- package/dist/cjs/query/builder/functions.d.cts +25 -3
- package/dist/cjs/query/builder/index.cjs +200 -39
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/index.d.cts +4 -3
- package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
- package/dist/cjs/query/builder/ref-proxy.d.cts +14 -3
- package/dist/cjs/query/builder/types.d.cts +84 -19
- package/dist/cjs/query/compiler/evaluators.cjs +51 -0
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +100 -28
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/group-by.d.cts +4 -2
- package/dist/cjs/query/compiler/index.cjs +283 -11
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +30 -2
- package/dist/cjs/query/compiler/order-by.cjs +29 -10
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +1 -1
- package/dist/cjs/query/compiler/select.cjs +8 -0
- package/dist/cjs/query/compiler/select.cjs.map +1 -1
- package/dist/cjs/query/effect.cjs +602 -0
- package/dist/cjs/query/effect.cjs.map +1 -0
- package/dist/cjs/query/effect.d.cts +94 -0
- package/dist/cjs/query/index.d.cts +2 -1
- package/dist/cjs/query/ir.cjs +18 -1
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +21 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +493 -66
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +7 -0
- package/dist/cjs/query/live/collection-subscriber.cjs +33 -100
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +0 -1
- package/dist/cjs/query/live/types.d.cts +3 -3
- package/dist/cjs/query/live/utils.cjs +219 -0
- package/dist/cjs/query/live/utils.cjs.map +1 -0
- package/dist/cjs/query/live/utils.d.cts +110 -0
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +9 -6
- package/dist/cjs/query/query-once.cjs.map +1 -1
- package/dist/cjs/query/query-once.d.cts +7 -5
- package/dist/cjs/query/subset-dedupe.cjs +9 -3
- package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
- package/dist/cjs/types.d.cts +42 -8
- package/dist/cjs/utils/array-utils.cjs +27 -0
- package/dist/cjs/utils/array-utils.cjs.map +1 -0
- package/dist/cjs/utils/array-utils.d.cts +16 -0
- package/dist/cjs/utils/comparison.cjs +11 -0
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/index-optimization.cjs +4 -0
- package/dist/cjs/utils/index-optimization.cjs.map +1 -1
- package/dist/cjs/utils.cjs +7 -9
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +6 -1
- package/dist/cjs/virtual-props.cjs +33 -0
- package/dist/cjs/virtual-props.cjs.map +1 -0
- package/dist/cjs/virtual-props.d.cts +196 -0
- package/dist/esm/collection/change-events.d.ts +3 -2
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.d.ts +10 -1
- package/dist/esm/collection/changes.js +13 -4
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/cleanup-queue.d.ts +30 -0
- package/dist/esm/collection/cleanup-queue.js +89 -0
- package/dist/esm/collection/cleanup-queue.js.map +1 -0
- package/dist/esm/collection/events.d.ts +39 -1
- package/dist/esm/collection/events.js +14 -0
- package/dist/esm/collection/events.js.map +1 -1
- package/dist/esm/collection/index.d.ts +49 -36
- package/dist/esm/collection/index.js +67 -29
- package/dist/esm/collection/index.js.map +1 -1
- package/dist/esm/collection/indexes.d.ts +27 -17
- package/dist/esm/collection/indexes.js +211 -62
- package/dist/esm/collection/indexes.js.map +1 -1
- package/dist/esm/collection/lifecycle.d.ts +0 -1
- package/dist/esm/collection/lifecycle.js +5 -22
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/mutations.d.ts +1 -0
- package/dist/esm/collection/mutations.js +18 -0
- package/dist/esm/collection/mutations.js.map +1 -1
- package/dist/esm/collection/state.d.ts +65 -1
- package/dist/esm/collection/state.js +381 -53
- package/dist/esm/collection/state.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +4 -0
- package/dist/esm/collection/subscription.js +6 -0
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/collection/sync.d.ts +2 -0
- package/dist/esm/collection/sync.js +108 -1
- package/dist/esm/collection/sync.js.map +1 -1
- package/dist/esm/collection/transaction-metadata.d.ts +1 -0
- package/dist/esm/collection/transaction-metadata.js +5 -0
- package/dist/esm/collection/transaction-metadata.js.map +1 -0
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +8 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +12 -3
- package/dist/esm/index.js +27 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/indexes/auto-index.js +13 -6
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +2 -6
- package/dist/esm/indexes/base-index.js +1 -4
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/basic-index.d.ts +102 -0
- package/dist/esm/indexes/basic-index.js +361 -0
- package/dist/esm/indexes/basic-index.js.map +1 -0
- package/dist/esm/indexes/btree-index.d.ts +1 -1
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/index-options.d.ts +8 -9
- package/dist/esm/indexes/index-registry.d.ts +61 -0
- package/dist/esm/indexes/index-registry.js +89 -0
- package/dist/esm/indexes/index-registry.js.map +1 -0
- package/dist/esm/local-only.js +5 -0
- package/dist/esm/local-only.js.map +1 -1
- package/dist/esm/query/builder/functions.d.ts +25 -3
- package/dist/esm/query/builder/functions.js +27 -11
- package/dist/esm/query/builder/functions.js.map +1 -1
- package/dist/esm/query/builder/index.d.ts +4 -3
- package/dist/esm/query/builder/index.js +201 -40
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/ref-proxy.d.ts +14 -3
- package/dist/esm/query/builder/ref-proxy.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +84 -19
- package/dist/esm/query/compiler/evaluators.js +51 -0
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.d.ts +4 -2
- package/dist/esm/query/compiler/group-by.js +101 -29
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +30 -2
- package/dist/esm/query/compiler/index.js +285 -13
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +1 -1
- package/dist/esm/query/compiler/order-by.js +30 -11
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/compiler/select.js +8 -0
- package/dist/esm/query/compiler/select.js.map +1 -1
- package/dist/esm/query/effect.d.ts +94 -0
- package/dist/esm/query/effect.js +602 -0
- package/dist/esm/query/effect.js.map +1 -0
- package/dist/esm/query/index.d.ts +2 -1
- package/dist/esm/query/ir.d.ts +21 -1
- package/dist/esm/query/ir.js +18 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +7 -0
- package/dist/esm/query/live/collection-config-builder.js +492 -65
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.d.ts +0 -1
- package/dist/esm/query/live/collection-subscriber.js +31 -98
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/query/live/types.d.ts +3 -3
- package/dist/esm/query/live/utils.d.ts +110 -0
- package/dist/esm/query/live/utils.js +219 -0
- package/dist/esm/query/live/utils.js.map +1 -0
- package/dist/esm/query/live-query-collection.d.ts +9 -6
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/query-once.d.ts +7 -5
- package/dist/esm/query/query-once.js.map +1 -1
- package/dist/esm/query/subset-dedupe.js +9 -3
- package/dist/esm/query/subset-dedupe.js.map +1 -1
- package/dist/esm/types.d.ts +42 -8
- package/dist/esm/utils/array-utils.d.ts +16 -0
- package/dist/esm/utils/array-utils.js +27 -0
- package/dist/esm/utils/array-utils.js.map +1 -0
- package/dist/esm/utils/comparison.js +11 -0
- package/dist/esm/utils/comparison.js.map +1 -1
- package/dist/esm/utils/index-optimization.js +4 -0
- package/dist/esm/utils/index-optimization.js.map +1 -1
- package/dist/esm/utils.d.ts +6 -1
- package/dist/esm/utils.js +7 -9
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/virtual-props.d.ts +196 -0
- package/dist/esm/virtual-props.js +33 -0
- package/dist/esm/virtual-props.js.map +1 -0
- package/package.json +2 -2
- package/skills/db-core/collection-setup/references/electric-adapter.md +1 -1
- package/src/collection/change-events.ts +13 -9
- package/src/collection/changes.ts +30 -7
- package/src/collection/cleanup-queue.ts +105 -0
- package/src/collection/events.ts +65 -0
- package/src/collection/index.ts +110 -45
- package/src/collection/indexes.ts +283 -76
- package/src/collection/lifecycle.ts +5 -26
- package/src/collection/mutations.ts +21 -0
- package/src/collection/state.ts +545 -71
- package/src/collection/subscription.ts +7 -0
- package/src/collection/sync.ts +137 -0
- package/src/collection/transaction-metadata.ts +1 -0
- package/src/errors.ts +9 -0
- package/src/index.ts +57 -3
- package/src/indexes/auto-index.ts +18 -8
- package/src/indexes/base-index.ts +2 -10
- package/src/indexes/basic-index.ts +507 -0
- package/src/indexes/btree-index.ts +1 -1
- package/src/indexes/index-options.ts +17 -37
- package/src/indexes/index-registry.ts +174 -0
- package/src/local-only.ts +7 -0
- package/src/query/builder/functions.ts +84 -7
- package/src/query/builder/index.ts +329 -9
- package/src/query/builder/ref-proxy.ts +22 -4
- package/src/query/builder/types.ts +257 -62
- package/src/query/compiler/evaluators.ts +57 -0
- package/src/query/compiler/group-by.ts +156 -35
- package/src/query/compiler/index.ts +445 -15
- package/src/query/compiler/order-by.ts +51 -12
- package/src/query/compiler/select.ts +9 -0
- package/src/query/effect.ts +1119 -0
- package/src/query/index.ts +7 -0
- package/src/query/ir.ts +23 -2
- package/src/query/live/collection-config-builder.ts +778 -104
- package/src/query/live/collection-subscriber.ts +40 -156
- package/src/query/live/types.ts +10 -4
- package/src/query/live/utils.ts +417 -0
- package/src/query/live-query-collection.ts +43 -18
- package/src/query/query-once.ts +31 -12
- package/src/query/subset-dedupe.ts +11 -7
- package/src/types.ts +49 -9
- package/src/utils/array-utils.ts +49 -0
- package/src/utils/comparison.ts +14 -0
- package/src/utils/index-optimization.ts +4 -0
- package/src/utils.ts +12 -9
- package/src/virtual-props.ts +282 -0
- package/dist/cjs/indexes/lazy-index.cjs +0 -190
- package/dist/cjs/indexes/lazy-index.cjs.map +0 -1
- package/dist/cjs/indexes/lazy-index.d.cts +0 -96
- package/dist/esm/indexes/lazy-index.d.ts +0 -96
- package/dist/esm/indexes/lazy-index.js +0 -190
- package/dist/esm/indexes/lazy-index.js.map +0 -1
- package/src/indexes/lazy-index.ts +0 -251
|
@@ -1,21 +1,173 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const lazyIndex = require("../indexes/lazy-index.cjs");
|
|
4
3
|
const refProxy = require("../query/builder/ref-proxy.cjs");
|
|
5
|
-
const
|
|
4
|
+
const errors = require("../errors.cjs");
|
|
5
|
+
const INDEX_SIGNATURE_VERSION = 1;
|
|
6
|
+
function compareStringsCodePoint(left, right) {
|
|
7
|
+
if (left === right) {
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
return left < right ? -1 : 1;
|
|
11
|
+
}
|
|
12
|
+
function resolveResolverMetadata(resolver) {
|
|
13
|
+
return {
|
|
14
|
+
kind: `constructor`,
|
|
15
|
+
...resolver.name ? { name: resolver.name } : {}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function toSerializableIndexValue(value) {
|
|
19
|
+
if (value == null) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
switch (typeof value) {
|
|
23
|
+
case `string`:
|
|
24
|
+
case `boolean`:
|
|
25
|
+
return value;
|
|
26
|
+
case `number`:
|
|
27
|
+
return Number.isFinite(value) ? value : null;
|
|
28
|
+
case `bigint`:
|
|
29
|
+
return { __type: `bigint`, value: value.toString() };
|
|
30
|
+
case `function`:
|
|
31
|
+
case `symbol`:
|
|
32
|
+
return void 0;
|
|
33
|
+
case `undefined`:
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
return value.map((entry) => toSerializableIndexValue(entry) ?? null);
|
|
38
|
+
}
|
|
39
|
+
if (value instanceof Date) {
|
|
40
|
+
return {
|
|
41
|
+
__type: `date`,
|
|
42
|
+
value: value.toISOString()
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (value instanceof Set) {
|
|
46
|
+
const serializedValues = Array.from(value).map((entry) => toSerializableIndexValue(entry) ?? null).sort(
|
|
47
|
+
(a, b) => compareStringsCodePoint(
|
|
48
|
+
stableStringifyCollectionIndexValue(a),
|
|
49
|
+
stableStringifyCollectionIndexValue(b)
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
return {
|
|
53
|
+
__type: `set`,
|
|
54
|
+
values: serializedValues
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (value instanceof Map) {
|
|
58
|
+
const serializedEntries = Array.from(value.entries()).map(([mapKey, mapValue]) => ({
|
|
59
|
+
key: toSerializableIndexValue(mapKey) ?? null,
|
|
60
|
+
value: toSerializableIndexValue(mapValue) ?? null
|
|
61
|
+
})).sort(
|
|
62
|
+
(a, b) => compareStringsCodePoint(
|
|
63
|
+
stableStringifyCollectionIndexValue(a.key),
|
|
64
|
+
stableStringifyCollectionIndexValue(b.key)
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
return {
|
|
68
|
+
__type: `map`,
|
|
69
|
+
entries: serializedEntries
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (value instanceof RegExp) {
|
|
73
|
+
return {
|
|
74
|
+
__type: `regexp`,
|
|
75
|
+
value: value.toString()
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const serializedObject = {};
|
|
79
|
+
const entries = Object.entries(value).sort(
|
|
80
|
+
([leftKey], [rightKey]) => compareStringsCodePoint(leftKey, rightKey)
|
|
81
|
+
);
|
|
82
|
+
for (const [key, entryValue] of entries) {
|
|
83
|
+
const serializedEntry = toSerializableIndexValue(entryValue);
|
|
84
|
+
if (serializedEntry !== void 0) {
|
|
85
|
+
serializedObject[key] = serializedEntry;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return serializedObject;
|
|
89
|
+
}
|
|
90
|
+
function stableStringifyCollectionIndexValue(value) {
|
|
91
|
+
if (value === null) {
|
|
92
|
+
return `null`;
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
return `[${value.map(stableStringifyCollectionIndexValue).join(`,`)}]`;
|
|
96
|
+
}
|
|
97
|
+
if (typeof value !== `object`) {
|
|
98
|
+
return JSON.stringify(value);
|
|
99
|
+
}
|
|
100
|
+
const sortedKeys = Object.keys(value).sort(
|
|
101
|
+
(left, right) => compareStringsCodePoint(left, right)
|
|
102
|
+
);
|
|
103
|
+
const serializedEntries = sortedKeys.map(
|
|
104
|
+
(key) => `${JSON.stringify(key)}:${stableStringifyCollectionIndexValue(value[key])}`
|
|
105
|
+
);
|
|
106
|
+
return `{${serializedEntries.join(`,`)}}`;
|
|
107
|
+
}
|
|
108
|
+
function createCollectionIndexMetadata(indexId, expression, name, resolver, options) {
|
|
109
|
+
const resolverMetadata = resolveResolverMetadata(resolver);
|
|
110
|
+
const serializedExpression = toSerializableIndexValue(expression) ?? null;
|
|
111
|
+
const serializedOptions = toSerializableIndexValue(options);
|
|
112
|
+
const signatureInput = toSerializableIndexValue({
|
|
113
|
+
signatureVersion: INDEX_SIGNATURE_VERSION,
|
|
114
|
+
expression: serializedExpression,
|
|
115
|
+
options: serializedOptions ?? null
|
|
116
|
+
});
|
|
117
|
+
const normalizedSignatureInput = signatureInput ?? null;
|
|
118
|
+
const signature = stableStringifyCollectionIndexValue(
|
|
119
|
+
normalizedSignatureInput
|
|
120
|
+
);
|
|
121
|
+
return {
|
|
122
|
+
signatureVersion: INDEX_SIGNATURE_VERSION,
|
|
123
|
+
signature,
|
|
124
|
+
indexId,
|
|
125
|
+
name,
|
|
126
|
+
expression,
|
|
127
|
+
resolver: resolverMetadata,
|
|
128
|
+
...serializedOptions === void 0 ? {} : { options: serializedOptions }
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function cloneSerializableIndexValue(value) {
|
|
132
|
+
if (value === null || typeof value !== `object`) {
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
if (Array.isArray(value)) {
|
|
136
|
+
return value.map((entry) => cloneSerializableIndexValue(entry));
|
|
137
|
+
}
|
|
138
|
+
const cloned = {};
|
|
139
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
140
|
+
cloned[key] = cloneSerializableIndexValue(entryValue);
|
|
141
|
+
}
|
|
142
|
+
return cloned;
|
|
143
|
+
}
|
|
144
|
+
function cloneExpression(expression) {
|
|
145
|
+
return JSON.parse(JSON.stringify(expression));
|
|
146
|
+
}
|
|
6
147
|
class CollectionIndexesManager {
|
|
7
148
|
constructor() {
|
|
8
|
-
this.
|
|
9
|
-
this.
|
|
10
|
-
this.isIndexesResolved = false;
|
|
149
|
+
this.indexes = /* @__PURE__ */ new Map();
|
|
150
|
+
this.indexMetadata = /* @__PURE__ */ new Map();
|
|
11
151
|
this.indexCounter = 0;
|
|
12
152
|
}
|
|
13
153
|
setDeps(deps) {
|
|
14
154
|
this.state = deps.state;
|
|
15
155
|
this.lifecycle = deps.lifecycle;
|
|
156
|
+
this.defaultIndexType = deps.defaultIndexType;
|
|
157
|
+
this.events = deps.events;
|
|
16
158
|
}
|
|
17
159
|
/**
|
|
18
160
|
* Creates an index on a collection for faster queries.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* // With explicit index type (recommended for tree-shaking)
|
|
165
|
+
* import { BasicIndex } from '@tanstack/db'
|
|
166
|
+
* collection.createIndex((row) => row.userId, { indexType: BasicIndex })
|
|
167
|
+
*
|
|
168
|
+
* // With collection's default index type
|
|
169
|
+
* collection.createIndex((row) => row.userId)
|
|
170
|
+
* ```
|
|
19
171
|
*/
|
|
20
172
|
createIndex(indexCallback, config = {}) {
|
|
21
173
|
this.lifecycle.validateCollectionUsable(`createIndex`);
|
|
@@ -23,75 +175,73 @@ class CollectionIndexesManager {
|
|
|
23
175
|
const singleRowRefProxy = refProxy.createSingleRowRefProxy();
|
|
24
176
|
const indexExpression = indexCallback(singleRowRefProxy);
|
|
25
177
|
const expression = refProxy.toExpression(indexExpression);
|
|
26
|
-
const
|
|
27
|
-
|
|
178
|
+
const IndexType = config.indexType ?? this.defaultIndexType;
|
|
179
|
+
if (!IndexType) {
|
|
180
|
+
throw new errors.CollectionConfigurationError(
|
|
181
|
+
`No index type specified and no defaultIndexType set on collection. Either pass indexType in config, or set defaultIndexType on the collection:
|
|
182
|
+
import { BasicIndex } from '@tanstack/db'
|
|
183
|
+
createCollection({ defaultIndexType: BasicIndex, ... })`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const index = new IndexType(
|
|
28
187
|
indexId,
|
|
29
188
|
expression,
|
|
30
189
|
config.name,
|
|
31
|
-
|
|
32
|
-
config.options,
|
|
33
|
-
this.state.entries()
|
|
190
|
+
config.options
|
|
34
191
|
);
|
|
35
|
-
this.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
} else if (typeof resolver === `function` && resolver.prototype) {
|
|
44
|
-
try {
|
|
45
|
-
const resolvedIndex = lazyIndex$1.getResolved();
|
|
46
|
-
this.resolvedIndexes.set(indexId, resolvedIndex);
|
|
47
|
-
} catch {
|
|
48
|
-
this.resolveSingleIndex(indexId, lazyIndex$1).catch((error) => {
|
|
49
|
-
console.warn(`Failed to resolve single index:`, error);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
} else if (this.isIndexesResolved) {
|
|
53
|
-
this.resolveSingleIndex(indexId, lazyIndex$1).catch((error) => {
|
|
54
|
-
console.warn(`Failed to resolve single index:`, error);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
return new lazyIndex.IndexProxy(indexId, lazyIndex$1);
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Resolve all lazy indexes (called when collection first syncs)
|
|
61
|
-
*/
|
|
62
|
-
async resolveAllIndexes() {
|
|
63
|
-
if (this.isIndexesResolved) return;
|
|
64
|
-
const resolutionPromises = Array.from(this.lazyIndexes.entries()).map(
|
|
65
|
-
async ([indexId, lazyIndex2]) => {
|
|
66
|
-
const resolvedIndex = await lazyIndex2.resolve();
|
|
67
|
-
resolvedIndex.build(this.state.entries());
|
|
68
|
-
this.resolvedIndexes.set(indexId, resolvedIndex);
|
|
69
|
-
return { indexId, resolvedIndex };
|
|
70
|
-
}
|
|
192
|
+
index.build(this.state.entries());
|
|
193
|
+
this.indexes.set(indexId, index);
|
|
194
|
+
const metadata = createCollectionIndexMetadata(
|
|
195
|
+
indexId,
|
|
196
|
+
expression,
|
|
197
|
+
config.name,
|
|
198
|
+
IndexType,
|
|
199
|
+
config.options
|
|
71
200
|
);
|
|
72
|
-
|
|
73
|
-
this.
|
|
201
|
+
this.indexMetadata.set(indexId, metadata);
|
|
202
|
+
this.events.emitIndexAdded(metadata);
|
|
203
|
+
return index;
|
|
74
204
|
}
|
|
75
205
|
/**
|
|
76
|
-
*
|
|
206
|
+
* Removes an index from this collection.
|
|
207
|
+
* Returns true when an index existed and was removed, false otherwise.
|
|
77
208
|
*/
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.
|
|
82
|
-
|
|
209
|
+
removeIndex(indexOrId) {
|
|
210
|
+
this.lifecycle.validateCollectionUsable(`removeIndex`);
|
|
211
|
+
const indexId = typeof indexOrId === `number` ? indexOrId : indexOrId.id;
|
|
212
|
+
const index = this.indexes.get(indexId);
|
|
213
|
+
if (!index) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
if (typeof indexOrId !== `number` && index !== indexOrId) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
this.indexes.delete(indexId);
|
|
220
|
+
const metadata = this.indexMetadata.get(indexId);
|
|
221
|
+
this.indexMetadata.delete(indexId);
|
|
222
|
+
if (metadata) {
|
|
223
|
+
this.events.emitIndexRemoved(metadata);
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
83
226
|
}
|
|
84
227
|
/**
|
|
85
|
-
*
|
|
228
|
+
* Returns a sorted snapshot of index metadata.
|
|
229
|
+
* This allows persisted wrappers to bootstrap from indexes that were created
|
|
230
|
+
* before they attached lifecycle listeners.
|
|
86
231
|
*/
|
|
87
|
-
|
|
88
|
-
return this.
|
|
232
|
+
getIndexMetadataSnapshot() {
|
|
233
|
+
return Array.from(this.indexMetadata.values()).sort((left, right) => left.indexId - right.indexId).map((metadata) => ({
|
|
234
|
+
...metadata,
|
|
235
|
+
expression: cloneExpression(metadata.expression),
|
|
236
|
+
resolver: { ...metadata.resolver },
|
|
237
|
+
...metadata.options === void 0 ? {} : { options: cloneSerializableIndexValue(metadata.options) }
|
|
238
|
+
}));
|
|
89
239
|
}
|
|
90
240
|
/**
|
|
91
241
|
* Updates all indexes when the collection changes
|
|
92
242
|
*/
|
|
93
243
|
updateIndexes(changes) {
|
|
94
|
-
for (const index of this.
|
|
244
|
+
for (const index of this.indexes.values()) {
|
|
95
245
|
for (const change of changes) {
|
|
96
246
|
switch (change.type) {
|
|
97
247
|
case `insert`:
|
|
@@ -112,12 +262,11 @@ class CollectionIndexesManager {
|
|
|
112
262
|
}
|
|
113
263
|
}
|
|
114
264
|
/**
|
|
115
|
-
* Clean up
|
|
116
|
-
* This can be called manually or automatically by garbage collection
|
|
265
|
+
* Clean up indexes
|
|
117
266
|
*/
|
|
118
267
|
cleanup() {
|
|
119
|
-
this.
|
|
120
|
-
this.
|
|
268
|
+
this.indexes.clear();
|
|
269
|
+
this.indexMetadata.clear();
|
|
121
270
|
}
|
|
122
271
|
}
|
|
123
272
|
exports.CollectionIndexesManager = CollectionIndexesManager;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexes.cjs","sources":["../../../src/collection/indexes.ts"],"sourcesContent":["import { IndexProxy, LazyIndexWrapper } from '../indexes/lazy-index'\nimport {\n createSingleRowRefProxy,\n toExpression,\n} from '../query/builder/ref-proxy'\nimport { BTreeIndex } from '../indexes/btree-index'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { BaseIndex, IndexResolver } from '../indexes/base-index'\nimport type { ChangeMessage } from '../types'\nimport type { IndexOptions } from '../indexes/index-options'\nimport type { SingleRowRefProxy } from '../query/builder/ref-proxy'\nimport type { CollectionLifecycleManager } from './lifecycle'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionIndexesManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n public lazyIndexes = new Map<number, LazyIndexWrapper<TKey>>()\n public resolvedIndexes = new Map<number, BaseIndex<TKey>>()\n public isIndexesResolved = false\n public indexCounter = 0\n\n constructor() {}\n\n setDeps(deps: {\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n }) {\n this.state = deps.state\n this.lifecycle = deps.lifecycle\n }\n\n /**\n * Creates an index on a collection for faster queries.\n */\n public createIndex<TResolver extends IndexResolver<TKey> = typeof BTreeIndex>(\n indexCallback: (row: SingleRowRefProxy<TOutput>) => any,\n config: IndexOptions<TResolver> = {},\n ): IndexProxy<TKey> {\n this.lifecycle.validateCollectionUsable(`createIndex`)\n\n const indexId = ++this.indexCounter\n const singleRowRefProxy = createSingleRowRefProxy<TOutput>()\n const indexExpression = indexCallback(singleRowRefProxy)\n const expression = toExpression(indexExpression)\n\n // Default to BTreeIndex if no type specified\n const resolver = config.indexType ?? (BTreeIndex as unknown as TResolver)\n\n // Create lazy wrapper\n const lazyIndex = new LazyIndexWrapper<TKey>(\n indexId,\n expression,\n config.name,\n resolver,\n config.options,\n this.state.entries(),\n )\n\n this.lazyIndexes.set(indexId, lazyIndex)\n\n // For BTreeIndex, resolve immediately and synchronously\n if ((resolver as unknown) === BTreeIndex) {\n try {\n const resolvedIndex = lazyIndex.getResolved()\n this.resolvedIndexes.set(indexId, resolvedIndex)\n } catch (error) {\n console.warn(`Failed to resolve BTreeIndex:`, error)\n }\n } else if (typeof resolver === `function` && resolver.prototype) {\n // Other synchronous constructors - resolve immediately\n try {\n const resolvedIndex = lazyIndex.getResolved()\n this.resolvedIndexes.set(indexId, resolvedIndex)\n } catch {\n // Fallback to async resolution\n this.resolveSingleIndex(indexId, lazyIndex).catch((error) => {\n console.warn(`Failed to resolve single index:`, error)\n })\n }\n } else if (this.isIndexesResolved) {\n // Async loader but indexes are already resolved - resolve this one\n this.resolveSingleIndex(indexId, lazyIndex).catch((error) => {\n console.warn(`Failed to resolve single index:`, error)\n })\n }\n\n return new IndexProxy(indexId, lazyIndex)\n }\n\n /**\n * Resolve all lazy indexes (called when collection first syncs)\n */\n public async resolveAllIndexes(): Promise<void> {\n if (this.isIndexesResolved) return\n\n const resolutionPromises = Array.from(this.lazyIndexes.entries()).map(\n async ([indexId, lazyIndex]) => {\n const resolvedIndex = await lazyIndex.resolve()\n\n // Build index with current data\n resolvedIndex.build(this.state.entries())\n\n this.resolvedIndexes.set(indexId, resolvedIndex)\n return { indexId, resolvedIndex }\n },\n )\n\n await Promise.all(resolutionPromises)\n this.isIndexesResolved = true\n }\n\n /**\n * Resolve a single index immediately\n */\n private async resolveSingleIndex(\n indexId: number,\n lazyIndex: LazyIndexWrapper<TKey>,\n ): Promise<BaseIndex<TKey>> {\n const resolvedIndex = await lazyIndex.resolve()\n resolvedIndex.build(this.state.entries())\n this.resolvedIndexes.set(indexId, resolvedIndex)\n return resolvedIndex\n }\n\n /**\n * Get resolved indexes for query optimization\n */\n get indexes(): Map<number, BaseIndex<TKey>> {\n return this.resolvedIndexes\n }\n\n /**\n * Updates all indexes when the collection changes\n */\n public updateIndexes(changes: Array<ChangeMessage<TOutput, TKey>>): void {\n for (const index of this.resolvedIndexes.values()) {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n index.add(change.key, change.value)\n break\n case `update`:\n if (change.previousValue) {\n index.update(change.key, change.previousValue, change.value)\n } else {\n index.add(change.key, change.value)\n }\n break\n case `delete`:\n index.remove(change.key, change.value)\n break\n }\n }\n }\n }\n\n /**\n * Clean up the collection by stopping sync and clearing data\n * This can be called manually or automatically by garbage collection\n */\n public cleanup(): void {\n this.lazyIndexes.clear()\n this.resolvedIndexes.clear()\n }\n}\n"],"names":["createSingleRowRefProxy","toExpression","BTreeIndex","lazyIndex","LazyIndexWrapper","IndexProxy"],"mappings":";;;;;AAcO,MAAM,yBAKX;AAAA,EASA,cAAc;AALd,SAAO,kCAAkB,IAAA;AACzB,SAAO,sCAAsB,IAAA;AAC7B,SAAO,oBAAoB;AAC3B,SAAO,eAAe;AAAA,EAEP;AAAA,EAEf,QAAQ,MAGL;AACD,SAAK,QAAQ,KAAK;AAClB,SAAK,YAAY,KAAK;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKO,YACL,eACA,SAAkC,IAChB;AAClB,SAAK,UAAU,yBAAyB,aAAa;AAErD,UAAM,UAAU,EAAE,KAAK;AACvB,UAAM,oBAAoBA,SAAAA,wBAAA;AAC1B,UAAM,kBAAkB,cAAc,iBAAiB;AACvD,UAAM,aAAaC,SAAAA,aAAa,eAAe;AAG/C,UAAM,WAAW,OAAO,aAAcC,WAAAA;AAGtC,UAAMC,cAAY,IAAIC,UAAAA;AAAAA,MACpB;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA,OAAO;AAAA,MACP,KAAK,MAAM,QAAA;AAAA,IAAQ;AAGrB,SAAK,YAAY,IAAI,SAASD,WAAS;AAGvC,QAAK,aAAyBD,WAAAA,YAAY;AACxC,UAAI;AACF,cAAM,gBAAgBC,YAAU,YAAA;AAChC,aAAK,gBAAgB,IAAI,SAAS,aAAa;AAAA,MACjD,SAAS,OAAO;AACd,gBAAQ,KAAK,iCAAiC,KAAK;AAAA,MACrD;AAAA,IACF,WAAW,OAAO,aAAa,cAAc,SAAS,WAAW;AAE/D,UAAI;AACF,cAAM,gBAAgBA,YAAU,YAAA;AAChC,aAAK,gBAAgB,IAAI,SAAS,aAAa;AAAA,MACjD,QAAQ;AAEN,aAAK,mBAAmB,SAASA,WAAS,EAAE,MAAM,CAAC,UAAU;AAC3D,kBAAQ,KAAK,mCAAmC,KAAK;AAAA,QACvD,CAAC;AAAA,MACH;AAAA,IACF,WAAW,KAAK,mBAAmB;AAEjC,WAAK,mBAAmB,SAASA,WAAS,EAAE,MAAM,CAAC,UAAU;AAC3D,gBAAQ,KAAK,mCAAmC,KAAK;AAAA,MACvD,CAAC;AAAA,IACH;AAEA,WAAO,IAAIE,UAAAA,WAAW,SAASF,WAAS;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,oBAAmC;AAC9C,QAAI,KAAK,kBAAmB;AAE5B,UAAM,qBAAqB,MAAM,KAAK,KAAK,YAAY,QAAA,CAAS,EAAE;AAAA,MAChE,OAAO,CAAC,SAASA,UAAS,MAAM;AAC9B,cAAM,gBAAgB,MAAMA,WAAU,QAAA;AAGtC,sBAAc,MAAM,KAAK,MAAM,QAAA,CAAS;AAExC,aAAK,gBAAgB,IAAI,SAAS,aAAa;AAC/C,eAAO,EAAE,SAAS,cAAA;AAAA,MACpB;AAAA,IAAA;AAGF,UAAM,QAAQ,IAAI,kBAAkB;AACpC,SAAK,oBAAoB;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,mBACZ,SACAA,YAC0B;AAC1B,UAAM,gBAAgB,MAAMA,WAAU,QAAA;AACtC,kBAAc,MAAM,KAAK,MAAM,QAAA,CAAS;AACxC,SAAK,gBAAgB,IAAI,SAAS,aAAa;AAC/C,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAwC;AAC1C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKO,cAAc,SAAoD;AACvE,eAAW,SAAS,KAAK,gBAAgB,OAAA,GAAU;AACjD,iBAAW,UAAU,SAAS;AAC5B,gBAAQ,OAAO,MAAA;AAAA,UACb,KAAK;AACH,kBAAM,IAAI,OAAO,KAAK,OAAO,KAAK;AAClC;AAAA,UACF,KAAK;AACH,gBAAI,OAAO,eAAe;AACxB,oBAAM,OAAO,OAAO,KAAK,OAAO,eAAe,OAAO,KAAK;AAAA,YAC7D,OAAO;AACL,oBAAM,IAAI,OAAO,KAAK,OAAO,KAAK;AAAA,YACpC;AACA;AAAA,UACF,KAAK;AACH,kBAAM,OAAO,OAAO,KAAK,OAAO,KAAK;AACrC;AAAA,QAAA;AAAA,MAEN;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,UAAgB;AACrB,SAAK,YAAY,MAAA;AACjB,SAAK,gBAAgB,MAAA;AAAA,EACvB;AACF;;"}
|
|
1
|
+
{"version":3,"file":"indexes.cjs","sources":["../../../src/collection/indexes.ts"],"sourcesContent":["import {\n createSingleRowRefProxy,\n toExpression,\n} from '../query/builder/ref-proxy'\nimport { CollectionConfigurationError } from '../errors'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { BaseIndex, IndexConstructor } from '../indexes/base-index'\nimport type { ChangeMessage } from '../types'\nimport type { IndexOptions } from '../indexes/index-options'\nimport type { SingleRowRefProxy } from '../query/builder/ref-proxy'\nimport type { CollectionLifecycleManager } from './lifecycle'\nimport type { CollectionStateManager } from './state'\nimport type { BasicExpression } from '../query/ir'\nimport type {\n CollectionEventsManager,\n CollectionIndexMetadata,\n CollectionIndexResolverMetadata,\n CollectionIndexSerializableValue,\n} from './events'\n\nconst INDEX_SIGNATURE_VERSION = 1 as const\n\nfunction compareStringsCodePoint(left: string, right: string): number {\n if (left === right) {\n return 0\n }\n\n return left < right ? -1 : 1\n}\n\nfunction resolveResolverMetadata<TKey extends string | number>(\n resolver: IndexConstructor<TKey>,\n): CollectionIndexResolverMetadata {\n return {\n kind: `constructor`,\n ...(resolver.name ? { name: resolver.name } : {}),\n }\n}\n\nfunction toSerializableIndexValue(\n value: unknown,\n): CollectionIndexSerializableValue | undefined {\n if (value == null) {\n return value\n }\n\n switch (typeof value) {\n case `string`:\n case `boolean`:\n return value\n case `number`:\n return Number.isFinite(value) ? value : null\n case `bigint`:\n return { __type: `bigint`, value: value.toString() }\n case `function`:\n case `symbol`:\n // Function and symbol identity are process-local and not stable across runtimes.\n // Dropping them keeps signatures deterministic; we may skip index reuse, which is acceptable.\n return undefined\n case `undefined`:\n return undefined\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => toSerializableIndexValue(entry) ?? null)\n }\n\n if (value instanceof Date) {\n return {\n __type: `date`,\n value: value.toISOString(),\n }\n }\n\n if (value instanceof Set) {\n const serializedValues = Array.from(value)\n .map((entry) => toSerializableIndexValue(entry) ?? null)\n .sort((a, b) =>\n compareStringsCodePoint(\n stableStringifyCollectionIndexValue(a),\n stableStringifyCollectionIndexValue(b),\n ),\n )\n return {\n __type: `set`,\n values: serializedValues,\n }\n }\n\n if (value instanceof Map) {\n const serializedEntries = Array.from(value.entries())\n .map(([mapKey, mapValue]) => ({\n key: toSerializableIndexValue(mapKey) ?? null,\n value: toSerializableIndexValue(mapValue) ?? null,\n }))\n .sort((a, b) =>\n compareStringsCodePoint(\n stableStringifyCollectionIndexValue(a.key),\n stableStringifyCollectionIndexValue(b.key),\n ),\n )\n\n return {\n __type: `map`,\n entries: serializedEntries,\n }\n }\n\n if (value instanceof RegExp) {\n return {\n __type: `regexp`,\n value: value.toString(),\n }\n }\n\n const serializedObject: Record<string, CollectionIndexSerializableValue> = {}\n const entries = Object.entries(value as Record<string, unknown>).sort(\n ([leftKey], [rightKey]) => compareStringsCodePoint(leftKey, rightKey),\n )\n\n for (const [key, entryValue] of entries) {\n const serializedEntry = toSerializableIndexValue(entryValue)\n if (serializedEntry !== undefined) {\n serializedObject[key] = serializedEntry\n }\n }\n\n return serializedObject\n}\n\nfunction stableStringifyCollectionIndexValue(\n value: CollectionIndexSerializableValue,\n): string {\n if (value === null) {\n return `null`\n }\n\n if (Array.isArray(value)) {\n return `[${value.map(stableStringifyCollectionIndexValue).join(`,`)}]`\n }\n\n if (typeof value !== `object`) {\n return JSON.stringify(value)\n }\n\n const sortedKeys = Object.keys(value).sort((left, right) =>\n compareStringsCodePoint(left, right),\n )\n const serializedEntries = sortedKeys.map(\n (key) =>\n `${JSON.stringify(key)}:${stableStringifyCollectionIndexValue(value[key]!)}`,\n )\n return `{${serializedEntries.join(`,`)}}`\n}\n\nfunction createCollectionIndexMetadata<TKey extends string | number>(\n indexId: number,\n expression: BasicExpression,\n name: string | undefined,\n resolver: IndexConstructor<TKey>,\n options: unknown,\n): CollectionIndexMetadata {\n const resolverMetadata = resolveResolverMetadata(resolver)\n const serializedExpression = toSerializableIndexValue(expression) ?? null\n const serializedOptions = toSerializableIndexValue(options)\n const signatureInput = toSerializableIndexValue({\n signatureVersion: INDEX_SIGNATURE_VERSION,\n expression: serializedExpression,\n options: serializedOptions ?? null,\n })\n const normalizedSignatureInput = signatureInput ?? null\n const signature = stableStringifyCollectionIndexValue(\n normalizedSignatureInput,\n )\n\n return {\n signatureVersion: INDEX_SIGNATURE_VERSION,\n signature,\n indexId,\n name,\n expression,\n resolver: resolverMetadata,\n ...(serializedOptions === undefined ? {} : { options: serializedOptions }),\n }\n}\n\nfunction cloneSerializableIndexValue(\n value: CollectionIndexSerializableValue,\n): CollectionIndexSerializableValue {\n if (value === null || typeof value !== `object`) {\n return value\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => cloneSerializableIndexValue(entry))\n }\n\n const cloned: Record<string, CollectionIndexSerializableValue> = {}\n for (const [key, entryValue] of Object.entries(value)) {\n cloned[key] = cloneSerializableIndexValue(entryValue)\n }\n return cloned\n}\n\nfunction cloneExpression(expression: BasicExpression): BasicExpression {\n return JSON.parse(JSON.stringify(expression)) as BasicExpression\n}\n\nexport class CollectionIndexesManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n private defaultIndexType: IndexConstructor<TKey> | undefined\n private events!: CollectionEventsManager\n\n public indexes = new Map<number, BaseIndex<TKey>>()\n public indexMetadata = new Map<number, CollectionIndexMetadata>()\n public indexCounter = 0\n\n constructor() {}\n\n setDeps(deps: {\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>\n defaultIndexType?: IndexConstructor<TKey>\n events: CollectionEventsManager\n }) {\n this.state = deps.state\n this.lifecycle = deps.lifecycle\n this.defaultIndexType = deps.defaultIndexType\n this.events = deps.events\n }\n\n /**\n * Creates an index on a collection for faster queries.\n *\n * @example\n * ```ts\n * // With explicit index type (recommended for tree-shaking)\n * import { BasicIndex } from '@tanstack/db'\n * collection.createIndex((row) => row.userId, { indexType: BasicIndex })\n *\n * // With collection's default index type\n * collection.createIndex((row) => row.userId)\n * ```\n */\n public createIndex<TIndexType extends IndexConstructor<TKey>>(\n indexCallback: (row: SingleRowRefProxy<TOutput>) => any,\n config: IndexOptions<TIndexType> = {},\n ): BaseIndex<TKey> {\n this.lifecycle.validateCollectionUsable(`createIndex`)\n\n const indexId = ++this.indexCounter\n const singleRowRefProxy = createSingleRowRefProxy<TOutput>()\n const indexExpression = indexCallback(singleRowRefProxy)\n const expression = toExpression(indexExpression)\n\n // Use provided index type, or fall back to collection's default\n const IndexType = config.indexType ?? this.defaultIndexType\n if (!IndexType) {\n throw new CollectionConfigurationError(\n `No index type specified and no defaultIndexType set on collection. ` +\n `Either pass indexType in config, or set defaultIndexType on the collection:\\n` +\n ` import { BasicIndex } from '@tanstack/db'\\n` +\n ` createCollection({ defaultIndexType: BasicIndex, ... })`,\n )\n }\n\n // Create index synchronously\n const index = new IndexType(\n indexId,\n expression,\n config.name,\n config.options,\n )\n\n // Build with current data\n index.build(this.state.entries())\n\n this.indexes.set(indexId, index)\n\n // Track metadata and emit event\n const metadata = createCollectionIndexMetadata(\n indexId,\n expression,\n config.name,\n IndexType,\n config.options,\n )\n this.indexMetadata.set(indexId, metadata)\n this.events.emitIndexAdded(metadata)\n\n return index\n }\n\n /**\n * Removes an index from this collection.\n * Returns true when an index existed and was removed, false otherwise.\n */\n public removeIndex(indexOrId: BaseIndex<TKey> | number): boolean {\n this.lifecycle.validateCollectionUsable(`removeIndex`)\n\n const indexId = typeof indexOrId === `number` ? indexOrId : indexOrId.id\n const index = this.indexes.get(indexId)\n if (!index) {\n return false\n }\n\n if (typeof indexOrId !== `number` && index !== indexOrId) {\n // Passed a different index instance with the same id — do not remove.\n return false\n }\n\n this.indexes.delete(indexId)\n\n const metadata = this.indexMetadata.get(indexId)\n this.indexMetadata.delete(indexId)\n if (metadata) {\n this.events.emitIndexRemoved(metadata)\n }\n\n return true\n }\n\n /**\n * Returns a sorted snapshot of index metadata.\n * This allows persisted wrappers to bootstrap from indexes that were created\n * before they attached lifecycle listeners.\n */\n public getIndexMetadataSnapshot(): Array<CollectionIndexMetadata> {\n return Array.from(this.indexMetadata.values())\n .sort((left, right) => left.indexId - right.indexId)\n .map((metadata) => ({\n ...metadata,\n expression: cloneExpression(metadata.expression),\n resolver: { ...metadata.resolver },\n ...(metadata.options === undefined\n ? {}\n : { options: cloneSerializableIndexValue(metadata.options) }),\n }))\n }\n\n /**\n * Updates all indexes when the collection changes\n */\n public updateIndexes(changes: Array<ChangeMessage<TOutput, TKey>>): void {\n for (const index of this.indexes.values()) {\n for (const change of changes) {\n switch (change.type) {\n case `insert`:\n index.add(change.key, change.value)\n break\n case `update`:\n if (change.previousValue) {\n index.update(change.key, change.previousValue, change.value)\n } else {\n index.add(change.key, change.value)\n }\n break\n case `delete`:\n index.remove(change.key, change.value)\n break\n }\n }\n }\n }\n\n /**\n * Clean up indexes\n */\n public cleanup(): void {\n this.indexes.clear()\n this.indexMetadata.clear()\n }\n}\n"],"names":["createSingleRowRefProxy","toExpression","CollectionConfigurationError"],"mappings":"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"}
|
|
@@ -1,47 +1,57 @@
|
|
|
1
|
-
import { IndexProxy, LazyIndexWrapper } from '../indexes/lazy-index.cjs';
|
|
2
|
-
import { BTreeIndex } from '../indexes/btree-index.cjs';
|
|
3
1
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
|
-
import { BaseIndex,
|
|
2
|
+
import { BaseIndex, IndexConstructor } from '../indexes/base-index.cjs';
|
|
5
3
|
import { ChangeMessage } from '../types.cjs';
|
|
6
4
|
import { IndexOptions } from '../indexes/index-options.cjs';
|
|
7
5
|
import { SingleRowRefProxy } from '../query/builder/ref-proxy.cjs';
|
|
8
6
|
import { CollectionLifecycleManager } from './lifecycle.cjs';
|
|
9
7
|
import { CollectionStateManager } from './state.cjs';
|
|
8
|
+
import { CollectionEventsManager, CollectionIndexMetadata } from './events.cjs';
|
|
10
9
|
export declare class CollectionIndexesManager<TOutput extends object = Record<string, unknown>, TKey extends string | number = string | number, TSchema extends StandardSchemaV1 = StandardSchemaV1, TInput extends object = TOutput> {
|
|
11
10
|
private lifecycle;
|
|
12
11
|
private state;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
private defaultIndexType;
|
|
13
|
+
private events;
|
|
14
|
+
indexes: Map<number, BaseIndex<TKey>>;
|
|
15
|
+
indexMetadata: Map<number, CollectionIndexMetadata>;
|
|
16
16
|
indexCounter: number;
|
|
17
17
|
constructor();
|
|
18
18
|
setDeps(deps: {
|
|
19
19
|
state: CollectionStateManager<TOutput, TKey, TSchema, TInput>;
|
|
20
20
|
lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>;
|
|
21
|
+
defaultIndexType?: IndexConstructor<TKey>;
|
|
22
|
+
events: CollectionEventsManager;
|
|
21
23
|
}): void;
|
|
22
24
|
/**
|
|
23
25
|
* Creates an index on a collection for faster queries.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* // With explicit index type (recommended for tree-shaking)
|
|
30
|
+
* import { BasicIndex } from '@tanstack/db'
|
|
31
|
+
* collection.createIndex((row) => row.userId, { indexType: BasicIndex })
|
|
32
|
+
*
|
|
33
|
+
* // With collection's default index type
|
|
34
|
+
* collection.createIndex((row) => row.userId)
|
|
35
|
+
* ```
|
|
24
36
|
*/
|
|
25
|
-
createIndex<
|
|
37
|
+
createIndex<TIndexType extends IndexConstructor<TKey>>(indexCallback: (row: SingleRowRefProxy<TOutput>) => any, config?: IndexOptions<TIndexType>): BaseIndex<TKey>;
|
|
26
38
|
/**
|
|
27
|
-
*
|
|
39
|
+
* Removes an index from this collection.
|
|
40
|
+
* Returns true when an index existed and was removed, false otherwise.
|
|
28
41
|
*/
|
|
29
|
-
|
|
42
|
+
removeIndex(indexOrId: BaseIndex<TKey> | number): boolean;
|
|
30
43
|
/**
|
|
31
|
-
*
|
|
44
|
+
* Returns a sorted snapshot of index metadata.
|
|
45
|
+
* This allows persisted wrappers to bootstrap from indexes that were created
|
|
46
|
+
* before they attached lifecycle listeners.
|
|
32
47
|
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Get resolved indexes for query optimization
|
|
36
|
-
*/
|
|
37
|
-
get indexes(): Map<number, BaseIndex<TKey>>;
|
|
48
|
+
getIndexMetadataSnapshot(): Array<CollectionIndexMetadata>;
|
|
38
49
|
/**
|
|
39
50
|
* Updates all indexes when the collection changes
|
|
40
51
|
*/
|
|
41
52
|
updateIndexes(changes: Array<ChangeMessage<TOutput, TKey>>): void;
|
|
42
53
|
/**
|
|
43
|
-
* Clean up
|
|
44
|
-
* This can be called manually or automatically by garbage collection
|
|
54
|
+
* Clean up indexes
|
|
45
55
|
*/
|
|
46
56
|
cleanup(): void;
|
|
47
57
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const errors = require("../errors.cjs");
|
|
4
4
|
const browserPolyfills = require("../utils/browser-polyfills.cjs");
|
|
5
|
+
const cleanupQueue = require("./cleanup-queue.cjs");
|
|
5
6
|
class CollectionLifecycleManager {
|
|
6
7
|
/**
|
|
7
8
|
* Creates a new CollectionLifecycleManager instance
|
|
@@ -11,7 +12,6 @@ class CollectionLifecycleManager {
|
|
|
11
12
|
this.hasBeenReady = false;
|
|
12
13
|
this.hasReceivedFirstCommit = false;
|
|
13
14
|
this.onFirstReadyCallbacks = [];
|
|
14
|
-
this.gcTimeoutId = null;
|
|
15
15
|
this.idleCallbackId = null;
|
|
16
16
|
this.config = config;
|
|
17
17
|
this.id = id;
|
|
@@ -54,14 +54,6 @@ class CollectionLifecycleManager {
|
|
|
54
54
|
this.validateStatusTransition(this.status, newStatus);
|
|
55
55
|
const previousStatus = this.status;
|
|
56
56
|
this.status = newStatus;
|
|
57
|
-
if (newStatus === `ready` && !this.indexes.isIndexesResolved) {
|
|
58
|
-
this.indexes.resolveAllIndexes().catch((error) => {
|
|
59
|
-
console.warn(
|
|
60
|
-
`${this.config.id ? `[${this.config.id}] ` : ``}Failed to resolve indexes:`,
|
|
61
|
-
error
|
|
62
|
-
);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
57
|
this.events.emitStatusChange(newStatus, previousStatus);
|
|
66
58
|
}
|
|
67
59
|
/**
|
|
@@ -106,28 +98,22 @@ class CollectionLifecycleManager {
|
|
|
106
98
|
* Called when the collection becomes inactive (no subscribers)
|
|
107
99
|
*/
|
|
108
100
|
startGCTimer() {
|
|
109
|
-
if (this.gcTimeoutId) {
|
|
110
|
-
clearTimeout(this.gcTimeoutId);
|
|
111
|
-
}
|
|
112
101
|
const gcTime = this.config.gcTime ?? 3e5;
|
|
113
102
|
if (gcTime <= 0 || !Number.isFinite(gcTime)) {
|
|
114
103
|
return;
|
|
115
104
|
}
|
|
116
|
-
this
|
|
105
|
+
cleanupQueue.CleanupQueue.getInstance().schedule(this, gcTime, () => {
|
|
117
106
|
if (this.changes.activeSubscribersCount === 0) {
|
|
118
107
|
this.scheduleIdleCleanup();
|
|
119
108
|
}
|
|
120
|
-
}
|
|
109
|
+
});
|
|
121
110
|
}
|
|
122
111
|
/**
|
|
123
112
|
* Cancel the garbage collection timer
|
|
124
113
|
* Called when the collection becomes active again
|
|
125
114
|
*/
|
|
126
115
|
cancelGCTimer() {
|
|
127
|
-
|
|
128
|
-
clearTimeout(this.gcTimeoutId);
|
|
129
|
-
this.gcTimeoutId = null;
|
|
130
|
-
}
|
|
116
|
+
cleanupQueue.CleanupQueue.getInstance().cancel(this);
|
|
131
117
|
if (this.idleCallbackId !== null) {
|
|
132
118
|
browserPolyfills.safeCancelIdleCallback(this.idleCallbackId);
|
|
133
119
|
this.idleCallbackId = null;
|
|
@@ -166,10 +152,7 @@ class CollectionLifecycleManager {
|
|
|
166
152
|
this.state.cleanup();
|
|
167
153
|
this.changes.cleanup();
|
|
168
154
|
this.indexes.cleanup();
|
|
169
|
-
|
|
170
|
-
clearTimeout(this.gcTimeoutId);
|
|
171
|
-
this.gcTimeoutId = null;
|
|
172
|
-
}
|
|
155
|
+
cleanupQueue.CleanupQueue.getInstance().cancel(this);
|
|
173
156
|
this.hasBeenReady = false;
|
|
174
157
|
const callbacks = [...this.onFirstReadyCallbacks];
|
|
175
158
|
this.onFirstReadyCallbacks = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.cjs","sources":["../../../src/collection/lifecycle.ts"],"sourcesContent":["import {\n CollectionInErrorStateError,\n CollectionStateError,\n InvalidCollectionStatusTransitionError,\n} from '../errors'\nimport {\n safeCancelIdleCallback,\n safeRequestIdleCallback,\n} from '../utils/browser-polyfills'\nimport type { IdleCallbackDeadline } from '../utils/browser-polyfills'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { CollectionConfig, CollectionStatus } from '../types'\nimport type { CollectionEventsManager } from './events'\nimport type { CollectionIndexesManager } from './indexes'\nimport type { CollectionChangesManager } from './changes'\nimport type { CollectionSyncManager } from './sync'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionLifecycleManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private config: CollectionConfig<TOutput, TKey, TSchema>\n private id: string\n private indexes!: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n private events!: CollectionEventsManager\n private changes!: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n public status: CollectionStatus = `idle`\n public hasBeenReady = false\n public hasReceivedFirstCommit = false\n public onFirstReadyCallbacks: Array<() => void> = []\n public gcTimeoutId: ReturnType<typeof setTimeout> | null = null\n private idleCallbackId: number | null = null\n\n /**\n * Creates a new CollectionLifecycleManager instance\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n this.config = config\n this.id = id\n }\n\n setDeps(deps: {\n indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n }) {\n this.indexes = deps.indexes\n this.events = deps.events\n this.changes = deps.changes\n this.sync = deps.sync\n this.state = deps.state\n }\n\n /**\n * Validates state transitions to prevent invalid status changes\n */\n public validateStatusTransition(\n from: CollectionStatus,\n to: CollectionStatus,\n ): void {\n if (from === to) {\n // Allow same state transitions\n return\n }\n const validTransitions: Record<\n CollectionStatus,\n Array<CollectionStatus>\n > = {\n idle: [`loading`, `error`, `cleaned-up`],\n loading: [`ready`, `error`, `cleaned-up`],\n ready: [`cleaned-up`, `error`],\n error: [`cleaned-up`, `idle`],\n 'cleaned-up': [`loading`, `error`],\n }\n\n if (!validTransitions[from].includes(to)) {\n throw new InvalidCollectionStatusTransitionError(from, to, this.id)\n }\n }\n\n /**\n * Safely update the collection status with validation\n * @private\n */\n public setStatus(\n newStatus: CollectionStatus,\n allowReady: boolean = false,\n ): void {\n if (newStatus === `ready` && !allowReady) {\n // setStatus('ready') is an internal method that should not be called directly\n // Instead, use markReady to transition to ready triggering the necessary events\n // and side effects.\n throw new CollectionStateError(\n `You can't directly call \"setStatus('ready'). You must use markReady instead.`,\n )\n }\n this.validateStatusTransition(this.status, newStatus)\n const previousStatus = this.status\n this.status = newStatus\n\n // Resolve indexes when collection becomes ready\n if (newStatus === `ready` && !this.indexes.isIndexesResolved) {\n // Resolve indexes asynchronously without blocking\n this.indexes.resolveAllIndexes().catch((error) => {\n console.warn(\n `${this.config.id ? `[${this.config.id}] ` : ``}Failed to resolve indexes:`,\n error,\n )\n })\n }\n\n // Emit event\n this.events.emitStatusChange(newStatus, previousStatus)\n }\n\n /**\n * Validates that the collection is in a usable state for data operations\n * @private\n */\n public validateCollectionUsable(operation: string): void {\n switch (this.status) {\n case `error`:\n throw new CollectionInErrorStateError(operation, this.id)\n case `cleaned-up`:\n // Automatically restart the collection when operations are called on cleaned-up collections\n this.sync.startSync()\n break\n }\n }\n\n /**\n * Mark the collection as ready for use\n * This is called by sync implementations to explicitly signal that the collection is ready,\n * providing a more intuitive alternative to using commits for readiness signaling\n * @private - Should only be called by sync implementations\n */\n public markReady(): void {\n this.validateStatusTransition(this.status, `ready`)\n // Can transition to ready from loading state\n if (this.status === `loading`) {\n this.setStatus(`ready`, true)\n\n // Call any registered first ready callbacks (only on first time becoming ready)\n if (!this.hasBeenReady) {\n this.hasBeenReady = true\n\n // Also mark as having received first commit for backwards compatibility\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n }\n\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n // Notify dependents when markReady is called, after status is set\n // This ensures live queries get notified when their dependencies become ready\n if (this.changes.changeSubscriptions.size > 0) {\n this.changes.emitEmptyReadyEvent()\n }\n }\n }\n\n /**\n * Start the garbage collection timer\n * Called when the collection becomes inactive (no subscribers)\n */\n public startGCTimer(): void {\n if (this.gcTimeoutId) {\n clearTimeout(this.gcTimeoutId)\n }\n\n const gcTime = this.config.gcTime ?? 300000 // 5 minutes default\n\n // If gcTime is 0, negative, or non-finite (Infinity, -Infinity, NaN), GC is disabled.\n // Note: setTimeout with Infinity coerces to 0 via ToInt32, causing immediate GC,\n // so we must explicitly check for non-finite values here.\n if (gcTime <= 0 || !Number.isFinite(gcTime)) {\n return\n }\n\n this.gcTimeoutId = setTimeout(() => {\n if (this.changes.activeSubscribersCount === 0) {\n // Schedule cleanup during idle time to avoid blocking the UI thread\n this.scheduleIdleCleanup()\n }\n }, gcTime)\n }\n\n /**\n * Cancel the garbage collection timer\n * Called when the collection becomes active again\n */\n public cancelGCTimer(): void {\n if (this.gcTimeoutId) {\n clearTimeout(this.gcTimeoutId)\n this.gcTimeoutId = null\n }\n // Also cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n }\n\n /**\n * Schedule cleanup to run during browser idle time\n * This prevents blocking the UI thread during cleanup operations\n */\n private scheduleIdleCleanup(): void {\n // Cancel any existing idle callback\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n }\n\n // Schedule cleanup with a timeout of 1 second\n // This ensures cleanup happens even if the browser is busy\n this.idleCallbackId = safeRequestIdleCallback(\n (deadline) => {\n // Perform cleanup if we still have no subscribers\n if (this.changes.activeSubscribersCount === 0) {\n const cleanupCompleted = this.performCleanup(deadline)\n // Only clear the callback ID if cleanup actually completed\n if (cleanupCompleted) {\n this.idleCallbackId = null\n }\n } else {\n // No need to cleanup, clear the callback ID\n this.idleCallbackId = null\n }\n },\n { timeout: 1000 },\n )\n }\n\n /**\n * Perform cleanup operations, optionally in chunks during idle time\n * @returns true if cleanup was completed, false if it was rescheduled\n */\n private performCleanup(deadline?: IdleCallbackDeadline): boolean {\n // If we have a deadline, we can potentially split cleanup into chunks\n // For now, we'll do all cleanup at once but check if we have time\n const hasTime =\n !deadline || deadline.timeRemaining() > 0 || deadline.didTimeout\n\n if (hasTime) {\n // Perform all cleanup operations except events\n this.sync.cleanup()\n this.state.cleanup()\n this.changes.cleanup()\n this.indexes.cleanup()\n\n if (this.gcTimeoutId) {\n clearTimeout(this.gcTimeoutId)\n this.gcTimeoutId = null\n }\n\n this.hasBeenReady = false\n\n // Call any pending onFirstReady callbacks before clearing them.\n // This ensures preload() promises resolve during cleanup instead of hanging.\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => {\n try {\n callback()\n } catch (error) {\n console.error(\n `${this.config.id ? `[${this.config.id}] ` : ``}Error in onFirstReady callback during cleanup:`,\n error,\n )\n }\n })\n\n // Set status to cleaned-up after everything is cleaned up\n // This fires the status:change event to notify listeners\n this.setStatus(`cleaned-up`)\n\n // Finally, cleanup event handlers after the event has been fired\n this.events.cleanup()\n\n return true\n } else {\n // If we don't have time, reschedule for the next idle period\n this.scheduleIdleCleanup()\n return false\n }\n }\n\n /**\n * Register a callback to be executed when the collection first becomes ready\n * Useful for preloading collections\n * @param callback Function to call when the collection first becomes ready\n */\n public onFirstReady(callback: () => void): void {\n // If already ready, call immediately\n if (this.hasBeenReady) {\n callback()\n return\n }\n\n this.onFirstReadyCallbacks.push(callback)\n }\n\n public cleanup(): void {\n // Cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n\n // Perform cleanup immediately (used when explicitly called)\n this.performCleanup()\n }\n}\n"],"names":["InvalidCollectionStatusTransitionError","CollectionStateError","CollectionInErrorStateError","safeCancelIdleCallback","safeRequestIdleCallback"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecycle.cjs","sources":["../../../src/collection/lifecycle.ts"],"sourcesContent":["import {\n CollectionInErrorStateError,\n CollectionStateError,\n InvalidCollectionStatusTransitionError,\n} from '../errors'\nimport {\n safeCancelIdleCallback,\n safeRequestIdleCallback,\n} from '../utils/browser-polyfills'\nimport { CleanupQueue } from './cleanup-queue'\nimport type { IdleCallbackDeadline } from '../utils/browser-polyfills'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { CollectionConfig, CollectionStatus } from '../types'\nimport type { CollectionEventsManager } from './events'\nimport type { CollectionIndexesManager } from './indexes'\nimport type { CollectionChangesManager } from './changes'\nimport type { CollectionSyncManager } from './sync'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionLifecycleManager<\n TOutput extends object = Record<string, unknown>,\n TKey extends string | number = string | number,\n TSchema extends StandardSchemaV1 = StandardSchemaV1,\n TInput extends object = TOutput,\n> {\n private config: CollectionConfig<TOutput, TKey, TSchema>\n private id: string\n private indexes!: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n private events!: CollectionEventsManager\n private changes!: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n public status: CollectionStatus = `idle`\n public hasBeenReady = false\n public hasReceivedFirstCommit = false\n public onFirstReadyCallbacks: Array<() => void> = []\n private idleCallbackId: number | null = null\n\n /**\n * Creates a new CollectionLifecycleManager instance\n */\n constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n this.config = config\n this.id = id\n }\n\n setDeps(deps: {\n indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n events: CollectionEventsManager\n changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n }) {\n this.indexes = deps.indexes\n this.events = deps.events\n this.changes = deps.changes\n this.sync = deps.sync\n this.state = deps.state\n }\n\n /**\n * Validates state transitions to prevent invalid status changes\n */\n public validateStatusTransition(\n from: CollectionStatus,\n to: CollectionStatus,\n ): void {\n if (from === to) {\n // Allow same state transitions\n return\n }\n const validTransitions: Record<\n CollectionStatus,\n Array<CollectionStatus>\n > = {\n idle: [`loading`, `error`, `cleaned-up`],\n loading: [`ready`, `error`, `cleaned-up`],\n ready: [`cleaned-up`, `error`],\n error: [`cleaned-up`, `idle`],\n 'cleaned-up': [`loading`, `error`],\n }\n\n if (!validTransitions[from].includes(to)) {\n throw new InvalidCollectionStatusTransitionError(from, to, this.id)\n }\n }\n\n /**\n * Safely update the collection status with validation\n * @private\n */\n public setStatus(\n newStatus: CollectionStatus,\n allowReady: boolean = false,\n ): void {\n if (newStatus === `ready` && !allowReady) {\n // setStatus('ready') is an internal method that should not be called directly\n // Instead, use markReady to transition to ready triggering the necessary events\n // and side effects.\n throw new CollectionStateError(\n `You can't directly call \"setStatus('ready'). You must use markReady instead.`,\n )\n }\n this.validateStatusTransition(this.status, newStatus)\n const previousStatus = this.status\n this.status = newStatus\n\n // Emit event\n this.events.emitStatusChange(newStatus, previousStatus)\n }\n\n /**\n * Validates that the collection is in a usable state for data operations\n * @private\n */\n public validateCollectionUsable(operation: string): void {\n switch (this.status) {\n case `error`:\n throw new CollectionInErrorStateError(operation, this.id)\n case `cleaned-up`:\n // Automatically restart the collection when operations are called on cleaned-up collections\n this.sync.startSync()\n break\n }\n }\n\n /**\n * Mark the collection as ready for use\n * This is called by sync implementations to explicitly signal that the collection is ready,\n * providing a more intuitive alternative to using commits for readiness signaling\n * @private - Should only be called by sync implementations\n */\n public markReady(): void {\n this.validateStatusTransition(this.status, `ready`)\n // Can transition to ready from loading state\n if (this.status === `loading`) {\n this.setStatus(`ready`, true)\n\n // Call any registered first ready callbacks (only on first time becoming ready)\n if (!this.hasBeenReady) {\n this.hasBeenReady = true\n\n // Also mark as having received first commit for backwards compatibility\n if (!this.hasReceivedFirstCommit) {\n this.hasReceivedFirstCommit = true\n }\n\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => callback())\n }\n // Notify dependents when markReady is called, after status is set\n // This ensures live queries get notified when their dependencies become ready\n if (this.changes.changeSubscriptions.size > 0) {\n this.changes.emitEmptyReadyEvent()\n }\n }\n }\n\n /**\n * Start the garbage collection timer\n * Called when the collection becomes inactive (no subscribers)\n */\n public startGCTimer(): void {\n const gcTime = this.config.gcTime ?? 300000 // 5 minutes default\n\n // If gcTime is 0, negative, or non-finite (Infinity, -Infinity, NaN), GC is disabled.\n // Note: setTimeout with Infinity coerces to 0 via ToInt32, causing immediate GC,\n // so we must explicitly check for non-finite values here.\n if (gcTime <= 0 || !Number.isFinite(gcTime)) {\n return\n }\n\n CleanupQueue.getInstance().schedule(this, gcTime, () => {\n if (this.changes.activeSubscribersCount === 0) {\n // Schedule cleanup during idle time to avoid blocking the UI thread\n this.scheduleIdleCleanup()\n }\n })\n }\n\n /**\n * Cancel the garbage collection timer\n * Called when the collection becomes active again\n */\n public cancelGCTimer(): void {\n CleanupQueue.getInstance().cancel(this)\n // Also cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n }\n\n /**\n * Schedule cleanup to run during browser idle time\n * This prevents blocking the UI thread during cleanup operations\n */\n private scheduleIdleCleanup(): void {\n // Cancel any existing idle callback\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n }\n\n // Schedule cleanup with a timeout of 1 second\n // This ensures cleanup happens even if the browser is busy\n this.idleCallbackId = safeRequestIdleCallback(\n (deadline) => {\n // Perform cleanup if we still have no subscribers\n if (this.changes.activeSubscribersCount === 0) {\n const cleanupCompleted = this.performCleanup(deadline)\n // Only clear the callback ID if cleanup actually completed\n if (cleanupCompleted) {\n this.idleCallbackId = null\n }\n } else {\n // No need to cleanup, clear the callback ID\n this.idleCallbackId = null\n }\n },\n { timeout: 1000 },\n )\n }\n\n /**\n * Perform cleanup operations, optionally in chunks during idle time\n * @returns true if cleanup was completed, false if it was rescheduled\n */\n private performCleanup(deadline?: IdleCallbackDeadline): boolean {\n // If we have a deadline, we can potentially split cleanup into chunks\n // For now, we'll do all cleanup at once but check if we have time\n const hasTime =\n !deadline || deadline.timeRemaining() > 0 || deadline.didTimeout\n\n if (hasTime) {\n // Perform all cleanup operations except events\n this.sync.cleanup()\n this.state.cleanup()\n this.changes.cleanup()\n this.indexes.cleanup()\n\n CleanupQueue.getInstance().cancel(this)\n\n this.hasBeenReady = false\n\n // Call any pending onFirstReady callbacks before clearing them.\n // This ensures preload() promises resolve during cleanup instead of hanging.\n const callbacks = [...this.onFirstReadyCallbacks]\n this.onFirstReadyCallbacks = []\n callbacks.forEach((callback) => {\n try {\n callback()\n } catch (error) {\n console.error(\n `${this.config.id ? `[${this.config.id}] ` : ``}Error in onFirstReady callback during cleanup:`,\n error,\n )\n }\n })\n\n // Set status to cleaned-up after everything is cleaned up\n // This fires the status:change event to notify listeners\n this.setStatus(`cleaned-up`)\n\n // Finally, cleanup event handlers after the event has been fired\n this.events.cleanup()\n\n return true\n } else {\n // If we don't have time, reschedule for the next idle period\n this.scheduleIdleCleanup()\n return false\n }\n }\n\n /**\n * Register a callback to be executed when the collection first becomes ready\n * Useful for preloading collections\n * @param callback Function to call when the collection first becomes ready\n */\n public onFirstReady(callback: () => void): void {\n // If already ready, call immediately\n if (this.hasBeenReady) {\n callback()\n return\n }\n\n this.onFirstReadyCallbacks.push(callback)\n }\n\n public cleanup(): void {\n // Cancel any pending idle cleanup\n if (this.idleCallbackId !== null) {\n safeCancelIdleCallback(this.idleCallbackId)\n this.idleCallbackId = null\n }\n\n // Perform cleanup immediately (used when explicitly called)\n this.performCleanup()\n }\n}\n"],"names":["InvalidCollectionStatusTransitionError","CollectionStateError","CollectionInErrorStateError","CleanupQueue","safeCancelIdleCallback","safeRequestIdleCallback"],"mappings":"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"}
|
|
@@ -17,7 +17,6 @@ export declare class CollectionLifecycleManager<TOutput extends object = Record<
|
|
|
17
17
|
hasBeenReady: boolean;
|
|
18
18
|
hasReceivedFirstCommit: boolean;
|
|
19
19
|
onFirstReadyCallbacks: Array<() => void>;
|
|
20
|
-
gcTimeoutId: ReturnType<typeof setTimeout> | null;
|
|
21
20
|
private idleCallbackId;
|
|
22
21
|
/**
|
|
23
22
|
* Creates a new CollectionLifecycleManager instance
|