@tanstack/db 0.4.2 → 0.4.4

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 (180) hide show
  1. package/dist/cjs/collection/change-events.cjs +1 -1
  2. package/dist/cjs/collection/change-events.cjs.map +1 -1
  3. package/dist/cjs/collection/changes.cjs +7 -3
  4. package/dist/cjs/collection/changes.cjs.map +1 -1
  5. package/dist/cjs/collection/events.cjs +3 -6
  6. package/dist/cjs/collection/events.cjs.map +1 -1
  7. package/dist/cjs/collection/index.cjs +4 -1
  8. package/dist/cjs/collection/index.cjs.map +1 -1
  9. package/dist/cjs/collection/index.d.cts +16 -4
  10. package/dist/cjs/collection/mutations.cjs +13 -20
  11. package/dist/cjs/collection/mutations.cjs.map +1 -1
  12. package/dist/cjs/collection/state.cjs +9 -2
  13. package/dist/cjs/collection/state.cjs.map +1 -1
  14. package/dist/cjs/collection/subscription.cjs +4 -5
  15. package/dist/cjs/collection/subscription.cjs.map +1 -1
  16. package/dist/cjs/collection/subscription.d.cts +2 -2
  17. package/dist/cjs/collection/sync.cjs +10 -2
  18. package/dist/cjs/collection/sync.cjs.map +1 -1
  19. package/dist/cjs/indexes/auto-index.cjs +4 -3
  20. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  21. package/dist/cjs/indexes/auto-index.d.cts +2 -1
  22. package/dist/cjs/indexes/base-index.cjs +26 -0
  23. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  24. package/dist/cjs/indexes/base-index.d.cts +47 -2
  25. package/dist/cjs/indexes/btree-index.cjs +45 -9
  26. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  27. package/dist/cjs/indexes/btree-index.d.cts +15 -0
  28. package/dist/cjs/indexes/lazy-index.cjs +3 -6
  29. package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
  30. package/dist/cjs/indexes/reverse-index.cjs +78 -0
  31. package/dist/cjs/indexes/reverse-index.cjs.map +1 -0
  32. package/dist/cjs/indexes/reverse-index.d.cts +30 -0
  33. package/dist/cjs/proxy.cjs +1 -1
  34. package/dist/cjs/proxy.cjs.map +1 -1
  35. package/dist/cjs/query/builder/index.cjs +21 -0
  36. package/dist/cjs/query/builder/index.cjs.map +1 -1
  37. package/dist/cjs/query/builder/index.d.cts +16 -1
  38. package/dist/cjs/query/builder/types.d.cts +7 -0
  39. package/dist/cjs/query/compiler/evaluators.cjs +1 -1
  40. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  41. package/dist/cjs/query/compiler/group-by.cjs +2 -10
  42. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  43. package/dist/cjs/query/compiler/index.cjs +2 -39
  44. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  45. package/dist/cjs/query/compiler/index.d.cts +1 -0
  46. package/dist/cjs/query/compiler/joins.cjs +15 -14
  47. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  48. package/dist/cjs/query/compiler/joins.d.cts +2 -1
  49. package/dist/cjs/query/compiler/order-by.cjs +8 -10
  50. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  51. package/dist/cjs/query/compiler/order-by.d.cts +2 -2
  52. package/dist/cjs/query/compiler/select.cjs +1 -1
  53. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  54. package/dist/cjs/query/index.d.cts +1 -1
  55. package/dist/cjs/query/ir.cjs +38 -0
  56. package/dist/cjs/query/ir.cjs.map +1 -1
  57. package/dist/cjs/query/ir.d.cts +11 -1
  58. package/dist/cjs/query/live/collection-config-builder.cjs +3 -2
  59. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  60. package/dist/cjs/query/live/collection-config-builder.d.cts +2 -2
  61. package/dist/cjs/query/live/collection-subscriber.cjs +2 -3
  62. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  63. package/dist/cjs/query/live/types.d.cts +4 -0
  64. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  65. package/dist/cjs/query/live-query-collection.d.cts +7 -4
  66. package/dist/cjs/query/optimizer.cjs +2 -4
  67. package/dist/cjs/query/optimizer.cjs.map +1 -1
  68. package/dist/cjs/transactions.cjs +2 -3
  69. package/dist/cjs/transactions.cjs.map +1 -1
  70. package/dist/cjs/types.d.cts +13 -0
  71. package/dist/cjs/utils/btree.cjs +1 -1
  72. package/dist/cjs/utils/btree.cjs.map +1 -1
  73. package/dist/cjs/utils/index-optimization.cjs +7 -2
  74. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  75. package/dist/cjs/utils/index-optimization.d.cts +3 -2
  76. package/dist/cjs/utils.cjs +6 -0
  77. package/dist/cjs/utils.cjs.map +1 -1
  78. package/dist/cjs/utils.d.cts +2 -3
  79. package/dist/esm/collection/change-events.js +1 -1
  80. package/dist/esm/collection/change-events.js.map +1 -1
  81. package/dist/esm/collection/changes.js +7 -3
  82. package/dist/esm/collection/changes.js.map +1 -1
  83. package/dist/esm/collection/events.js +3 -6
  84. package/dist/esm/collection/events.js.map +1 -1
  85. package/dist/esm/collection/index.d.ts +16 -4
  86. package/dist/esm/collection/index.js +4 -1
  87. package/dist/esm/collection/index.js.map +1 -1
  88. package/dist/esm/collection/mutations.js +13 -20
  89. package/dist/esm/collection/mutations.js.map +1 -1
  90. package/dist/esm/collection/state.js +9 -2
  91. package/dist/esm/collection/state.js.map +1 -1
  92. package/dist/esm/collection/subscription.d.ts +2 -2
  93. package/dist/esm/collection/subscription.js +4 -5
  94. package/dist/esm/collection/subscription.js.map +1 -1
  95. package/dist/esm/collection/sync.js +10 -2
  96. package/dist/esm/collection/sync.js.map +1 -1
  97. package/dist/esm/indexes/auto-index.d.ts +2 -1
  98. package/dist/esm/indexes/auto-index.js +4 -3
  99. package/dist/esm/indexes/auto-index.js.map +1 -1
  100. package/dist/esm/indexes/base-index.d.ts +47 -2
  101. package/dist/esm/indexes/base-index.js +26 -0
  102. package/dist/esm/indexes/base-index.js.map +1 -1
  103. package/dist/esm/indexes/btree-index.d.ts +15 -0
  104. package/dist/esm/indexes/btree-index.js +45 -9
  105. package/dist/esm/indexes/btree-index.js.map +1 -1
  106. package/dist/esm/indexes/lazy-index.js +3 -6
  107. package/dist/esm/indexes/lazy-index.js.map +1 -1
  108. package/dist/esm/indexes/reverse-index.d.ts +30 -0
  109. package/dist/esm/indexes/reverse-index.js +78 -0
  110. package/dist/esm/indexes/reverse-index.js.map +1 -0
  111. package/dist/esm/proxy.js +1 -1
  112. package/dist/esm/proxy.js.map +1 -1
  113. package/dist/esm/query/builder/index.d.ts +16 -1
  114. package/dist/esm/query/builder/index.js +21 -0
  115. package/dist/esm/query/builder/index.js.map +1 -1
  116. package/dist/esm/query/builder/types.d.ts +7 -0
  117. package/dist/esm/query/compiler/evaluators.js +1 -1
  118. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  119. package/dist/esm/query/compiler/group-by.js +3 -11
  120. package/dist/esm/query/compiler/group-by.js.map +1 -1
  121. package/dist/esm/query/compiler/index.d.ts +1 -0
  122. package/dist/esm/query/compiler/index.js +4 -41
  123. package/dist/esm/query/compiler/index.js.map +1 -1
  124. package/dist/esm/query/compiler/joins.d.ts +2 -1
  125. package/dist/esm/query/compiler/joins.js +15 -14
  126. package/dist/esm/query/compiler/joins.js.map +1 -1
  127. package/dist/esm/query/compiler/order-by.d.ts +2 -2
  128. package/dist/esm/query/compiler/order-by.js +5 -7
  129. package/dist/esm/query/compiler/order-by.js.map +1 -1
  130. package/dist/esm/query/compiler/select.js +1 -1
  131. package/dist/esm/query/compiler/select.js.map +1 -1
  132. package/dist/esm/query/index.d.ts +1 -1
  133. package/dist/esm/query/ir.d.ts +11 -1
  134. package/dist/esm/query/ir.js +38 -0
  135. package/dist/esm/query/ir.js.map +1 -1
  136. package/dist/esm/query/live/collection-config-builder.d.ts +2 -2
  137. package/dist/esm/query/live/collection-config-builder.js +3 -2
  138. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  139. package/dist/esm/query/live/collection-subscriber.js +2 -3
  140. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  141. package/dist/esm/query/live/types.d.ts +4 -0
  142. package/dist/esm/query/live-query-collection.d.ts +7 -4
  143. package/dist/esm/query/live-query-collection.js.map +1 -1
  144. package/dist/esm/query/optimizer.js +2 -4
  145. package/dist/esm/query/optimizer.js.map +1 -1
  146. package/dist/esm/transactions.js +2 -3
  147. package/dist/esm/transactions.js.map +1 -1
  148. package/dist/esm/types.d.ts +13 -0
  149. package/dist/esm/utils/btree.js +1 -1
  150. package/dist/esm/utils/btree.js.map +1 -1
  151. package/dist/esm/utils/index-optimization.d.ts +3 -2
  152. package/dist/esm/utils/index-optimization.js +7 -2
  153. package/dist/esm/utils/index-optimization.js.map +1 -1
  154. package/dist/esm/utils.d.ts +2 -3
  155. package/dist/esm/utils.js +6 -0
  156. package/dist/esm/utils.js.map +1 -1
  157. package/package.json +1 -1
  158. package/src/collection/changes.ts +10 -4
  159. package/src/collection/index.ts +38 -5
  160. package/src/collection/state.ts +22 -5
  161. package/src/collection/subscription.ts +4 -4
  162. package/src/collection/sync.ts +17 -2
  163. package/src/indexes/auto-index.ts +8 -3
  164. package/src/indexes/base-index.ts +94 -4
  165. package/src/indexes/btree-index.ts +58 -7
  166. package/src/indexes/reverse-index.ts +120 -0
  167. package/src/query/builder/index.ts +30 -2
  168. package/src/query/builder/types.ts +12 -0
  169. package/src/query/compiler/group-by.ts +1 -10
  170. package/src/query/compiler/index.ts +4 -1
  171. package/src/query/compiler/joins.ts +13 -8
  172. package/src/query/compiler/order-by.ts +16 -20
  173. package/src/query/index.ts +1 -0
  174. package/src/query/ir.ts +68 -1
  175. package/src/query/live/collection-config-builder.ts +3 -2
  176. package/src/query/live/types.ts +5 -0
  177. package/src/query/live-query-collection.ts +34 -8
  178. package/src/types.ts +22 -0
  179. package/src/utils/index-optimization.ts +19 -5
  180. package/src/utils.ts +8 -0
