@tanstack/db 0.0.27 → 0.0.30

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.
Files changed (167) hide show
  1. package/dist/cjs/change-events.cjs +141 -0
  2. package/dist/cjs/change-events.cjs.map +1 -0
  3. package/dist/cjs/change-events.d.cts +49 -0
  4. package/dist/cjs/collection.cjs +234 -86
  5. package/dist/cjs/collection.cjs.map +1 -1
  6. package/dist/cjs/collection.d.cts +95 -20
  7. package/dist/cjs/errors.cjs +509 -1
  8. package/dist/cjs/errors.cjs.map +1 -1
  9. package/dist/cjs/errors.d.cts +225 -1
  10. package/dist/cjs/index.cjs +82 -3
  11. package/dist/cjs/index.cjs.map +1 -1
  12. package/dist/cjs/index.d.cts +5 -1
  13. package/dist/cjs/indexes/auto-index.cjs +64 -0
  14. package/dist/cjs/indexes/auto-index.cjs.map +1 -0
  15. package/dist/cjs/indexes/auto-index.d.cts +9 -0
  16. package/dist/cjs/indexes/base-index.cjs +46 -0
  17. package/dist/cjs/indexes/base-index.cjs.map +1 -0
  18. package/dist/cjs/indexes/base-index.d.cts +54 -0
  19. package/dist/cjs/indexes/btree-index.cjs +191 -0
  20. package/dist/cjs/indexes/btree-index.cjs.map +1 -0
  21. package/dist/cjs/indexes/btree-index.d.cts +74 -0
  22. package/dist/cjs/indexes/index-options.d.cts +13 -0
  23. package/dist/cjs/indexes/lazy-index.cjs +193 -0
  24. package/dist/cjs/indexes/lazy-index.cjs.map +1 -0
  25. package/dist/cjs/indexes/lazy-index.d.cts +96 -0
  26. package/dist/cjs/local-storage.cjs +9 -15
  27. package/dist/cjs/local-storage.cjs.map +1 -1
  28. package/dist/cjs/query/builder/functions.cjs +11 -0
  29. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  30. package/dist/cjs/query/builder/functions.d.cts +4 -0
  31. package/dist/cjs/query/builder/index.cjs +6 -7
  32. package/dist/cjs/query/builder/index.cjs.map +1 -1
  33. package/dist/cjs/query/builder/ref-proxy.cjs +37 -0
  34. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  35. package/dist/cjs/query/builder/ref-proxy.d.cts +12 -0
  36. package/dist/cjs/query/compiler/evaluators.cjs +83 -58
  37. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  38. package/dist/cjs/query/compiler/evaluators.d.cts +8 -0
  39. package/dist/cjs/query/compiler/expressions.cjs +61 -0
  40. package/dist/cjs/query/compiler/expressions.cjs.map +1 -0
  41. package/dist/cjs/query/compiler/expressions.d.cts +25 -0
  42. package/dist/cjs/query/compiler/group-by.cjs +5 -10
  43. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  44. package/dist/cjs/query/compiler/index.cjs +23 -17
  45. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  46. package/dist/cjs/query/compiler/index.d.cts +12 -3
  47. package/dist/cjs/query/compiler/joins.cjs +61 -12
  48. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  49. package/dist/cjs/query/compiler/order-by.cjs +4 -34
  50. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  51. package/dist/cjs/query/compiler/types.d.cts +2 -2
  52. package/dist/cjs/query/live-query-collection.cjs +54 -12
  53. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  54. package/dist/cjs/query/optimizer.cjs +45 -7
  55. package/dist/cjs/query/optimizer.cjs.map +1 -1
  56. package/dist/cjs/query/optimizer.d.cts +13 -3
  57. package/dist/cjs/transactions.cjs +5 -4
  58. package/dist/cjs/transactions.cjs.map +1 -1
  59. package/dist/cjs/types.d.cts +31 -0
  60. package/dist/cjs/utils/array-utils.d.cts +8 -0
  61. package/dist/cjs/utils/btree.cjs +677 -0
  62. package/dist/cjs/utils/btree.cjs.map +1 -0
  63. package/dist/cjs/utils/btree.d.cts +197 -0
  64. package/dist/cjs/utils/comparison.cjs +52 -0
  65. package/dist/cjs/utils/comparison.cjs.map +1 -0
  66. package/dist/cjs/utils/comparison.d.cts +11 -0
  67. package/dist/cjs/utils/index-optimization.cjs +270 -0
  68. package/dist/cjs/utils/index-optimization.cjs.map +1 -0
  69. package/dist/cjs/utils/index-optimization.d.cts +29 -0
  70. package/dist/esm/change-events.d.ts +49 -0
  71. package/dist/esm/change-events.js +141 -0
  72. package/dist/esm/change-events.js.map +1 -0
  73. package/dist/esm/collection.d.ts +95 -20
  74. package/dist/esm/collection.js +232 -84
  75. package/dist/esm/collection.js.map +1 -1
  76. package/dist/esm/errors.d.ts +225 -1
  77. package/dist/esm/errors.js +510 -2
  78. package/dist/esm/errors.js.map +1 -1
  79. package/dist/esm/index.d.ts +5 -1
  80. package/dist/esm/index.js +81 -2
  81. package/dist/esm/index.js.map +1 -1
  82. package/dist/esm/indexes/auto-index.d.ts +9 -0
  83. package/dist/esm/indexes/auto-index.js +64 -0
  84. package/dist/esm/indexes/auto-index.js.map +1 -0
  85. package/dist/esm/indexes/base-index.d.ts +54 -0
  86. package/dist/esm/indexes/base-index.js +46 -0
  87. package/dist/esm/indexes/base-index.js.map +1 -0
  88. package/dist/esm/indexes/btree-index.d.ts +74 -0
  89. package/dist/esm/indexes/btree-index.js +191 -0
  90. package/dist/esm/indexes/btree-index.js.map +1 -0
  91. package/dist/esm/indexes/index-options.d.ts +13 -0
  92. package/dist/esm/indexes/lazy-index.d.ts +96 -0
  93. package/dist/esm/indexes/lazy-index.js +193 -0
  94. package/dist/esm/indexes/lazy-index.js.map +1 -0
  95. package/dist/esm/local-storage.js +9 -15
  96. package/dist/esm/local-storage.js.map +1 -1
  97. package/dist/esm/query/builder/functions.d.ts +4 -0
  98. package/dist/esm/query/builder/functions.js +11 -0
  99. package/dist/esm/query/builder/functions.js.map +1 -1
  100. package/dist/esm/query/builder/index.js +6 -7
  101. package/dist/esm/query/builder/index.js.map +1 -1
  102. package/dist/esm/query/builder/ref-proxy.d.ts +12 -0
  103. package/dist/esm/query/builder/ref-proxy.js +37 -0
  104. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  105. package/dist/esm/query/compiler/evaluators.d.ts +8 -0
  106. package/dist/esm/query/compiler/evaluators.js +84 -59
  107. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  108. package/dist/esm/query/compiler/expressions.d.ts +25 -0
  109. package/dist/esm/query/compiler/expressions.js +61 -0
  110. package/dist/esm/query/compiler/expressions.js.map +1 -0
  111. package/dist/esm/query/compiler/group-by.js +5 -10
  112. package/dist/esm/query/compiler/group-by.js.map +1 -1
  113. package/dist/esm/query/compiler/index.d.ts +12 -3
  114. package/dist/esm/query/compiler/index.js +23 -17
  115. package/dist/esm/query/compiler/index.js.map +1 -1
  116. package/dist/esm/query/compiler/joins.js +61 -12
  117. package/dist/esm/query/compiler/joins.js.map +1 -1
  118. package/dist/esm/query/compiler/order-by.js +1 -31
  119. package/dist/esm/query/compiler/order-by.js.map +1 -1
  120. package/dist/esm/query/compiler/types.d.ts +2 -2
  121. package/dist/esm/query/live-query-collection.js +54 -12
  122. package/dist/esm/query/live-query-collection.js.map +1 -1
  123. package/dist/esm/query/optimizer.d.ts +13 -3
  124. package/dist/esm/query/optimizer.js +40 -2
  125. package/dist/esm/query/optimizer.js.map +1 -1
  126. package/dist/esm/transactions.js +5 -4
  127. package/dist/esm/transactions.js.map +1 -1
  128. package/dist/esm/types.d.ts +31 -0
  129. package/dist/esm/utils/array-utils.d.ts +8 -0
  130. package/dist/esm/utils/btree.d.ts +197 -0
  131. package/dist/esm/utils/btree.js +677 -0
  132. package/dist/esm/utils/btree.js.map +1 -0
  133. package/dist/esm/utils/comparison.d.ts +11 -0
  134. package/dist/esm/utils/comparison.js +52 -0
  135. package/dist/esm/utils/comparison.js.map +1 -0
  136. package/dist/esm/utils/index-optimization.d.ts +29 -0
  137. package/dist/esm/utils/index-optimization.js +270 -0
  138. package/dist/esm/utils/index-optimization.js.map +1 -0
  139. package/package.json +1 -1
  140. package/src/change-events.ts +257 -0
  141. package/src/collection.ts +316 -105
  142. package/src/errors.ts +545 -1
  143. package/src/index.ts +7 -1
  144. package/src/indexes/auto-index.ts +108 -0
  145. package/src/indexes/base-index.ts +119 -0
  146. package/src/indexes/btree-index.ts +263 -0
  147. package/src/indexes/index-options.ts +42 -0
  148. package/src/indexes/lazy-index.ts +251 -0
  149. package/src/local-storage.ts +16 -17
  150. package/src/query/builder/functions.ts +14 -0
  151. package/src/query/builder/index.ts +12 -7
  152. package/src/query/builder/ref-proxy.ts +65 -0
  153. package/src/query/compiler/evaluators.ts +114 -62
  154. package/src/query/compiler/expressions.ts +92 -0
  155. package/src/query/compiler/group-by.ts +10 -10
  156. package/src/query/compiler/index.ts +52 -22
  157. package/src/query/compiler/joins.ts +114 -15
  158. package/src/query/compiler/order-by.ts +1 -45
  159. package/src/query/compiler/types.ts +2 -2
  160. package/src/query/live-query-collection.ts +95 -15
  161. package/src/query/optimizer.ts +94 -5
  162. package/src/transactions.ts +10 -4
  163. package/src/types.ts +38 -0
  164. package/src/utils/array-utils.ts +28 -0
  165. package/src/utils/btree.ts +1010 -0
  166. package/src/utils/comparison.ts +79 -0
  167. package/src/utils/index-optimization.ts +546 -0
