@tanstack/db 0.5.33 → 0.6.1
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 +22 -4
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +11 -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/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 +501 -5
- 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/types.d.cts +3 -3
- package/dist/cjs/query/live/utils.cjs +43 -3
- package/dist/cjs/query/live/utils.cjs.map +1 -1
- package/dist/cjs/query/live/utils.d.cts +1 -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 +11 -3
- package/dist/esm/index.js +25 -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/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 +503 -7
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/types.d.ts +3 -3
- package/dist/esm/query/live/utils.d.ts +1 -0
- package/dist/esm/query/live/utils.js +43 -3
- package/dist/esm/query/live/utils.js.map +1 -1
- 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 +4 -3
- package/skills/db-core/SKILL.md +4 -2
- package/skills/db-core/collection-setup/SKILL.md +30 -11
- package/skills/db-core/collection-setup/references/electric-adapter.md +1 -1
- package/skills/db-core/collection-setup/references/powersync-adapter.md +4 -0
- package/skills/db-core/collection-setup/references/query-adapter.md +32 -0
- package/skills/db-core/custom-adapter/SKILL.md +58 -9
- package/skills/db-core/live-queries/SKILL.md +162 -2
- package/skills/db-core/mutations-optimistic/SKILL.md +1 -1
- package/skills/db-core/persistence/SKILL.md +241 -0
- package/skills/meta-framework/SKILL.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 +46 -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/index.ts +7 -0
- package/src/query/ir.ts +23 -2
- package/src/query/live/collection-config-builder.ts +809 -9
- package/src/query/live/types.ts +10 -4
- package/src/query/live/utils.ts +64 -3
- 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,16 +1,210 @@
|
|
|
1
|
-
import { IndexProxy, LazyIndexWrapper } from '../indexes/lazy-index'
|
|
2
1
|
import {
|
|
3
2
|
createSingleRowRefProxy,
|
|
4
3
|
toExpression,
|
|
5
4
|
} from '../query/builder/ref-proxy'
|
|
6
|
-
import {
|
|
5
|
+
import { CollectionConfigurationError } from '../errors'
|
|
7
6
|
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
8
|
-
import type { BaseIndex,
|
|
7
|
+
import type { BaseIndex, IndexConstructor } from '../indexes/base-index'
|
|
9
8
|
import type { ChangeMessage } from '../types'
|
|
10
9
|
import type { IndexOptions } from '../indexes/index-options'
|
|
11
10
|
import type { SingleRowRefProxy } from '../query/builder/ref-proxy'
|
|
12
11
|
import type { CollectionLifecycleManager } from './lifecycle'
|
|
13
12
|
import type { CollectionStateManager } from './state'
|
|
13
|
+
import type { BasicExpression } from '../query/ir'
|
|
14
|
+
import type {
|
|
15
|
+
CollectionEventsManager,
|
|
16
|
+
CollectionIndexMetadata,
|
|
17
|
+
CollectionIndexResolverMetadata,
|
|
18
|
+
CollectionIndexSerializableValue,
|
|
19
|
+
} from './events'
|
|
20
|
+
|
|
21
|
+
const INDEX_SIGNATURE_VERSION = 1 as const
|
|
22
|
+
|
|
23
|
+
function compareStringsCodePoint(left: string, right: string): number {
|
|
24
|
+
if (left === right) {
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return left < right ? -1 : 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveResolverMetadata<TKey extends string | number>(
|
|
32
|
+
resolver: IndexConstructor<TKey>,
|
|
33
|
+
): CollectionIndexResolverMetadata {
|
|
34
|
+
return {
|
|
35
|
+
kind: `constructor`,
|
|
36
|
+
...(resolver.name ? { name: resolver.name } : {}),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function toSerializableIndexValue(
|
|
41
|
+
value: unknown,
|
|
42
|
+
): CollectionIndexSerializableValue | undefined {
|
|
43
|
+
if (value == null) {
|
|
44
|
+
return value
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
switch (typeof value) {
|
|
48
|
+
case `string`:
|
|
49
|
+
case `boolean`:
|
|
50
|
+
return value
|
|
51
|
+
case `number`:
|
|
52
|
+
return Number.isFinite(value) ? value : null
|
|
53
|
+
case `bigint`:
|
|
54
|
+
return { __type: `bigint`, value: value.toString() }
|
|
55
|
+
case `function`:
|
|
56
|
+
case `symbol`:
|
|
57
|
+
// Function and symbol identity are process-local and not stable across runtimes.
|
|
58
|
+
// Dropping them keeps signatures deterministic; we may skip index reuse, which is acceptable.
|
|
59
|
+
return undefined
|
|
60
|
+
case `undefined`:
|
|
61
|
+
return undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
return value.map((entry) => toSerializableIndexValue(entry) ?? null)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (value instanceof Date) {
|
|
69
|
+
return {
|
|
70
|
+
__type: `date`,
|
|
71
|
+
value: value.toISOString(),
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (value instanceof Set) {
|
|
76
|
+
const serializedValues = Array.from(value)
|
|
77
|
+
.map((entry) => toSerializableIndexValue(entry) ?? null)
|
|
78
|
+
.sort((a, b) =>
|
|
79
|
+
compareStringsCodePoint(
|
|
80
|
+
stableStringifyCollectionIndexValue(a),
|
|
81
|
+
stableStringifyCollectionIndexValue(b),
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
return {
|
|
85
|
+
__type: `set`,
|
|
86
|
+
values: serializedValues,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (value instanceof Map) {
|
|
91
|
+
const serializedEntries = Array.from(value.entries())
|
|
92
|
+
.map(([mapKey, mapValue]) => ({
|
|
93
|
+
key: toSerializableIndexValue(mapKey) ?? null,
|
|
94
|
+
value: toSerializableIndexValue(mapValue) ?? null,
|
|
95
|
+
}))
|
|
96
|
+
.sort((a, b) =>
|
|
97
|
+
compareStringsCodePoint(
|
|
98
|
+
stableStringifyCollectionIndexValue(a.key),
|
|
99
|
+
stableStringifyCollectionIndexValue(b.key),
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
__type: `map`,
|
|
105
|
+
entries: serializedEntries,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (value instanceof RegExp) {
|
|
110
|
+
return {
|
|
111
|
+
__type: `regexp`,
|
|
112
|
+
value: value.toString(),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const serializedObject: Record<string, CollectionIndexSerializableValue> = {}
|
|
117
|
+
const entries = Object.entries(value as Record<string, unknown>).sort(
|
|
118
|
+
([leftKey], [rightKey]) => compareStringsCodePoint(leftKey, rightKey),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
for (const [key, entryValue] of entries) {
|
|
122
|
+
const serializedEntry = toSerializableIndexValue(entryValue)
|
|
123
|
+
if (serializedEntry !== undefined) {
|
|
124
|
+
serializedObject[key] = serializedEntry
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return serializedObject
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function stableStringifyCollectionIndexValue(
|
|
132
|
+
value: CollectionIndexSerializableValue,
|
|
133
|
+
): string {
|
|
134
|
+
if (value === null) {
|
|
135
|
+
return `null`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (Array.isArray(value)) {
|
|
139
|
+
return `[${value.map(stableStringifyCollectionIndexValue).join(`,`)}]`
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (typeof value !== `object`) {
|
|
143
|
+
return JSON.stringify(value)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const sortedKeys = Object.keys(value).sort((left, right) =>
|
|
147
|
+
compareStringsCodePoint(left, right),
|
|
148
|
+
)
|
|
149
|
+
const serializedEntries = sortedKeys.map(
|
|
150
|
+
(key) =>
|
|
151
|
+
`${JSON.stringify(key)}:${stableStringifyCollectionIndexValue(value[key]!)}`,
|
|
152
|
+
)
|
|
153
|
+
return `{${serializedEntries.join(`,`)}}`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function createCollectionIndexMetadata<TKey extends string | number>(
|
|
157
|
+
indexId: number,
|
|
158
|
+
expression: BasicExpression,
|
|
159
|
+
name: string | undefined,
|
|
160
|
+
resolver: IndexConstructor<TKey>,
|
|
161
|
+
options: unknown,
|
|
162
|
+
): CollectionIndexMetadata {
|
|
163
|
+
const resolverMetadata = resolveResolverMetadata(resolver)
|
|
164
|
+
const serializedExpression = toSerializableIndexValue(expression) ?? null
|
|
165
|
+
const serializedOptions = toSerializableIndexValue(options)
|
|
166
|
+
const signatureInput = toSerializableIndexValue({
|
|
167
|
+
signatureVersion: INDEX_SIGNATURE_VERSION,
|
|
168
|
+
expression: serializedExpression,
|
|
169
|
+
options: serializedOptions ?? null,
|
|
170
|
+
})
|
|
171
|
+
const normalizedSignatureInput = signatureInput ?? null
|
|
172
|
+
const signature = stableStringifyCollectionIndexValue(
|
|
173
|
+
normalizedSignatureInput,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
signatureVersion: INDEX_SIGNATURE_VERSION,
|
|
178
|
+
signature,
|
|
179
|
+
indexId,
|
|
180
|
+
name,
|
|
181
|
+
expression,
|
|
182
|
+
resolver: resolverMetadata,
|
|
183
|
+
...(serializedOptions === undefined ? {} : { options: serializedOptions }),
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function cloneSerializableIndexValue(
|
|
188
|
+
value: CollectionIndexSerializableValue,
|
|
189
|
+
): CollectionIndexSerializableValue {
|
|
190
|
+
if (value === null || typeof value !== `object`) {
|
|
191
|
+
return value
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (Array.isArray(value)) {
|
|
195
|
+
return value.map((entry) => cloneSerializableIndexValue(entry))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const cloned: Record<string, CollectionIndexSerializableValue> = {}
|
|
199
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
200
|
+
cloned[key] = cloneSerializableIndexValue(entryValue)
|
|
201
|
+
}
|
|
202
|
+
return cloned
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function cloneExpression(expression: BasicExpression): BasicExpression {
|
|
206
|
+
return JSON.parse(JSON.stringify(expression)) as BasicExpression
|
|
207
|
+
}
|
|
14
208
|
|
|
15
209
|
export class CollectionIndexesManager<
|
|
16
210
|
TOutput extends object = Record<string, unknown>,
|
|
@@ -20,10 +214,11 @@ export class CollectionIndexesManager<
|
|
|
20
214
|
> {
|
|
21
215
|
private lifecycle!: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>
|
|
22
216
|
private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>
|
|
217
|
+
private defaultIndexType: IndexConstructor<TKey> | undefined
|
|
218
|
+
private events!: CollectionEventsManager
|
|
23
219
|
|
|
24
|
-
public
|
|
25
|
-
public
|
|
26
|
-
public isIndexesResolved = false
|
|
220
|
+
public indexes = new Map<number, BaseIndex<TKey>>()
|
|
221
|
+
public indexMetadata = new Map<number, CollectionIndexMetadata>()
|
|
27
222
|
public indexCounter = 0
|
|
28
223
|
|
|
29
224
|
constructor() {}
|
|
@@ -31,18 +226,32 @@ export class CollectionIndexesManager<
|
|
|
31
226
|
setDeps(deps: {
|
|
32
227
|
state: CollectionStateManager<TOutput, TKey, TSchema, TInput>
|
|
33
228
|
lifecycle: CollectionLifecycleManager<TOutput, TKey, TSchema, TInput>
|
|
229
|
+
defaultIndexType?: IndexConstructor<TKey>
|
|
230
|
+
events: CollectionEventsManager
|
|
34
231
|
}) {
|
|
35
232
|
this.state = deps.state
|
|
36
233
|
this.lifecycle = deps.lifecycle
|
|
234
|
+
this.defaultIndexType = deps.defaultIndexType
|
|
235
|
+
this.events = deps.events
|
|
37
236
|
}
|
|
38
237
|
|
|
39
238
|
/**
|
|
40
239
|
* Creates an index on a collection for faster queries.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* // With explicit index type (recommended for tree-shaking)
|
|
244
|
+
* import { BasicIndex } from '@tanstack/db'
|
|
245
|
+
* collection.createIndex((row) => row.userId, { indexType: BasicIndex })
|
|
246
|
+
*
|
|
247
|
+
* // With collection's default index type
|
|
248
|
+
* collection.createIndex((row) => row.userId)
|
|
249
|
+
* ```
|
|
41
250
|
*/
|
|
42
|
-
public createIndex<
|
|
251
|
+
public createIndex<TIndexType extends IndexConstructor<TKey>>(
|
|
43
252
|
indexCallback: (row: SingleRowRefProxy<TOutput>) => any,
|
|
44
|
-
config: IndexOptions<
|
|
45
|
-
):
|
|
253
|
+
config: IndexOptions<TIndexType> = {},
|
|
254
|
+
): BaseIndex<TKey> {
|
|
46
255
|
this.lifecycle.validateCollectionUsable(`createIndex`)
|
|
47
256
|
|
|
48
257
|
const indexId = ++this.indexCounter
|
|
@@ -50,97 +259,96 @@ export class CollectionIndexesManager<
|
|
|
50
259
|
const indexExpression = indexCallback(singleRowRefProxy)
|
|
51
260
|
const expression = toExpression(indexExpression)
|
|
52
261
|
|
|
53
|
-
//
|
|
54
|
-
const
|
|
262
|
+
// Use provided index type, or fall back to collection's default
|
|
263
|
+
const IndexType = config.indexType ?? this.defaultIndexType
|
|
264
|
+
if (!IndexType) {
|
|
265
|
+
throw new CollectionConfigurationError(
|
|
266
|
+
`No index type specified and no defaultIndexType set on collection. ` +
|
|
267
|
+
`Either pass indexType in config, or set defaultIndexType on the collection:\n` +
|
|
268
|
+
` import { BasicIndex } from '@tanstack/db'\n` +
|
|
269
|
+
` createCollection({ defaultIndexType: BasicIndex, ... })`,
|
|
270
|
+
)
|
|
271
|
+
}
|
|
55
272
|
|
|
56
|
-
// Create
|
|
57
|
-
const
|
|
273
|
+
// Create index synchronously
|
|
274
|
+
const index = new IndexType(
|
|
58
275
|
indexId,
|
|
59
276
|
expression,
|
|
60
277
|
config.name,
|
|
61
|
-
resolver,
|
|
62
278
|
config.options,
|
|
63
|
-
this.state.entries(),
|
|
64
279
|
)
|
|
65
280
|
|
|
66
|
-
|
|
281
|
+
// Build with current data
|
|
282
|
+
index.build(this.state.entries())
|
|
67
283
|
|
|
68
|
-
|
|
69
|
-
if ((resolver as unknown) === BTreeIndex) {
|
|
70
|
-
try {
|
|
71
|
-
const resolvedIndex = lazyIndex.getResolved()
|
|
72
|
-
this.resolvedIndexes.set(indexId, resolvedIndex)
|
|
73
|
-
} catch (error) {
|
|
74
|
-
console.warn(`Failed to resolve BTreeIndex:`, error)
|
|
75
|
-
}
|
|
76
|
-
} else if (typeof resolver === `function` && resolver.prototype) {
|
|
77
|
-
// Other synchronous constructors - resolve immediately
|
|
78
|
-
try {
|
|
79
|
-
const resolvedIndex = lazyIndex.getResolved()
|
|
80
|
-
this.resolvedIndexes.set(indexId, resolvedIndex)
|
|
81
|
-
} catch {
|
|
82
|
-
// Fallback to async resolution
|
|
83
|
-
this.resolveSingleIndex(indexId, lazyIndex).catch((error) => {
|
|
84
|
-
console.warn(`Failed to resolve single index:`, error)
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
} else if (this.isIndexesResolved) {
|
|
88
|
-
// Async loader but indexes are already resolved - resolve this one
|
|
89
|
-
this.resolveSingleIndex(indexId, lazyIndex).catch((error) => {
|
|
90
|
-
console.warn(`Failed to resolve single index:`, error)
|
|
91
|
-
})
|
|
92
|
-
}
|
|
284
|
+
this.indexes.set(indexId, index)
|
|
93
285
|
|
|
94
|
-
|
|
286
|
+
// Track metadata and emit event
|
|
287
|
+
const metadata = createCollectionIndexMetadata(
|
|
288
|
+
indexId,
|
|
289
|
+
expression,
|
|
290
|
+
config.name,
|
|
291
|
+
IndexType,
|
|
292
|
+
config.options,
|
|
293
|
+
)
|
|
294
|
+
this.indexMetadata.set(indexId, metadata)
|
|
295
|
+
this.events.emitIndexAdded(metadata)
|
|
296
|
+
|
|
297
|
+
return index
|
|
95
298
|
}
|
|
96
299
|
|
|
97
300
|
/**
|
|
98
|
-
*
|
|
301
|
+
* Removes an index from this collection.
|
|
302
|
+
* Returns true when an index existed and was removed, false otherwise.
|
|
99
303
|
*/
|
|
100
|
-
public
|
|
101
|
-
|
|
304
|
+
public removeIndex(indexOrId: BaseIndex<TKey> | number): boolean {
|
|
305
|
+
this.lifecycle.validateCollectionUsable(`removeIndex`)
|
|
102
306
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
307
|
+
const indexId = typeof indexOrId === `number` ? indexOrId : indexOrId.id
|
|
308
|
+
const index = this.indexes.get(indexId)
|
|
309
|
+
if (!index) {
|
|
310
|
+
return false
|
|
311
|
+
}
|
|
106
312
|
|
|
107
|
-
|
|
108
|
-
|
|
313
|
+
if (typeof indexOrId !== `number` && index !== indexOrId) {
|
|
314
|
+
// Passed a different index instance with the same id — do not remove.
|
|
315
|
+
return false
|
|
316
|
+
}
|
|
109
317
|
|
|
110
|
-
|
|
111
|
-
return { indexId, resolvedIndex }
|
|
112
|
-
},
|
|
113
|
-
)
|
|
318
|
+
this.indexes.delete(indexId)
|
|
114
319
|
|
|
115
|
-
|
|
116
|
-
this.
|
|
117
|
-
|
|
320
|
+
const metadata = this.indexMetadata.get(indexId)
|
|
321
|
+
this.indexMetadata.delete(indexId)
|
|
322
|
+
if (metadata) {
|
|
323
|
+
this.events.emitIndexRemoved(metadata)
|
|
324
|
+
}
|
|
118
325
|
|
|
119
|
-
|
|
120
|
-
* Resolve a single index immediately
|
|
121
|
-
*/
|
|
122
|
-
private async resolveSingleIndex(
|
|
123
|
-
indexId: number,
|
|
124
|
-
lazyIndex: LazyIndexWrapper<TKey>,
|
|
125
|
-
): Promise<BaseIndex<TKey>> {
|
|
126
|
-
const resolvedIndex = await lazyIndex.resolve()
|
|
127
|
-
resolvedIndex.build(this.state.entries())
|
|
128
|
-
this.resolvedIndexes.set(indexId, resolvedIndex)
|
|
129
|
-
return resolvedIndex
|
|
326
|
+
return true
|
|
130
327
|
}
|
|
131
328
|
|
|
132
329
|
/**
|
|
133
|
-
*
|
|
330
|
+
* Returns a sorted snapshot of index metadata.
|
|
331
|
+
* This allows persisted wrappers to bootstrap from indexes that were created
|
|
332
|
+
* before they attached lifecycle listeners.
|
|
134
333
|
*/
|
|
135
|
-
|
|
136
|
-
return this.
|
|
334
|
+
public getIndexMetadataSnapshot(): Array<CollectionIndexMetadata> {
|
|
335
|
+
return Array.from(this.indexMetadata.values())
|
|
336
|
+
.sort((left, right) => left.indexId - right.indexId)
|
|
337
|
+
.map((metadata) => ({
|
|
338
|
+
...metadata,
|
|
339
|
+
expression: cloneExpression(metadata.expression),
|
|
340
|
+
resolver: { ...metadata.resolver },
|
|
341
|
+
...(metadata.options === undefined
|
|
342
|
+
? {}
|
|
343
|
+
: { options: cloneSerializableIndexValue(metadata.options) }),
|
|
344
|
+
}))
|
|
137
345
|
}
|
|
138
346
|
|
|
139
347
|
/**
|
|
140
348
|
* Updates all indexes when the collection changes
|
|
141
349
|
*/
|
|
142
350
|
public updateIndexes(changes: Array<ChangeMessage<TOutput, TKey>>): void {
|
|
143
|
-
for (const index of this.
|
|
351
|
+
for (const index of this.indexes.values()) {
|
|
144
352
|
for (const change of changes) {
|
|
145
353
|
switch (change.type) {
|
|
146
354
|
case `insert`:
|
|
@@ -162,11 +370,10 @@ export class CollectionIndexesManager<
|
|
|
162
370
|
}
|
|
163
371
|
|
|
164
372
|
/**
|
|
165
|
-
* Clean up
|
|
166
|
-
* This can be called manually or automatically by garbage collection
|
|
373
|
+
* Clean up indexes
|
|
167
374
|
*/
|
|
168
375
|
public cleanup(): void {
|
|
169
|
-
this.
|
|
170
|
-
this.
|
|
376
|
+
this.indexes.clear()
|
|
377
|
+
this.indexMetadata.clear()
|
|
171
378
|
}
|
|
172
379
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
safeCancelIdleCallback,
|
|
8
8
|
safeRequestIdleCallback,
|
|
9
9
|
} from '../utils/browser-polyfills'
|
|
10
|
+
import { CleanupQueue } from './cleanup-queue'
|
|
10
11
|
import type { IdleCallbackDeadline } from '../utils/browser-polyfills'
|
|
11
12
|
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
12
13
|
import type { CollectionConfig, CollectionStatus } from '../types'
|
|
@@ -34,7 +35,6 @@ export class CollectionLifecycleManager<
|
|
|
34
35
|
public hasBeenReady = false
|
|
35
36
|
public hasReceivedFirstCommit = false
|
|
36
37
|
public onFirstReadyCallbacks: Array<() => void> = []
|
|
37
|
-
public gcTimeoutId: ReturnType<typeof setTimeout> | null = null
|
|
38
38
|
private idleCallbackId: number | null = null
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -106,17 +106,6 @@ export class CollectionLifecycleManager<
|
|
|
106
106
|
const previousStatus = this.status
|
|
107
107
|
this.status = newStatus
|
|
108
108
|
|
|
109
|
-
// Resolve indexes when collection becomes ready
|
|
110
|
-
if (newStatus === `ready` && !this.indexes.isIndexesResolved) {
|
|
111
|
-
// Resolve indexes asynchronously without blocking
|
|
112
|
-
this.indexes.resolveAllIndexes().catch((error) => {
|
|
113
|
-
console.warn(
|
|
114
|
-
`${this.config.id ? `[${this.config.id}] ` : ``}Failed to resolve indexes:`,
|
|
115
|
-
error,
|
|
116
|
-
)
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
|
|
120
109
|
// Emit event
|
|
121
110
|
this.events.emitStatusChange(newStatus, previousStatus)
|
|
122
111
|
}
|
|
@@ -174,10 +163,6 @@ export class CollectionLifecycleManager<
|
|
|
174
163
|
* Called when the collection becomes inactive (no subscribers)
|
|
175
164
|
*/
|
|
176
165
|
public startGCTimer(): void {
|
|
177
|
-
if (this.gcTimeoutId) {
|
|
178
|
-
clearTimeout(this.gcTimeoutId)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
166
|
const gcTime = this.config.gcTime ?? 300000 // 5 minutes default
|
|
182
167
|
|
|
183
168
|
// If gcTime is 0, negative, or non-finite (Infinity, -Infinity, NaN), GC is disabled.
|
|
@@ -187,12 +172,12 @@ export class CollectionLifecycleManager<
|
|
|
187
172
|
return
|
|
188
173
|
}
|
|
189
174
|
|
|
190
|
-
this
|
|
175
|
+
CleanupQueue.getInstance().schedule(this, gcTime, () => {
|
|
191
176
|
if (this.changes.activeSubscribersCount === 0) {
|
|
192
177
|
// Schedule cleanup during idle time to avoid blocking the UI thread
|
|
193
178
|
this.scheduleIdleCleanup()
|
|
194
179
|
}
|
|
195
|
-
}
|
|
180
|
+
})
|
|
196
181
|
}
|
|
197
182
|
|
|
198
183
|
/**
|
|
@@ -200,10 +185,7 @@ export class CollectionLifecycleManager<
|
|
|
200
185
|
* Called when the collection becomes active again
|
|
201
186
|
*/
|
|
202
187
|
public cancelGCTimer(): void {
|
|
203
|
-
|
|
204
|
-
clearTimeout(this.gcTimeoutId)
|
|
205
|
-
this.gcTimeoutId = null
|
|
206
|
-
}
|
|
188
|
+
CleanupQueue.getInstance().cancel(this)
|
|
207
189
|
// Also cancel any pending idle cleanup
|
|
208
190
|
if (this.idleCallbackId !== null) {
|
|
209
191
|
safeCancelIdleCallback(this.idleCallbackId)
|
|
@@ -258,10 +240,7 @@ export class CollectionLifecycleManager<
|
|
|
258
240
|
this.changes.cleanup()
|
|
259
241
|
this.indexes.cleanup()
|
|
260
242
|
|
|
261
|
-
|
|
262
|
-
clearTimeout(this.gcTimeoutId)
|
|
263
|
-
this.gcTimeoutId = null
|
|
264
|
-
}
|
|
243
|
+
CleanupQueue.getInstance().cancel(this)
|
|
265
244
|
|
|
266
245
|
this.hasBeenReady = false
|
|
267
246
|
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
UndefinedKeyError,
|
|
18
18
|
UpdateKeyNotFoundError,
|
|
19
19
|
} from '../errors'
|
|
20
|
+
import { DIRECT_TRANSACTION_METADATA_KEY } from './transaction-metadata.js'
|
|
20
21
|
import type { Collection, CollectionImpl } from './index.js'
|
|
21
22
|
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
22
23
|
import type {
|
|
@@ -153,6 +154,14 @@ export class CollectionMutationsManager<
|
|
|
153
154
|
return `KEY::${this.id}/${key}`
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
private markPendingLocalOrigins(
|
|
158
|
+
mutations: Array<PendingMutation<TOutput>>,
|
|
159
|
+
): void {
|
|
160
|
+
for (const mutation of mutations) {
|
|
161
|
+
this.state.pendingLocalOrigins.add(mutation.key as TKey)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
156
165
|
/**
|
|
157
166
|
* Inserts one or more items into the collection
|
|
158
167
|
*/
|
|
@@ -222,6 +231,9 @@ export class CollectionMutationsManager<
|
|
|
222
231
|
} else {
|
|
223
232
|
// Create a new transaction with a mutation function that calls the onInsert handler
|
|
224
233
|
const directOpTransaction = createTransaction<TOutput>({
|
|
234
|
+
metadata: {
|
|
235
|
+
[DIRECT_TRANSACTION_METADATA_KEY]: true,
|
|
236
|
+
},
|
|
225
237
|
mutationFn: async (params) => {
|
|
226
238
|
// Call the onInsert handler with the transaction and collection
|
|
227
239
|
return await this.config.onInsert!({
|
|
@@ -237,6 +249,7 @@ export class CollectionMutationsManager<
|
|
|
237
249
|
|
|
238
250
|
// Apply mutations to the new transaction
|
|
239
251
|
directOpTransaction.applyMutations(mutations)
|
|
252
|
+
this.markPendingLocalOrigins(mutations)
|
|
240
253
|
// Errors still reject tx.isPersisted.promise; this catch only prevents global unhandled rejections
|
|
241
254
|
directOpTransaction.commit().catch(() => undefined)
|
|
242
255
|
|
|
@@ -417,6 +430,9 @@ export class CollectionMutationsManager<
|
|
|
417
430
|
|
|
418
431
|
// Create a new transaction with a mutation function that calls the onUpdate handler
|
|
419
432
|
const directOpTransaction = createTransaction<TOutput>({
|
|
433
|
+
metadata: {
|
|
434
|
+
[DIRECT_TRANSACTION_METADATA_KEY]: true,
|
|
435
|
+
},
|
|
420
436
|
mutationFn: async (params) => {
|
|
421
437
|
// Call the onUpdate handler with the transaction and collection
|
|
422
438
|
return this.config.onUpdate!({
|
|
@@ -432,6 +448,7 @@ export class CollectionMutationsManager<
|
|
|
432
448
|
|
|
433
449
|
// Apply mutations to the new transaction
|
|
434
450
|
directOpTransaction.applyMutations(mutations)
|
|
451
|
+
this.markPendingLocalOrigins(mutations)
|
|
435
452
|
// Errors still hit tx.isPersisted.promise; avoid leaking an unhandled rejection from the fire-and-forget commit
|
|
436
453
|
directOpTransaction.commit().catch(() => undefined)
|
|
437
454
|
|
|
@@ -519,6 +536,9 @@ export class CollectionMutationsManager<
|
|
|
519
536
|
// Create a new transaction with a mutation function that calls the onDelete handler
|
|
520
537
|
const directOpTransaction = createTransaction<TOutput>({
|
|
521
538
|
autoCommit: true,
|
|
539
|
+
metadata: {
|
|
540
|
+
[DIRECT_TRANSACTION_METADATA_KEY]: true,
|
|
541
|
+
},
|
|
522
542
|
mutationFn: async (params) => {
|
|
523
543
|
// Call the onDelete handler with the transaction and collection
|
|
524
544
|
return this.config.onDelete!({
|
|
@@ -534,6 +554,7 @@ export class CollectionMutationsManager<
|
|
|
534
554
|
|
|
535
555
|
// Apply mutations to the new transaction
|
|
536
556
|
directOpTransaction.applyMutations(mutations)
|
|
557
|
+
this.markPendingLocalOrigins(mutations)
|
|
537
558
|
// Errors still reject tx.isPersisted.promise; silence the internal commit promise to prevent test noise
|
|
538
559
|
directOpTransaction.commit().catch(() => undefined)
|
|
539
560
|
|