@@ -17,10 +17,10 @@ import {
17
17
  UnsupportedJoinTypeError,
18
18
  } from "../../errors.js"
19
19
  import { ensureIndexForField } from "../../indexes/auto-index.js"
20
- import { PropRef } from "../ir.js"
20
+ import { PropRef, followRef } from "../ir.js"
21
21
  import { inArray } from "../builder/functions.js"
22
22
  import { compileExpression } from "./evaluators.js"
23
- import { compileQuery, followRef } from "./index.js"
23
+ import type { CompileQueryFn } from "./index.js"
24
24
  import type { OrderByOptimizationInfo } from "./order-by.js"
25
25
  import type {
26
26
  BasicExpression,
@@ -62,7 +62,8 @@ export function processJoins(
62
62
  callbacks: Record<string, LazyCollectionCallbacks>,
63
63
  lazyCollections: Set<string>,
64
64
  optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
65
- rawQuery: QueryIR
65
+ rawQuery: QueryIR,
66
+ onCompileSubquery: CompileQueryFn
66
67
  ): NamespacedAndKeyedStream {
67
68
  let resultPipeline = pipeline
68
69
 
@@ -81,7 +82,8 @@ export function processJoins(
81
82
  callbacks,
82
83
  lazyCollections,
83
84
  optimizableOrderByCollections,
84
- rawQuery
85
+ rawQuery,
86
+ onCompileSubquery
85
87
  )
86
88
  }
87
89
 
@@ -105,7 +107,8 @@ function processJoin(
105
107
  callbacks: Record<string, LazyCollectionCallbacks>,
106
108
  lazyCollections: Set<string>,
107
109
  optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
108
- rawQuery: QueryIR
110
+ rawQuery: QueryIR,
111
+ onCompileSubquery: CompileQueryFn
109
112
  ): NamespacedAndKeyedStream {
110
113
  // Get the joined table alias and input stream
111
114
  const {
@@ -121,7 +124,8 @@ function processJoin(
121
124
  lazyCollections,
122
125
  optimizableOrderByCollections,
123
126
  cache,
124
- queryMapping
127
+ queryMapping,
128
+ onCompileSubquery
125
129
  )
126
130
 
127
131
  // Add the joined table to the tables map
@@ -392,7 +396,8 @@ function processJoinSource(
392
396
  lazyCollections: Set<string>,
393
397
  optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,
394
398
  cache: QueryCache,
395
- queryMapping: QueryMapping
399
+ queryMapping: QueryMapping,
400
+ onCompileSubquery: CompileQueryFn
396
401
  ): { alias: string; input: KeyedStream; collectionId: string } {
397
402
  switch (from.type) {
398
403
  case `collectionRef`: {
@@ -407,7 +412,7 @@ function processJoinSource(
407
412
  const originalQuery = queryMapping.get(from.query) || from.query
408
413
 
409
414
  // Recursively compile the sub-query with cache
410
- const subQueryResult = compileQuery(
415
+ const subQueryResult = onCompileSubquery(
411
416
  originalQuery,
412
417
  allInputs,
413
418
  collections,
@@ -1,16 +1,15 @@
1
1
  import { orderByWithFractionalIndex } from "@tanstack/db-ivm"
2
2
  import { defaultComparator, makeComparator } from "../../utils/comparison.js"
3
- import { PropRef } from "../ir.js"
3
+ import { PropRef, followRef } from "../ir.js"
4
4
  import { ensureIndexForField } from "../../indexes/auto-index.js"
5
5
  import { findIndexForField } from "../../utils/index-optimization.js"
6
6
  import { compileExpression } from "./evaluators.js"
7
7
  import { replaceAggregatesByRefs } from "./group-by.js"
8
- import { followRef } from "./index.js"
9
8
  import type { CompiledSingleRowExpression } from "./evaluators.js"
10
9
  import type { OrderByClause, QueryIR, Select } from "../ir.js"
11
10
  import type { NamespacedAndKeyedStream, NamespacedRow } from "../../types.js"
12
11
  import type { IStreamBuilder, KeyValue } from "@tanstack/db-ivm"
13
- import type { BaseIndex } from "../../indexes/base-index.js"
12
+ import type { IndexInterface } from "../../indexes/base-index.js"
14
13
  import type { Collection } from "../../collection/index.js"
15
14
 
16
15
  export type OrderByOptimizationInfo = {
@@ -21,7 +20,7 @@ export type OrderByOptimizationInfo = {
21
20
  b: Record<string, unknown> | null | undefined
22
21
  ) => number
23
22
  valueExtractorForRawRow: (row: Record<string, unknown>) => any
24
- index: BaseIndex<string | number>
23
+ index: IndexInterface<string | number>
25
24
  dataNeeded?: () => number
26
25
  }
27
26
 
@@ -55,18 +54,12 @@ export function processOrderBy(
55
54
 
56
55
  // Create a value extractor function for the orderBy operator
57
56
  const valueExtractor = (row: NamespacedRow & { __select_results?: any }) => {
58
- // For ORDER BY expressions, we need to provide access to both:
59
- // 1. The original namespaced row data (for direct table column references)
60
- // 2. The __select_results (for SELECT alias references)
61
-
62
- // Create a merged context for expression evaluation
63
- const orderByContext = { ...row }
64
-
65
- // If there are select results, merge them at the top level for alias access
66
- if (row.__select_results) {
67
- // Add select results as top-level properties for alias access
68
- Object.assign(orderByContext, row.__select_results)
69
- }
57
+ // The namespaced row contains:
58
+ // 1. Table aliases as top-level properties (e.g., row["tableName"])
59
+ // 2. SELECT results in __select_results (e.g., row.__select_results["aggregateAlias"])
60
+ // The replaceAggregatesByRefs function has already transformed any aggregate expressions
61
+ // that match SELECT aggregates to use the __select_results namespace.
62
+ const orderByContext = row
70
63
 
71
64
  if (orderByClause.length > 1) {
72
65
  // For multiple orderBy columns, create a composite key
@@ -133,6 +126,7 @@ export function processOrderBy(
133
126
  fieldName,
134
127
  followRefResult.path,
135
128
  followRefCollection,
129
+ clause.compareOptions,
136
130
  compare
137
131
  )
138
132
  }
@@ -151,10 +145,12 @@ export function processOrderBy(
151
145
  return compare(extractedA, extractedB)
152
146
  }
153
147
 
154
- const index: BaseIndex<string | number> | undefined = findIndexForField(
155
- followRefCollection.indexes,
156
- followRefResult.path
157
- )
148
+ const index: IndexInterface<string | number> | undefined =
149
+ findIndexForField(
150
+ followRefCollection.indexes,
151
+ followRefResult.path,
152
+ clause.compareOptions
153
+ )
158
154
 
159
155
  if (index && index.supports(`gt`)) {
160
156
  // We found an index that we can use to lazily load ordered data
@@ -9,6 +9,7 @@ export {
9
9
  type Context,
10
10
  type Source,
11
11
  type GetResult,
12
+ type InferResultType,
12
13
  } from "./builder/index.js"
13
14
 
14
15
  // Expression functions exports
package/src/query/ir.ts CHANGED
@@ -3,7 +3,7 @@ This is the intermediate representation of the query.
3
3
  */
4
4
 
5
5
  import type { CompareOptions } from "./builder/types"
6
- import type { CollectionImpl } from "../collection/index.js"
6
+ import type { Collection, CollectionImpl } from "../collection/index.js"
7
7
  import type { NamespacedRow } from "../types"
8
8
 
9
9
  export interface QueryIR {
@@ -17,6 +17,7 @@ export interface QueryIR {
17
17
  limit?: Limit
18
18
  offset?: Offset
19
19
  distinct?: true
20
+ singleResult?: true
20
21
 
21
22
  // Functional variants
22
23
  fnSelect?: (row: NamespacedRow) => any
@@ -188,3 +189,69 @@ export function createResidualWhere(
188
189
  ): Where {
189
190
  return { expression, residual: true }
190
191
  }
192
+
193
+ function getRefFromAlias(
194
+ query: QueryIR,
195
+ alias: string
196
+ ): CollectionRef | QueryRef | void {
197
+ if (query.from.alias === alias) {
198
+ return query.from
199
+ }
200
+
201
+ for (const join of query.join || []) {
202
+ if (join.from.alias === alias) {
203
+ return join.from
204
+ }
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Follows the given reference in a query
210
+ * until its finds the root field the reference points to.
211
+ * @returns The collection, its alias, and the path to the root field in this collection
212
+ */
213
+ export function followRef(
214
+ query: QueryIR,
215
+ ref: PropRef<any>,
216
+ collection: Collection
217
+ ): { collection: Collection; path: Array<string> } | void {
218
+ if (ref.path.length === 0) {
219
+ return
220
+ }
221
+
222
+ if (ref.path.length === 1) {
223
+ // This field should be part of this collection
224
+ const field = ref.path[0]!
225
+ // is it part of the select clause?
226
+ if (query.select) {
227
+ const selectedField = query.select[field]
228
+ if (selectedField && selectedField.type === `ref`) {
229
+ return followRef(query, selectedField, collection)
230
+ }
231
+ }
232
+
233
+ // Either this field is not part of the select clause
234
+ // and thus it must be part of the collection itself
235
+ // or it is part of the select but is not a reference
236
+ // so we can stop here and don't have to follow it
237
+ return { collection, path: [field] }
238
+ }
239
+
240
+ if (ref.path.length > 1) {
241
+ // This is a nested field
242
+ const [alias, ...rest] = ref.path
243
+ const aliasRef = getRefFromAlias(query, alias!)
244
+ if (!aliasRef) {
245
+ return
246
+ }
247
+
248
+ if (aliasRef.type === `queryRef`) {
249
+ return followRef(aliasRef.query, new PropRef(rest), collection)
250
+ } else {
251
+ // This is a reference to a collection
252
+ // we can't follow it further
253
+ // so the field must be on the collection itself
254
+ return { collection: aliasRef.collection, path: rest }
255
+ }
256
+ }
257
+ }
@@ -7,7 +7,7 @@ import type { RootStreamBuilder } from "@tanstack/db-ivm"
7
7
  import type { OrderByOptimizationInfo } from "../compiler/order-by.js"
8
8
  import type { Collection } from "../../collection/index.js"
9
9
  import type {
10
- CollectionConfig,
10
+ CollectionConfigSingleRowOption,
11
11
  KeyedStream,
12
12
  ResultStream,
13
13
  SyncConfig,
@@ -79,7 +79,7 @@ export class CollectionConfigBuilder<
79
79
  this.compileBasePipeline()
80
80
  }
81
81
 
82
- getConfig(): CollectionConfig<TResult> {
82
+ getConfig(): CollectionConfigSingleRowOption<TResult> {
83
83
  return {
84
84
  id: this.id,
85
85
  getKey:
@@ -93,6 +93,7 @@ export class CollectionConfigBuilder<
93
93
  onUpdate: this.config.onUpdate,
94
94
  onDelete: this.config.onDelete,
95
95
  startSync: this.config.startSync,
96
+ singleResult: this.query.singleResult,
96
97
  }
97
98
  }
98
99
 
@@ -90,4 +90,9 @@ export interface LiveQueryCollectionConfig<
90
90
  * GC time for the collection
91
91
  */
92
92
  gcTime?: number
93
+
94
+ /**
95
+ * If enabled the collection will return a single object instead of an array
96
+ */
97
+ singleResult?: true
93
98
  }
@@ -3,9 +3,29 @@ import { CollectionConfigBuilder } from "./live/collection-config-builder.js"
3
3
  import type { LiveQueryCollectionConfig } from "./live/types.js"
4
4
  import type { InitialQueryBuilder, QueryBuilder } from "./builder/index.js"
5
5
  import type { Collection } from "../collection/index.js"
6
- import type { CollectionConfig, UtilsRecord } from "../types.js"
6
+ import type {
7
+ CollectionConfig,
8
+ CollectionConfigSingleRowOption,
9
+ NonSingleResult,
10
+ SingleResult,
11
+ UtilsRecord,
12
+ } from "../types.js"
7
13
  import type { Context, GetResult } from "./builder/types.js"
8
14
 
15
+ type CollectionConfigForContext<
16
+ TContext extends Context,
17
+ TResult extends object,
18
+ > = TContext extends SingleResult
19
+ ? CollectionConfigSingleRowOption<TResult> & SingleResult
20
+ : CollectionConfigSingleRowOption<TResult> & NonSingleResult
21
+
22
+ type CollectionForContext<
23
+ TContext extends Context,
24
+ TResult extends object,
25
+ > = TContext extends SingleResult
26
+ ? Collection<TResult> & SingleResult
27
+ : Collection<TResult> & NonSingleResult
28
+
9
29
  /**
10
30
  * Creates live query collection options for use with createCollection
11
31
  *
@@ -35,12 +55,15 @@ export function liveQueryCollectionOptions<
35
55
  TResult extends object = GetResult<TContext>,
36
56
  >(
37
57
  config: LiveQueryCollectionConfig<TContext, TResult>
38
- ): CollectionConfig<TResult> {
58
+ ): CollectionConfigForContext<TContext, TResult> {
39
59
  const collectionConfigBuilder = new CollectionConfigBuilder<
40
60
  TContext,
41
61
  TResult
42
62
  >(config)
43
- return collectionConfigBuilder.getConfig()
63
+ return collectionConfigBuilder.getConfig() as CollectionConfigForContext<
64
+ TContext,
65
+ TResult
66
+ >
44
67
  }
45
68
 
46
69
  /**
@@ -83,7 +106,7 @@ export function createLiveQueryCollection<
83
106
  TResult extends object = GetResult<TContext>,
84
107
  >(
85
108
  query: (q: InitialQueryBuilder) => QueryBuilder<TContext>
86
- ): Collection<TResult, string | number, {}>
109
+ ): CollectionForContext<TContext, TResult>
87
110
 
88
111
  // Overload 2: Accept full config object with optional utilities
89
112
  export function createLiveQueryCollection<
@@ -92,7 +115,7 @@ export function createLiveQueryCollection<
92
115
  TUtils extends UtilsRecord = {},
93
116
  >(
94
117
  config: LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils }
95
- ): Collection<TResult, string | number, TUtils>
118
+ ): CollectionForContext<TContext, TResult>
96
119
 
97
120
  // Implementation
98
121
  export function createLiveQueryCollection<
@@ -103,7 +126,7 @@ export function createLiveQueryCollection<
103
126
  configOrQuery:
104
127
  | (LiveQueryCollectionConfig<TContext, TResult> & { utils?: TUtils })
105
128
  | ((q: InitialQueryBuilder) => QueryBuilder<TContext>)
106
- ): Collection<TResult, string | number, TUtils> {
129
+ ): CollectionForContext<TContext, TResult> {
107
130
  // Determine if the argument is a function (query) or a config object
108
131
  if (typeof configOrQuery === `function`) {
109
132
  // Simple query function case
@@ -113,7 +136,10 @@ export function createLiveQueryCollection<
113
136
  ) => QueryBuilder<TContext>,
114
137
  }
115
138
  const options = liveQueryCollectionOptions<TContext, TResult>(config)
116
- return bridgeToCreateCollection(options)
139
+ return bridgeToCreateCollection(options) as CollectionForContext<
140
+ TContext,
141
+ TResult
142
+ >
117
143
  } else {
118
144
  // Config object case
119
145
  const config = configOrQuery as LiveQueryCollectionConfig<
@@ -124,7 +150,7 @@ export function createLiveQueryCollection<
124
150
  return bridgeToCreateCollection({
125
151
  ...options,
126
152
  utils: config.utils,
127
- })
153
+ }) as CollectionForContext<TContext, TResult>
128
154
  }
129
155
  }
130
156
 
package/src/types.ts CHANGED
@@ -503,6 +503,28 @@ export interface CollectionConfig<
503
503
  sync: SyncConfig<T, TKey>
504
504
  }
505
505
 
506
+ export type SingleResult = {
507
+ singleResult: true
508
+ }
509
+
510
+ export type NonSingleResult = {
511
+ singleResult?: never
512
+ }
513
+
514
+ export type MaybeSingleResult = {
515
+ /**
516
+ * If enabled the collection will return a single object instead of an array
517
+ */
518
+ singleResult?: true
519
+ }
520
+
521
+ // Only used for live query collections
522
+ export type CollectionConfigSingleRowOption<
523
+ T extends object = Record<string, unknown>,
524
+ TKey extends string | number = string | number,
525
+ TSchema extends StandardSchemaV1 = never,
526
+ > = CollectionConfig<T, TKey, TSchema> & MaybeSingleResult
527
+
506
528
  export type ChangesPayload<T extends object = Record<string, unknown>> = Array<
507
529
  ChangeMessage<T>
508
530
  >
@@ -15,7 +15,14 @@
15
15
  * - Optimizes IN array expressions
16
16
  */
17
17
 
18
- import type { BaseIndex, IndexOperation } from "../indexes/base-index.js"
18
+ import { DEFAULT_COMPARE_OPTIONS } from "../utils.js"
19
+ import { ReverseIndex } from "../indexes/reverse-index.js"
20
+ import type { CompareOptions } from "../query/builder/types.js"
21
+ import type {
22
+ BaseIndex,
23
+ IndexInterface,
24
+ IndexOperation,
25
+ } from "../indexes/base-index.js"
19
26
  import type { BasicExpression } from "../query/ir.js"
20
27
 
21
28
  /**
@@ -30,11 +37,18 @@ export interface OptimizationResult<TKey> {
30
37
  * Finds an index that matches a given field path
31
38
  */
32
39
  export function findIndexForField<TKey extends string | number>(
33
- indexes: Map<number, BaseIndex<TKey>>,
34
- fieldPath: Array<string>
35
- ): BaseIndex<TKey> | undefined {
40
+ indexes: Map<number, IndexInterface<TKey>>,
41
+ fieldPath: Array<string>,
42
+ compareOptions: CompareOptions = DEFAULT_COMPARE_OPTIONS
43
+ ): IndexInterface<TKey> | undefined {
36
44
  for (const index of indexes.values()) {
37
- if (index.matchesField(fieldPath)) {
45
+ if (
46
+ index.matchesField(fieldPath) &&
47
+ index.matchesCompareOptions(compareOptions)
48
+ ) {
49
+ if (!index.matchesDirection(compareOptions.direction)) {
50
+ return new ReverseIndex(index)
51
+ }
38
52
  return index
39
53
  }
40
54
  }
package/src/utils.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  * Generic utility functions
3
3
  */
4
4
 
5
+ import type { CompareOptions } from "./query/builder/types"
6
+
5
7
  interface TypedArray {
6
8
  length: number
7
9
  [index: number]: number
@@ -209,3 +211,9 @@ export function isTemporal(a: any): boolean {
209
211
  const tag = getStringTag(a)
210
212
  return typeof tag === `string` && temporalTypes.includes(tag)
211
213
  }
214
+
215
+ export const DEFAULT_COMPARE_OPTIONS: CompareOptions = {
216
+ direction: `asc`,
217
+ nulls: `first`,
218
+ stringSort: `locale`,
219
+ }