@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.
Files changed (282) hide show
  1. package/dist/cjs/collection/change-events.cjs.map +1 -1
  2. package/dist/cjs/collection/change-events.d.cts +3 -2
  3. package/dist/cjs/collection/changes.cjs +13 -4
  4. package/dist/cjs/collection/changes.cjs.map +1 -1
  5. package/dist/cjs/collection/changes.d.cts +10 -1
  6. package/dist/cjs/collection/cleanup-queue.cjs +89 -0
  7. package/dist/cjs/collection/cleanup-queue.cjs.map +1 -0
  8. package/dist/cjs/collection/cleanup-queue.d.cts +30 -0
  9. package/dist/cjs/collection/events.cjs +14 -0
  10. package/dist/cjs/collection/events.cjs.map +1 -1
  11. package/dist/cjs/collection/events.d.cts +39 -1
  12. package/dist/cjs/collection/index.cjs +66 -28
  13. package/dist/cjs/collection/index.cjs.map +1 -1
  14. package/dist/cjs/collection/index.d.cts +49 -36
  15. package/dist/cjs/collection/indexes.cjs +211 -62
  16. package/dist/cjs/collection/indexes.cjs.map +1 -1
  17. package/dist/cjs/collection/indexes.d.cts +27 -17
  18. package/dist/cjs/collection/lifecycle.cjs +5 -22
  19. package/dist/cjs/collection/lifecycle.cjs.map +1 -1
  20. package/dist/cjs/collection/lifecycle.d.cts +0 -1
  21. package/dist/cjs/collection/mutations.cjs +18 -0
  22. package/dist/cjs/collection/mutations.cjs.map +1 -1
  23. package/dist/cjs/collection/mutations.d.cts +1 -0
  24. package/dist/cjs/collection/state.cjs +381 -53
  25. package/dist/cjs/collection/state.cjs.map +1 -1
  26. package/dist/cjs/collection/state.d.cts +65 -1
  27. package/dist/cjs/collection/subscription.cjs +6 -0
  28. package/dist/cjs/collection/subscription.cjs.map +1 -1
  29. package/dist/cjs/collection/subscription.d.cts +4 -0
  30. package/dist/cjs/collection/sync.cjs +108 -1
  31. package/dist/cjs/collection/sync.cjs.map +1 -1
  32. package/dist/cjs/collection/sync.d.cts +2 -0
  33. package/dist/cjs/collection/transaction-metadata.cjs +5 -0
  34. package/dist/cjs/collection/transaction-metadata.cjs.map +1 -0
  35. package/dist/cjs/collection/transaction-metadata.d.cts +1 -0
  36. package/dist/cjs/errors.cjs +8 -0
  37. package/dist/cjs/errors.cjs.map +1 -1
  38. package/dist/cjs/errors.d.cts +3 -0
  39. package/dist/cjs/index.cjs +22 -4
  40. package/dist/cjs/index.cjs.map +1 -1
  41. package/dist/cjs/index.d.cts +11 -3
  42. package/dist/cjs/indexes/auto-index.cjs +13 -6
  43. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  44. package/dist/cjs/indexes/base-index.cjs +0 -3
  45. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  46. package/dist/cjs/indexes/base-index.d.cts +2 -6
  47. package/dist/cjs/indexes/basic-index.cjs +361 -0
  48. package/dist/cjs/indexes/basic-index.cjs.map +1 -0
  49. package/dist/cjs/indexes/basic-index.d.cts +102 -0
  50. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  51. package/dist/cjs/indexes/btree-index.d.cts +1 -1
  52. package/dist/cjs/indexes/index-options.d.cts +8 -9
  53. package/dist/cjs/indexes/index-registry.cjs +89 -0
  54. package/dist/cjs/indexes/index-registry.cjs.map +1 -0
  55. package/dist/cjs/indexes/index-registry.d.cts +61 -0
  56. package/dist/cjs/local-only.cjs +5 -0
  57. package/dist/cjs/local-only.cjs.map +1 -1
  58. package/dist/cjs/query/builder/functions.cjs +27 -11
  59. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  60. package/dist/cjs/query/builder/functions.d.cts +25 -3
  61. package/dist/cjs/query/builder/index.cjs +200 -39
  62. package/dist/cjs/query/builder/index.cjs.map +1 -1
  63. package/dist/cjs/query/builder/index.d.cts +4 -3
  64. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  65. package/dist/cjs/query/builder/ref-proxy.d.cts +14 -3
  66. package/dist/cjs/query/builder/types.d.cts +84 -19
  67. package/dist/cjs/query/compiler/evaluators.cjs +51 -0
  68. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  69. package/dist/cjs/query/compiler/group-by.cjs +100 -28
  70. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  71. package/dist/cjs/query/compiler/group-by.d.cts +4 -2
  72. package/dist/cjs/query/compiler/index.cjs +283 -11
  73. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  74. package/dist/cjs/query/compiler/index.d.cts +30 -2
  75. package/dist/cjs/query/compiler/order-by.cjs +29 -10
  76. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  77. package/dist/cjs/query/compiler/order-by.d.cts +1 -1
  78. package/dist/cjs/query/compiler/select.cjs +8 -0
  79. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  80. package/dist/cjs/query/index.d.cts +2 -1
  81. package/dist/cjs/query/ir.cjs +18 -1
  82. package/dist/cjs/query/ir.cjs.map +1 -1
  83. package/dist/cjs/query/ir.d.cts +21 -1
  84. package/dist/cjs/query/live/collection-config-builder.cjs +501 -5
  85. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  86. package/dist/cjs/query/live/collection-config-builder.d.cts +7 -0
  87. package/dist/cjs/query/live/types.d.cts +3 -3
  88. package/dist/cjs/query/live/utils.cjs +43 -3
  89. package/dist/cjs/query/live/utils.cjs.map +1 -1
  90. package/dist/cjs/query/live/utils.d.cts +1 -0
  91. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  92. package/dist/cjs/query/live-query-collection.d.cts +9 -6
  93. package/dist/cjs/query/query-once.cjs.map +1 -1
  94. package/dist/cjs/query/query-once.d.cts +7 -5
  95. package/dist/cjs/query/subset-dedupe.cjs +9 -3
  96. package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
  97. package/dist/cjs/types.d.cts +42 -8
  98. package/dist/cjs/utils/array-utils.cjs +27 -0
  99. package/dist/cjs/utils/array-utils.cjs.map +1 -0
  100. package/dist/cjs/utils/array-utils.d.cts +16 -0
  101. package/dist/cjs/utils/comparison.cjs +11 -0
  102. package/dist/cjs/utils/comparison.cjs.map +1 -1
  103. package/dist/cjs/utils/index-optimization.cjs +4 -0
  104. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  105. package/dist/cjs/utils.cjs +7 -9
  106. package/dist/cjs/utils.cjs.map +1 -1
  107. package/dist/cjs/utils.d.cts +6 -1
  108. package/dist/cjs/virtual-props.cjs +33 -0
  109. package/dist/cjs/virtual-props.cjs.map +1 -0
  110. package/dist/cjs/virtual-props.d.cts +196 -0
  111. package/dist/esm/collection/change-events.d.ts +3 -2
  112. package/dist/esm/collection/change-events.js.map +1 -1
  113. package/dist/esm/collection/changes.d.ts +10 -1
  114. package/dist/esm/collection/changes.js +13 -4
  115. package/dist/esm/collection/changes.js.map +1 -1
  116. package/dist/esm/collection/cleanup-queue.d.ts +30 -0
  117. package/dist/esm/collection/cleanup-queue.js +89 -0
  118. package/dist/esm/collection/cleanup-queue.js.map +1 -0
  119. package/dist/esm/collection/events.d.ts +39 -1
  120. package/dist/esm/collection/events.js +14 -0
  121. package/dist/esm/collection/events.js.map +1 -1
  122. package/dist/esm/collection/index.d.ts +49 -36
  123. package/dist/esm/collection/index.js +67 -29
  124. package/dist/esm/collection/index.js.map +1 -1
  125. package/dist/esm/collection/indexes.d.ts +27 -17
  126. package/dist/esm/collection/indexes.js +211 -62
  127. package/dist/esm/collection/indexes.js.map +1 -1
  128. package/dist/esm/collection/lifecycle.d.ts +0 -1
  129. package/dist/esm/collection/lifecycle.js +5 -22
  130. package/dist/esm/collection/lifecycle.js.map +1 -1
  131. package/dist/esm/collection/mutations.d.ts +1 -0
  132. package/dist/esm/collection/mutations.js +18 -0
  133. package/dist/esm/collection/mutations.js.map +1 -1
  134. package/dist/esm/collection/state.d.ts +65 -1
  135. package/dist/esm/collection/state.js +381 -53
  136. package/dist/esm/collection/state.js.map +1 -1
  137. package/dist/esm/collection/subscription.d.ts +4 -0
  138. package/dist/esm/collection/subscription.js +6 -0
  139. package/dist/esm/collection/subscription.js.map +1 -1
  140. package/dist/esm/collection/sync.d.ts +2 -0
  141. package/dist/esm/collection/sync.js +108 -1
  142. package/dist/esm/collection/sync.js.map +1 -1
  143. package/dist/esm/collection/transaction-metadata.d.ts +1 -0
  144. package/dist/esm/collection/transaction-metadata.js +5 -0
  145. package/dist/esm/collection/transaction-metadata.js.map +1 -0
  146. package/dist/esm/errors.d.ts +3 -0
  147. package/dist/esm/errors.js +8 -0
  148. package/dist/esm/errors.js.map +1 -1
  149. package/dist/esm/index.d.ts +11 -3
  150. package/dist/esm/index.js +25 -7
  151. package/dist/esm/index.js.map +1 -1
  152. package/dist/esm/indexes/auto-index.js +13 -6
  153. package/dist/esm/indexes/auto-index.js.map +1 -1
  154. package/dist/esm/indexes/base-index.d.ts +2 -6
  155. package/dist/esm/indexes/base-index.js +1 -4
  156. package/dist/esm/indexes/base-index.js.map +1 -1
  157. package/dist/esm/indexes/basic-index.d.ts +102 -0
  158. package/dist/esm/indexes/basic-index.js +361 -0
  159. package/dist/esm/indexes/basic-index.js.map +1 -0
  160. package/dist/esm/indexes/btree-index.d.ts +1 -1
  161. package/dist/esm/indexes/btree-index.js.map +1 -1
  162. package/dist/esm/indexes/index-options.d.ts +8 -9
  163. package/dist/esm/indexes/index-registry.d.ts +61 -0
  164. package/dist/esm/indexes/index-registry.js +89 -0
  165. package/dist/esm/indexes/index-registry.js.map +1 -0
  166. package/dist/esm/local-only.js +5 -0
  167. package/dist/esm/local-only.js.map +1 -1
  168. package/dist/esm/query/builder/functions.d.ts +25 -3
  169. package/dist/esm/query/builder/functions.js +27 -11
  170. package/dist/esm/query/builder/functions.js.map +1 -1
  171. package/dist/esm/query/builder/index.d.ts +4 -3
  172. package/dist/esm/query/builder/index.js +201 -40
  173. package/dist/esm/query/builder/index.js.map +1 -1
  174. package/dist/esm/query/builder/ref-proxy.d.ts +14 -3
  175. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  176. package/dist/esm/query/builder/types.d.ts +84 -19
  177. package/dist/esm/query/compiler/evaluators.js +51 -0
  178. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  179. package/dist/esm/query/compiler/group-by.d.ts +4 -2
  180. package/dist/esm/query/compiler/group-by.js +101 -29
  181. package/dist/esm/query/compiler/group-by.js.map +1 -1
  182. package/dist/esm/query/compiler/index.d.ts +30 -2
  183. package/dist/esm/query/compiler/index.js +285 -13
  184. package/dist/esm/query/compiler/index.js.map +1 -1
  185. package/dist/esm/query/compiler/order-by.d.ts +1 -1
  186. package/dist/esm/query/compiler/order-by.js +30 -11
  187. package/dist/esm/query/compiler/order-by.js.map +1 -1
  188. package/dist/esm/query/compiler/select.js +8 -0
  189. package/dist/esm/query/compiler/select.js.map +1 -1
  190. package/dist/esm/query/index.d.ts +2 -1
  191. package/dist/esm/query/ir.d.ts +21 -1
  192. package/dist/esm/query/ir.js +18 -1
  193. package/dist/esm/query/ir.js.map +1 -1
  194. package/dist/esm/query/live/collection-config-builder.d.ts +7 -0
  195. package/dist/esm/query/live/collection-config-builder.js +503 -7
  196. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  197. package/dist/esm/query/live/types.d.ts +3 -3
  198. package/dist/esm/query/live/utils.d.ts +1 -0
  199. package/dist/esm/query/live/utils.js +43 -3
  200. package/dist/esm/query/live/utils.js.map +1 -1
  201. package/dist/esm/query/live-query-collection.d.ts +9 -6
  202. package/dist/esm/query/live-query-collection.js.map +1 -1
  203. package/dist/esm/query/query-once.d.ts +7 -5
  204. package/dist/esm/query/query-once.js.map +1 -1
  205. package/dist/esm/query/subset-dedupe.js +9 -3
  206. package/dist/esm/query/subset-dedupe.js.map +1 -1
  207. package/dist/esm/types.d.ts +42 -8
  208. package/dist/esm/utils/array-utils.d.ts +16 -0
  209. package/dist/esm/utils/array-utils.js +27 -0
  210. package/dist/esm/utils/array-utils.js.map +1 -0
  211. package/dist/esm/utils/comparison.js +11 -0
  212. package/dist/esm/utils/comparison.js.map +1 -1
  213. package/dist/esm/utils/index-optimization.js +4 -0
  214. package/dist/esm/utils/index-optimization.js.map +1 -1
  215. package/dist/esm/utils.d.ts +6 -1
  216. package/dist/esm/utils.js +7 -9
  217. package/dist/esm/utils.js.map +1 -1
  218. package/dist/esm/virtual-props.d.ts +196 -0
  219. package/dist/esm/virtual-props.js +33 -0
  220. package/dist/esm/virtual-props.js.map +1 -0
  221. package/package.json +4 -3
  222. package/skills/db-core/SKILL.md +4 -2
  223. package/skills/db-core/collection-setup/SKILL.md +30 -11
  224. package/skills/db-core/collection-setup/references/electric-adapter.md +1 -1
  225. package/skills/db-core/collection-setup/references/powersync-adapter.md +4 -0
  226. package/skills/db-core/collection-setup/references/query-adapter.md +32 -0
  227. package/skills/db-core/custom-adapter/SKILL.md +58 -9
  228. package/skills/db-core/live-queries/SKILL.md +162 -2
  229. package/skills/db-core/mutations-optimistic/SKILL.md +1 -1
  230. package/skills/db-core/persistence/SKILL.md +241 -0
  231. package/skills/meta-framework/SKILL.md +1 -1
  232. package/src/collection/change-events.ts +13 -9
  233. package/src/collection/changes.ts +30 -7
  234. package/src/collection/cleanup-queue.ts +105 -0
  235. package/src/collection/events.ts +65 -0
  236. package/src/collection/index.ts +110 -45
  237. package/src/collection/indexes.ts +283 -76
  238. package/src/collection/lifecycle.ts +5 -26
  239. package/src/collection/mutations.ts +21 -0
  240. package/src/collection/state.ts +545 -71
  241. package/src/collection/subscription.ts +7 -0
  242. package/src/collection/sync.ts +137 -0
  243. package/src/collection/transaction-metadata.ts +1 -0
  244. package/src/errors.ts +9 -0
  245. package/src/index.ts +46 -3
  246. package/src/indexes/auto-index.ts +18 -8
  247. package/src/indexes/base-index.ts +2 -10
  248. package/src/indexes/basic-index.ts +507 -0
  249. package/src/indexes/btree-index.ts +1 -1
  250. package/src/indexes/index-options.ts +17 -37
  251. package/src/indexes/index-registry.ts +174 -0
  252. package/src/local-only.ts +7 -0
  253. package/src/query/builder/functions.ts +84 -7
  254. package/src/query/builder/index.ts +329 -9
  255. package/src/query/builder/ref-proxy.ts +22 -4
  256. package/src/query/builder/types.ts +257 -62
  257. package/src/query/compiler/evaluators.ts +57 -0
  258. package/src/query/compiler/group-by.ts +156 -35
  259. package/src/query/compiler/index.ts +445 -15
  260. package/src/query/compiler/order-by.ts +51 -12
  261. package/src/query/compiler/select.ts +9 -0
  262. package/src/query/index.ts +7 -0
  263. package/src/query/ir.ts +23 -2
  264. package/src/query/live/collection-config-builder.ts +809 -9
  265. package/src/query/live/types.ts +10 -4
  266. package/src/query/live/utils.ts +64 -3
  267. package/src/query/live-query-collection.ts +43 -18
  268. package/src/query/query-once.ts +31 -12
  269. package/src/query/subset-dedupe.ts +11 -7
  270. package/src/types.ts +49 -9
  271. package/src/utils/array-utils.ts +49 -0
  272. package/src/utils/comparison.ts +14 -0
  273. package/src/utils/index-optimization.ts +4 -0
  274. package/src/utils.ts +12 -9
  275. package/src/virtual-props.ts +282 -0
  276. package/dist/cjs/indexes/lazy-index.cjs +0 -190
  277. package/dist/cjs/indexes/lazy-index.cjs.map +0 -1
  278. package/dist/cjs/indexes/lazy-index.d.cts +0 -96
  279. package/dist/esm/indexes/lazy-index.d.ts +0 -96
  280. package/dist/esm/indexes/lazy-index.js +0 -190
  281. package/dist/esm/indexes/lazy-index.js.map +0 -1
  282. package/src/indexes/lazy-index.ts +0 -251
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Index Dev Mode - Helps developers identify when indexes would improve performance
3
+ *
4
+ * Dev mode suggestions are ON by default in non-production builds.
5
+ */
6
+
7
+ // Dev mode detection settings - ON by default in non-production
8
+ let devModeConfig: IndexDevModeConfig = {
9
+ enabled: true,
10
+ collectionSizeThreshold: 1000,
11
+ slowQueryThresholdMs: 10,
12
+ onSuggestion: null,
13
+ }
14
+
15
+ export interface IndexDevModeConfig {
16
+ /** Enable dev mode index suggestions */
17
+ enabled: boolean
18
+ /** Suggest indexes when collection has more than this many items */
19
+ collectionSizeThreshold: number
20
+ /** Suggest indexes when queries take longer than this (ms) */
21
+ slowQueryThresholdMs: number
22
+ /** Custom handler for index suggestions */
23
+ onSuggestion: ((suggestion: IndexSuggestion) => void) | null
24
+ }
25
+
26
+ export interface IndexSuggestion {
27
+ type: `collection-size` | `slow-query` | `frequent-field`
28
+ collectionId: string
29
+ fieldPath: Array<string>
30
+ message: string
31
+ collectionSize?: number
32
+ queryTimeMs?: number
33
+ queryCount?: number
34
+ }
35
+
36
+ // Track query patterns for dev mode
37
+ const queryPatterns = new Map<
38
+ string,
39
+ {
40
+ fieldPath: Array<string>
41
+ queryCount: number
42
+ totalTimeMs: number
43
+ avgTimeMs: number
44
+ }
45
+ >()
46
+
47
+ /**
48
+ * Configure dev mode for index suggestions
49
+ */
50
+ export function configureIndexDevMode(
51
+ config: Partial<IndexDevModeConfig>,
52
+ ): void {
53
+ devModeConfig = { ...devModeConfig, ...config }
54
+ }
55
+
56
+ /**
57
+ * Get current dev mode configuration
58
+ */
59
+ export function getIndexDevModeConfig(): IndexDevModeConfig {
60
+ return devModeConfig
61
+ }
62
+
63
+ /**
64
+ * Check if dev mode is enabled
65
+ */
66
+ export function isDevModeEnabled(): boolean {
67
+ return devModeConfig.enabled && process.env.NODE_ENV !== `production`
68
+ }
69
+
70
+ /**
71
+ * Emit an index suggestion (dev mode only)
72
+ */
73
+ export function emitIndexSuggestion(suggestion: IndexSuggestion): void {
74
+ if (!isDevModeEnabled()) return
75
+
76
+ if (devModeConfig.onSuggestion) {
77
+ try {
78
+ devModeConfig.onSuggestion(suggestion)
79
+ } catch {
80
+ // Don't let a buggy callback crash query execution
81
+ }
82
+ } else {
83
+ // Default: log to console with helpful formatting
84
+ console.warn(
85
+ `[TanStack DB] Index suggestion for "${suggestion.collectionId}":\n` +
86
+ ` ${suggestion.message}\n` +
87
+ ` Field: ${suggestion.fieldPath.join(`.`)}\n` +
88
+ ` Add index: collection.createIndex((row) => row.${suggestion.fieldPath.join(`.`)})`,
89
+ )
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Track a query for dev mode analysis
95
+ */
96
+ export function trackQuery(
97
+ collectionId: string,
98
+ fieldPath: Array<string>,
99
+ executionTimeMs: number,
100
+ ): void {
101
+ if (!isDevModeEnabled()) return
102
+
103
+ const key = `${collectionId}:${fieldPath.join(`.`)}`
104
+ const existing = queryPatterns.get(key)
105
+
106
+ if (existing) {
107
+ existing.queryCount++
108
+ existing.totalTimeMs += executionTimeMs
109
+ existing.avgTimeMs = existing.totalTimeMs / existing.queryCount
110
+ } else {
111
+ queryPatterns.set(key, {
112
+ fieldPath,
113
+ queryCount: 1,
114
+ totalTimeMs: executionTimeMs,
115
+ avgTimeMs: executionTimeMs,
116
+ })
117
+ }
118
+
119
+ // Check if we should suggest an index
120
+ const pattern = queryPatterns.get(key)!
121
+ if (pattern.avgTimeMs > devModeConfig.slowQueryThresholdMs) {
122
+ emitIndexSuggestion({
123
+ type: `slow-query`,
124
+ collectionId,
125
+ fieldPath,
126
+ message: `Queries on "${fieldPath.join(`.`)}" are slow (avg ${pattern.avgTimeMs.toFixed(1)}ms). Consider adding an index.`,
127
+ queryTimeMs: pattern.avgTimeMs,
128
+ queryCount: pattern.queryCount,
129
+ })
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Check collection size and suggest index if needed (dev mode)
135
+ */
136
+ export function checkCollectionSizeForIndex(
137
+ collectionId: string,
138
+ collectionSize: number,
139
+ fieldPath: Array<string>,
140
+ ): void {
141
+ if (!isDevModeEnabled()) return
142
+
143
+ if (collectionSize > devModeConfig.collectionSizeThreshold) {
144
+ emitIndexSuggestion({
145
+ type: `collection-size`,
146
+ collectionId,
147
+ fieldPath,
148
+ message: `Collection has ${collectionSize} items. Queries on "${fieldPath.join(`.`)}" may benefit from an index.`,
149
+ collectionSize,
150
+ })
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Clear query pattern tracking (useful for tests)
156
+ */
157
+ export function clearQueryPatterns(): void {
158
+ queryPatterns.clear()
159
+ }
160
+
161
+ /**
162
+ * Get query patterns (useful for debugging/testing)
163
+ */
164
+ export function getQueryPatterns(): Map<
165
+ string,
166
+ {
167
+ fieldPath: Array<string>
168
+ queryCount: number
169
+ totalTimeMs: number
170
+ avgTimeMs: number
171
+ }
172
+ > {
173
+ return new Map(queryPatterns)
174
+ }
package/src/local-only.ts CHANGED
@@ -314,9 +314,16 @@ function createLocalOnlySync<T extends object, TKey extends string | number>(
314
314
  syncWrite = write
315
315
  syncCommit = commit
316
316
  collection = params.collection
317
+ params.collection._state.isLocalOnly = true
317
318
 
318
319
  // Apply initial data if provided
319
320
  if (initialData && initialData.length > 0) {
321
+ // Mark initial data as local so $origin is 'local' for local-only collections
322
+ for (const item of initialData) {
323
+ const key = params.collection.getKeyFromItem(item)
324
+ params.collection._state.pendingLocalChanges.add(key)
325
+ }
326
+
320
327
  begin()
321
328
  initialData.forEach((item) => {
322
329
  write({
@@ -2,7 +2,13 @@ import { Aggregate, Func } from '../ir'
2
2
  import { toExpression } from './ref-proxy.js'
3
3
  import type { BasicExpression } from '../ir'
4
4
  import type { RefProxy } from './ref-proxy.js'
5
- import type { RefLeaf } from './types.js'
5
+ import type {
6
+ Context,
7
+ GetRawResult,
8
+ RefLeaf,
9
+ StringifiableScalar,
10
+ } from './types.js'
11
+ import type { QueryBuilder } from './index.js'
6
12
 
7
13
  type StringRef =
8
14
  | RefLeaf<string>
@@ -37,8 +43,20 @@ type ComparisonOperandPrimitive<T extends string | number | boolean> =
37
43
  | undefined
38
44
  | null
39
45
 
40
- // Helper type for any expression-like value
41
- type ExpressionLike = BasicExpression | RefProxy<any> | RefLeaf<any> | any
46
+ // Helper type for values that can be lowered to expressions.
47
+ type ExpressionLike =
48
+ | Aggregate
49
+ | BasicExpression
50
+ | RefProxy<any>
51
+ | RefLeaf<any>
52
+ | string
53
+ | number
54
+ | boolean
55
+ | bigint
56
+ | Date
57
+ | null
58
+ | undefined
59
+ | Array<unknown>
42
60
 
43
61
  // Helper type to extract the underlying type from various expression types
44
62
  type ExtractType<T> =
@@ -276,20 +294,61 @@ export function length<T extends ExpressionLike>(
276
294
  return new Func(`length`, [toExpression(arg)]) as NumericFunctionReturnType<T>
277
295
  }
278
296
 
297
+ export function concat<T extends StringifiableScalar>(
298
+ arg: ToArrayWrapper<T>,
299
+ ): ConcatToArrayWrapper<T>
300
+ export function concat(...args: Array<ExpressionLike>): BasicExpression<string>
279
301
  export function concat(
280
- ...args: Array<ExpressionLike>
281
- ): BasicExpression<string> {
302
+ ...args: Array<ExpressionLike | ToArrayWrapper<any>>
303
+ ): BasicExpression<string> | ConcatToArrayWrapper<any> {
304
+ const toArrayArg = args.find(
305
+ (arg): arg is ToArrayWrapper<any> => arg instanceof ToArrayWrapper,
306
+ )
307
+
308
+ if (toArrayArg) {
309
+ if (args.length !== 1) {
310
+ throw new Error(
311
+ `concat(toArray(...)) currently supports only a single toArray(...) argument`,
312
+ )
313
+ }
314
+ return new ConcatToArrayWrapper(toArrayArg.query)
315
+ }
316
+
282
317
  return new Func(
283
318
  `concat`,
284
319
  args.map((arg) => toExpression(arg)),
285
320
  )
286
321
  }
287
322
 
288
- export function coalesce(...args: Array<ExpressionLike>): BasicExpression<any> {
323
+ // Helper type for coalesce: extracts non-nullish value types from all args
324
+ type CoalesceArgTypes<T extends Array<ExpressionLike>> = {
325
+ [K in keyof T]: NonNullable<ExtractType<T[K]>>
326
+ }[number]
327
+
328
+ // Whether any arg in the tuple is statically guaranteed non-null (i.e., does not include null | undefined)
329
+ type HasGuaranteedNonNull<T extends Array<ExpressionLike>> = {
330
+ [K in keyof T]: null extends ExtractType<T[K]>
331
+ ? false
332
+ : undefined extends ExtractType<T[K]>
333
+ ? false
334
+ : true
335
+ }[number] extends false
336
+ ? false
337
+ : true
338
+
339
+ // coalesce() return type: union of all non-null arg types; null included unless a guaranteed non-null arg exists
340
+ type CoalesceReturnType<T extends Array<ExpressionLike>> =
341
+ HasGuaranteedNonNull<T> extends true
342
+ ? BasicExpression<CoalesceArgTypes<T>>
343
+ : BasicExpression<CoalesceArgTypes<T> | null>
344
+
345
+ export function coalesce<T extends [ExpressionLike, ...Array<ExpressionLike>]>(
346
+ ...args: T
347
+ ): CoalesceReturnType<T> {
289
348
  return new Func(
290
349
  `coalesce`,
291
350
  args.map((arg) => toExpression(arg)),
292
- )
351
+ ) as CoalesceReturnType<T>
293
352
  }
294
353
 
295
354
  export function add<T1 extends ExpressionLike, T2 extends ExpressionLike>(
@@ -376,3 +435,21 @@ export const operators = [
376
435
  ] as const
377
436
 
378
437
  export type OperatorName = (typeof operators)[number]
438
+
439
+ export class ToArrayWrapper<_T = unknown> {
440
+ declare readonly _type: `toArray`
441
+ declare readonly _result: _T
442
+ constructor(public readonly query: QueryBuilder<any>) {}
443
+ }
444
+
445
+ export class ConcatToArrayWrapper<_T = unknown> {
446
+ declare readonly _type: `concatToArray`
447
+ declare readonly _result: _T
448
+ constructor(public readonly query: QueryBuilder<any>) {}
449
+ }
450
+
451
+ export function toArray<TContext extends Context>(
452
+ query: QueryBuilder<TContext>,
453
+ ): ToArrayWrapper<GetRawResult<TContext>> {
454
+ return new ToArrayWrapper(query)
455
+ }