@@ -0,0 +1,119 @@
1
+ import { compileSingleRowExpression } from "../query/compiler/evaluators.js"
2
+ import { comparisonFunctions } from "../query/builder/functions.js"
3
+ import type { BasicExpression } from "../query/ir.js"
4
+
5
+ /**
6
+ * Operations that indexes can support, imported from available comparison functions
7
+ */
8
+ export const IndexOperation = comparisonFunctions
9
+
10
+ /**
11
+ * Type for index operation values
12
+ */
13
+ export type IndexOperation = (typeof comparisonFunctions)[number]
14
+
15
+ /**
16
+ * Statistics about index usage and performance
17
+ */
18
+ export interface IndexStats {
19
+ readonly entryCount: number
20
+ readonly lookupCount: number
21
+ readonly averageLookupTime: number
22
+ readonly lastUpdated: Date
23
+ }
24
+
25
+ /**
26
+ * Base abstract class that all index types extend
27
+ */
28
+ export abstract class BaseIndex<
29
+ TKey extends string | number = string | number,
30
+ > {
31
+ public readonly id: number
32
+ public readonly name?: string
33
+ public readonly expression: BasicExpression
34
+ public abstract readonly supportedOperations: Set<IndexOperation>
35
+
36
+ protected lookupCount = 0
37
+ protected totalLookupTime = 0
38
+ protected lastUpdated = new Date()
39
+
40
+ constructor(
41
+ id: number,
42
+ expression: BasicExpression,
43
+ name?: string,
44
+ options?: any
45
+ ) {
46
+ this.id = id
47
+ this.expression = expression
48
+ this.name = name
49
+ this.initialize(options)
50
+ }
51
+
52
+ // Abstract methods that each index type must implement
53
+ abstract add(key: TKey, item: any): void
54
+ abstract remove(key: TKey, item: any): void
55
+ abstract update(key: TKey, oldItem: any, newItem: any): void
56
+ abstract build(entries: Iterable<[TKey, any]>): void
57
+ abstract clear(): void
58
+ abstract lookup(operation: IndexOperation, value: any): Set<TKey>
59
+ abstract get keyCount(): number
60
+
61
+ // Common methods
62
+ supports(operation: IndexOperation): boolean {
63
+ return this.supportedOperations.has(operation)
64
+ }
65
+
66
+ matchesField(fieldPath: Array<string>): boolean {
67
+ return (
68
+ this.expression.type === `ref` &&
69
+ this.expression.path.length === fieldPath.length &&
70
+ this.expression.path.every((part, i) => part === fieldPath[i])
71
+ )
72
+ }
73
+
74
+ getStats(): IndexStats {
75
+ return {
76
+ entryCount: this.keyCount,
77
+ lookupCount: this.lookupCount,
78
+ averageLookupTime:
79
+ this.lookupCount > 0 ? this.totalLookupTime / this.lookupCount : 0,
80
+ lastUpdated: this.lastUpdated,
81
+ }
82
+ }
83
+
84
+ // Protected methods for subclasses
85
+ protected abstract initialize(options?: any): void
86
+
87
+ protected evaluateIndexExpression(item: any): any {
88
+ const evaluator = compileSingleRowExpression(this.expression)
89
+ return evaluator(item as Record<string, unknown>)
90
+ }
91
+
92
+ protected trackLookup(startTime: number): void {
93
+ const duration = performance.now() - startTime
94
+ this.lookupCount++
95
+ this.totalLookupTime += duration
96
+ }
97
+
98
+ protected updateTimestamp(): void {
99
+ this.lastUpdated = new Date()
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Type for index constructor
105
+ */
106
+ export type IndexConstructor<TKey extends string | number = string | number> =
107
+ new (
108
+ id: number,
109
+ expression: BasicExpression,
110
+ name?: string,
111
+ options?: any
112
+ ) => BaseIndex<TKey>
113
+
114
+ /**
115
+ * Index resolver can be either a class constructor or async loader
116
+ */
117
+ export type IndexResolver<TKey extends string | number = string | number> =
118
+ | IndexConstructor<TKey>
119
+ | (() => Promise<IndexConstructor<TKey>>)
@@ -0,0 +1,263 @@
1
+ import { ascComparator } from "../utils/comparison.js"
2
+ import { BTree } from "../utils/btree.js"
3
+ import { BaseIndex } from "./base-index.js"
4
+ import type { BasicExpression } from "../query/ir.js"
5
+ import type { IndexOperation } from "./base-index.js"
6
+
7
+ /**
8
+ * Options for Ordered index
9
+ */
10
+ export interface BTreeIndexOptions {
11
+ compareFn?: (a: any, b: any) => number
12
+ }
13
+
14
+ /**
15
+ * Options for range queries
16
+ */
17
+ export interface RangeQueryOptions {
18
+ from?: any
19
+ to?: any
20
+ fromInclusive?: boolean
21
+ toInclusive?: boolean
22
+ }
23
+
24
+ /**
25
+ * B+Tree index for sorted data with range queries
26
+ * This maintains items in sorted order and provides efficient range operations
27
+ */
28
+ export class BTreeIndex<
29
+ TKey extends string | number = string | number,
30
+ > extends BaseIndex<TKey> {
31
+ public readonly supportedOperations = new Set<IndexOperation>([
32
+ `eq`,
33
+ `gt`,
34
+ `gte`,
35
+ `lt`,
36
+ `lte`,
37
+ `in`,
38
+ ])
39
+
40
+ // Internal data structures - private to hide implementation details
41
+ // The `orderedEntries` B+ tree is used for efficient range queries
42
+ // The `valueMap` is used for O(1) lookups of PKs by indexed value
43
+ private orderedEntries: BTree<any, undefined> // we don't associate values with the keys of the B+ tree (the keys are indexed values)
44
+ private valueMap = new Map<any, Set<TKey>>() // instead we store a mapping of indexed values to a set of PKs
45
+ private indexedKeys = new Set<TKey>()
46
+ private compareFn: (a: any, b: any) => number = ascComparator
47
+
48
+ constructor(
49
+ id: number,
50
+ expression: BasicExpression,
51
+ name?: string,
52
+ options?: any
53
+ ) {
54
+ super(id, expression, name, options)
55
+ this.compareFn = options?.compareFn ?? ascComparator
56
+ this.orderedEntries = new BTree(this.compareFn)
57
+ }
58
+
59
+ protected initialize(_options?: BTreeIndexOptions): void {}
60
+
61
+ /**
62
+ * Adds a value to the index
63
+ */
64
+ add(key: TKey, item: any): void {
65
+ let indexedValue: any
66
+ try {
67
+ indexedValue = this.evaluateIndexExpression(item)
68
+ } catch (error) {
69
+ throw new Error(
70
+ `Failed to evaluate index expression for key ${key}: ${error}`
71
+ )
72
+ }
73
+
74
+ // Check if this value already exists
75
+ if (this.valueMap.has(indexedValue)) {
76
+ // Add to existing set
77
+ this.valueMap.get(indexedValue)!.add(key)
78
+ } else {
79
+ // Create new set for this value
80
+ const keySet = new Set<TKey>([key])
81
+ this.valueMap.set(indexedValue, keySet)
82
+ this.orderedEntries.set(indexedValue, undefined)
83
+ }
84
+
85
+ this.indexedKeys.add(key)
86
+ this.updateTimestamp()
87
+ }
88
+
89
+ /**
90
+ * Removes a value from the index
91
+ */
92
+ remove(key: TKey, item: any): void {
93
+ let indexedValue: any
94
+ try {
95
+ indexedValue = this.evaluateIndexExpression(item)
96
+ } catch (error) {
97
+ console.warn(
98
+ `Failed to evaluate index expression for key ${key} during removal:`,
99
+ error
100
+ )
101
+ return
102
+ }
103
+
104
+ if (this.valueMap.has(indexedValue)) {
105
+ const keySet = this.valueMap.get(indexedValue)!
106
+ keySet.delete(key)
107
+
108
+ // If set is now empty, remove the entry entirely
109
+ if (keySet.size === 0) {
110
+ this.valueMap.delete(indexedValue)
111
+
112
+ // Remove from ordered entries
113
+ this.orderedEntries.delete(indexedValue)
114
+ }
115
+ }
116
+
117
+ this.indexedKeys.delete(key)
118
+ this.updateTimestamp()
119
+ }
120
+
121
+ /**
122
+ * Updates a value in the index
123
+ */
124
+ update(key: TKey, oldItem: any, newItem: any): void {
125
+ this.remove(key, oldItem)
126
+ this.add(key, newItem)
127
+ }
128
+
129
+ /**
130
+ * Builds the index from a collection of entries
131
+ */
132
+ build(entries: Iterable<[TKey, any]>): void {
133
+ this.clear()
134
+
135
+ for (const [key, item] of entries) {
136
+ this.add(key, item)
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Clears all data from the index
142
+ */
143
+ clear(): void {
144
+ this.orderedEntries.clear()
145
+ this.valueMap.clear()
146
+ this.indexedKeys.clear()
147
+ this.updateTimestamp()
148
+ }
149
+
150
+ /**
151
+ * Performs a lookup operation
152
+ */
153
+ lookup(operation: IndexOperation, value: any): Set<TKey> {
154
+ const startTime = performance.now()
155
+
156
+ let result: Set<TKey>
157
+
158
+ switch (operation) {
159
+ case `eq`:
160
+ result = this.equalityLookup(value)
161
+ break
162
+ case `gt`:
163
+ result = this.rangeQuery({ from: value, fromInclusive: false })
164
+ break
165
+ case `gte`:
166
+ result = this.rangeQuery({ from: value, fromInclusive: true })
167
+ break
168
+ case `lt`:
169
+ result = this.rangeQuery({ to: value, toInclusive: false })
170
+ break
171
+ case `lte`:
172
+ result = this.rangeQuery({ to: value, toInclusive: true })
173
+ break
174
+ case `in`:
175
+ result = this.inArrayLookup(value)
176
+ break
177
+ default:
178
+ throw new Error(`Operation ${operation} not supported by BTreeIndex`)
179
+ }
180
+
181
+ this.trackLookup(startTime)
182
+ return result
183
+ }
184
+
185
+ /**
186
+ * Gets the number of indexed keys
187
+ */
188
+ get keyCount(): number {
189
+ return this.indexedKeys.size
190
+ }
191
+
192
+ // Public methods for backward compatibility (used by tests)
193
+
194
+ /**
195
+ * Performs an equality lookup
196
+ */
197
+ equalityLookup(value: any): Set<TKey> {
198
+ return new Set(this.valueMap.get(value) ?? [])
199
+ }
200
+
201
+ /**
202
+ * Performs a range query with options
203
+ * This is more efficient for compound queries like "WHERE a > 5 AND a < 10"
204
+ */
205
+ rangeQuery(options: RangeQueryOptions = {}): Set<TKey> {
206
+ const { from, to, fromInclusive = true, toInclusive = true } = options
207
+ const result = new Set<TKey>()
208
+
209
+ const fromKey = from ?? this.orderedEntries.minKey()
210
+ const toKey = to ?? this.orderedEntries.maxKey()
211
+
212
+ this.orderedEntries.forRange(
213
+ fromKey,
214
+ toKey,
215
+ toInclusive,
216
+ (indexedValue, _) => {
217
+ if (!fromInclusive && this.compareFn(indexedValue, from) === 0) {
218
+ // the B+ tree `forRange` method does not support exclusive lower bounds
219
+ // so we need to exclude it manually
220
+ return
221
+ }
222
+
223
+ const keys = this.valueMap.get(indexedValue)
224
+ if (keys) {
225
+ keys.forEach((key) => result.add(key))
226
+ }
227
+ }
228
+ )
229
+
230
+ return result
231
+ }
232
+
233
+ /**
234
+ * Performs an IN array lookup
235
+ */
236
+ inArrayLookup(values: Array<any>): Set<TKey> {
237
+ const result = new Set<TKey>()
238
+
239
+ for (const value of values) {
240
+ const keys = this.valueMap.get(value)
241
+ if (keys) {
242
+ keys.forEach((key) => result.add(key))
243
+ }
244
+ }
245
+
246
+ return result
247
+ }
248
+
249
+ // Getter methods for testing compatibility
250
+ get indexedKeysSet(): Set<TKey> {
251
+ return this.indexedKeys
252
+ }
253
+
254
+ get orderedEntriesArray(): Array<[any, Set<TKey>]> {
255
+ return this.orderedEntries
256
+ .keysArray()
257
+ .map((key) => [key, this.valueMap.get(key) ?? new Set()])
258
+ }
259
+
260
+ get valueMapData(): Map<any, Set<TKey>> {
261
+ return this.valueMap
262
+ }
263
+ }
@@ -0,0 +1,42 @@
1
+ import type { IndexConstructor, IndexResolver } from "./base-index.js"
2
+
3
+ /**
4
+ * Enhanced index options that support both sync and async resolvers
5
+ */
6
+ export interface IndexOptions<TResolver extends IndexResolver = IndexResolver> {
7
+ name?: string
8
+ indexType?: TResolver
9
+ options?: TResolver extends IndexConstructor<any>
10
+ ? TResolver extends new (
11
+ id: string,
12
+ expr: any,
13
+ name?: string,
14
+ options?: infer O
15
+ ) => any
16
+ ? O
17
+ : never
18
+ : TResolver extends () => Promise<infer TCtor>
19
+ ? TCtor extends new (
20
+ id: string,
21
+ expr: any,
22
+ name?: string,
23
+ options?: infer O
24
+ ) => any
25
+ ? O
26
+ : never
27
+ : never
28
+ }
29
+
30
+ /**
31
+ * Utility type to extract the constructed index type from a resolver
32
+ */
33
+ export type ResolvedIndexType<TResolver extends IndexResolver> =
34
+ TResolver extends IndexConstructor<any>
35
+ ? InstanceType<TResolver>
36
+ : TResolver extends () => Promise<IndexConstructor<any>>
37
+ ? TResolver extends () => Promise<infer TCtor>
38
+ ? TCtor extends IndexConstructor<any>
39
+ ? InstanceType<TCtor>
40
+ : never
41
+ : never
42
+ : never
@@ -0,0 +1,251 @@
1
+ import type {
2
+ BaseIndex,
3
+ IndexConstructor,
4
+ IndexResolver,
5
+ } from "./base-index.js"
6
+ import type { BasicExpression } from "../query/ir.js"
7
+
8
+ /**
9
+ * Utility to determine if a resolver is a constructor or async loader
10
+ */
11
+ function isConstructor<TKey extends string | number>(
12
+ resolver: IndexResolver<TKey>
13
+ ): resolver is IndexConstructor<TKey> {
14
+ // Check if it's a function with a prototype (constructor)
15
+ return (
16
+ typeof resolver === `function` &&
17
+ resolver.prototype !== undefined &&
18
+ resolver.prototype.constructor === resolver
19
+ )
20
+ }
21
+
22
+ /**
23
+ * Resolve index constructor from resolver
24
+ */
25
+ async function resolveIndexConstructor<TKey extends string | number>(
26
+ resolver: IndexResolver<TKey>
27
+ ): Promise<IndexConstructor<TKey>> {
28
+ if (isConstructor(resolver)) {
29
+ return resolver
30
+ } else {
31
+ // It's an async loader function
32
+ return await resolver()
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Wrapper that defers index creation until first sync
38
+ */
39
+ export class LazyIndexWrapper<TKey extends string | number = string | number> {
40
+ private indexPromise: Promise<BaseIndex<TKey>> | null = null
41
+ private resolvedIndex: BaseIndex<TKey> | null = null
42
+
43
+ constructor(
44
+ private id: number,
45
+ private expression: BasicExpression,
46
+ private name: string | undefined,
47
+ private resolver: IndexResolver<TKey>,
48
+ private options: any,
49
+ private collectionEntries?: Iterable<[TKey, any]>
50
+ ) {
51
+ // For synchronous constructors, resolve immediately
52
+ if (isConstructor(this.resolver)) {
53
+ this.resolvedIndex = new this.resolver(
54
+ this.id,
55
+ this.expression,
56
+ this.name,
57
+ this.options
58
+ )
59
+ // Build with initial data if provided
60
+ if (this.collectionEntries) {
61
+ this.resolvedIndex.build(this.collectionEntries)
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Resolve the actual index
68
+ */
69
+ async resolve(): Promise<BaseIndex<TKey>> {
70
+ if (this.resolvedIndex) {
71
+ return this.resolvedIndex
72
+ }
73
+
74
+ if (!this.indexPromise) {
75
+ this.indexPromise = this.createIndex()
76
+ }
77
+
78
+ this.resolvedIndex = await this.indexPromise
79
+ return this.resolvedIndex
80
+ }
81
+
82
+ /**
83
+ * Check if already resolved
84
+ */
85
+ isResolved(): boolean {
86
+ return this.resolvedIndex !== null
87
+ }
88
+
89
+ /**
90
+ * Get resolved index (throws if not ready)
91
+ */
92
+ getResolved(): BaseIndex<TKey> {
93
+ if (!this.resolvedIndex) {
94
+ throw new Error(
95
+ `Index ${this.id} has not been resolved yet. Ensure collection is synced.`
96
+ )
97
+ }
98
+ return this.resolvedIndex
99
+ }
100
+
101
+ /**
102
+ * Get the index ID
103
+ */
104
+ getId(): number {
105
+ return this.id
106
+ }
107
+
108
+ /**
109
+ * Get the index name
110
+ */
111
+ getName(): string | undefined {
112
+ return this.name
113
+ }
114
+
115
+ /**
116
+ * Get the index expression
117
+ */
118
+ getExpression(): BasicExpression {
119
+ return this.expression
120
+ }
121
+
122
+ private async createIndex(): Promise<BaseIndex<TKey>> {
123
+ const IndexClass = await resolveIndexConstructor(this.resolver)
124
+ return new IndexClass(this.id, this.expression, this.name, this.options)
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Proxy that provides synchronous interface while index loads asynchronously
130
+ */
131
+ export class IndexProxy<TKey extends string | number = string | number> {
132
+ constructor(
133
+ private indexId: number,
134
+ private lazyIndex: LazyIndexWrapper<TKey>
135
+ ) {}
136
+
137
+ /**
138
+ * Get the resolved index (throws if not ready)
139
+ */
140
+ get index(): BaseIndex<TKey> {
141
+ return this.lazyIndex.getResolved()
142
+ }
143
+
144
+ /**
145
+ * Check if index is ready
146
+ */
147
+ get isReady(): boolean {
148
+ return this.lazyIndex.isResolved()
149
+ }
150
+
151
+ /**
152
+ * Wait for index to be ready
153
+ */
154
+ async whenReady(): Promise<BaseIndex<TKey>> {
155
+ return await this.lazyIndex.resolve()
156
+ }
157
+
158
+ /**
159
+ * Get the index ID
160
+ */
161
+ get id(): number {
162
+ return this.indexId
163
+ }
164
+
165
+ /**
166
+ * Get the index name (throws if not ready)
167
+ */
168
+ get name(): string | undefined {
169
+ if (this.isReady) {
170
+ return this.index.name
171
+ }
172
+ return this.lazyIndex.getName()
173
+ }
174
+
175
+ /**
176
+ * Get the index expression (available immediately)
177
+ */
178
+ get expression(): BasicExpression {
179
+ return this.lazyIndex.getExpression()
180
+ }
181
+
182
+ /**
183
+ * Check if index supports an operation (throws if not ready)
184
+ */
185
+ supports(operation: any): boolean {
186
+ return this.index.supports(operation)
187
+ }
188
+
189
+ /**
190
+ * Get index statistics (throws if not ready)
191
+ */
192
+ getStats() {
193
+ return this.index.getStats()
194
+ }
195
+
196
+ /**
197
+ * Check if index matches a field path (available immediately)
198
+ */
199
+ matchesField(fieldPath: Array<string>): boolean {
200
+ const expr = this.expression
201
+ return (
202
+ expr.type === `ref` &&
203
+ expr.path.length === fieldPath.length &&
204
+ expr.path.every((part, i) => part === fieldPath[i])
205
+ )
206
+ }
207
+
208
+ /**
209
+ * Get the key count (throws if not ready)
210
+ */
211
+ get keyCount(): number {
212
+ return this.index.keyCount
213
+ }
214
+
215
+ // Test compatibility properties - delegate to resolved index
216
+ get indexedKeysSet(): Set<TKey> {
217
+ const resolved = this.index as any
218
+ return resolved.indexedKeysSet
219
+ }
220
+
221
+ get orderedEntriesArray(): Array<[any, Set<TKey>]> {
222
+ const resolved = this.index as any
223
+ return resolved.orderedEntriesArray
224
+ }
225
+
226
+ get valueMapData(): Map<any, Set<TKey>> {
227
+ const resolved = this.index as any
228
+ return resolved.valueMapData
229
+ }
230
+
231
+ // BTreeIndex compatibility methods
232
+ equalityLookup(value: any): Set<TKey> {
233
+ const resolved = this.index as any
234
+ return resolved.equalityLookup?.(value) ?? new Set()
235
+ }
236
+
237
+ rangeQuery(options: any): Set<TKey> {
238
+ const resolved = this.index as any
239
+ return resolved.rangeQuery?.(options) ?? new Set()
240
+ }
241
+
242
+ inArrayLookup(values: Array<any>): Set<TKey> {
243
+ const resolved = this.index as any
244
+ return resolved.inArrayLookup?.(values) ?? new Set()
245
+ }
246
+
247
+ // Internal method for the collection to get the lazy wrapper
248
+ _getLazyWrapper(): LazyIndexWrapper<TKey> {
249
+ return this.lazyIndex
250
+ }
251
+ }