@tanstack/db 0.0.14 → 0.0.15

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 (198) hide show
  1. package/dist/cjs/collection.cjs +117 -104
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +18 -21
  4. package/dist/cjs/index.cjs +35 -13
  5. package/dist/cjs/index.cjs.map +1 -1
  6. package/dist/cjs/index.d.cts +0 -1
  7. package/dist/cjs/query/builder/functions.cjs +107 -0
  8. package/dist/cjs/query/builder/functions.cjs.map +1 -0
  9. package/dist/cjs/query/builder/functions.d.cts +38 -0
  10. package/dist/cjs/query/builder/index.cjs +499 -0
  11. package/dist/cjs/query/builder/index.cjs.map +1 -0
  12. package/dist/cjs/query/builder/index.d.cts +324 -0
  13. package/dist/cjs/query/builder/ref-proxy.cjs +96 -0
  14. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -0
  15. package/dist/cjs/query/builder/ref-proxy.d.cts +28 -0
  16. package/dist/cjs/query/builder/types.d.cts +80 -0
  17. package/dist/cjs/query/compiler/evaluators.cjs +261 -0
  18. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -0
  19. package/dist/cjs/query/compiler/evaluators.d.cts +11 -0
  20. package/dist/cjs/query/compiler/group-by.cjs +271 -0
  21. package/dist/cjs/query/compiler/group-by.cjs.map +1 -0
  22. package/dist/cjs/query/compiler/group-by.d.cts +7 -0
  23. package/dist/cjs/query/compiler/index.cjs +181 -0
  24. package/dist/cjs/query/compiler/index.cjs.map +1 -0
  25. package/dist/cjs/query/compiler/index.d.cts +15 -0
  26. package/dist/cjs/query/compiler/joins.cjs +116 -0
  27. package/dist/cjs/query/compiler/joins.cjs.map +1 -0
  28. package/dist/cjs/query/compiler/joins.d.cts +11 -0
  29. package/dist/cjs/query/compiler/order-by.cjs +89 -0
  30. package/dist/cjs/query/compiler/order-by.cjs.map +1 -0
  31. package/dist/cjs/query/compiler/order-by.d.cts +9 -0
  32. package/dist/cjs/query/compiler/select.cjs +57 -0
  33. package/dist/cjs/query/compiler/select.cjs.map +1 -0
  34. package/dist/cjs/query/compiler/select.d.cts +15 -0
  35. package/dist/cjs/query/index.d.cts +6 -5
  36. package/dist/cjs/query/ir.cjs +57 -0
  37. package/dist/cjs/query/ir.cjs.map +1 -0
  38. package/dist/cjs/query/ir.d.cts +81 -0
  39. package/dist/cjs/query/live-query-collection.cjs +224 -0
  40. package/dist/cjs/query/live-query-collection.cjs.map +1 -0
  41. package/dist/cjs/query/live-query-collection.d.cts +124 -0
  42. package/dist/cjs/transactions.cjs +20 -13
  43. package/dist/cjs/transactions.cjs.map +1 -1
  44. package/dist/cjs/transactions.d.cts +10 -1
  45. package/dist/cjs/types.d.cts +13 -0
  46. package/dist/esm/collection.d.ts +18 -21
  47. package/dist/esm/collection.js +118 -105
  48. package/dist/esm/collection.js.map +1 -1
  49. package/dist/esm/index.d.ts +0 -1
  50. package/dist/esm/index.js +34 -12
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/query/builder/functions.d.ts +38 -0
  53. package/dist/esm/query/builder/functions.js +107 -0
  54. package/dist/esm/query/builder/functions.js.map +1 -0
  55. package/dist/esm/query/builder/index.d.ts +324 -0
  56. package/dist/esm/query/builder/index.js +499 -0
  57. package/dist/esm/query/builder/index.js.map +1 -0
  58. package/dist/esm/query/builder/ref-proxy.d.ts +28 -0
  59. package/dist/esm/query/builder/ref-proxy.js +96 -0
  60. package/dist/esm/query/builder/ref-proxy.js.map +1 -0
  61. package/dist/esm/query/builder/types.d.ts +80 -0
  62. package/dist/esm/query/compiler/evaluators.d.ts +11 -0
  63. package/dist/esm/query/compiler/evaluators.js +261 -0
  64. package/dist/esm/query/compiler/evaluators.js.map +1 -0
  65. package/dist/esm/query/compiler/group-by.d.ts +7 -0
  66. package/dist/esm/query/compiler/group-by.js +271 -0
  67. package/dist/esm/query/compiler/group-by.js.map +1 -0
  68. package/dist/esm/query/compiler/index.d.ts +15 -0
  69. package/dist/esm/query/compiler/index.js +181 -0
  70. package/dist/esm/query/compiler/index.js.map +1 -0
  71. package/dist/esm/query/compiler/joins.d.ts +11 -0
  72. package/dist/esm/query/compiler/joins.js +116 -0
  73. package/dist/esm/query/compiler/joins.js.map +1 -0
  74. package/dist/esm/query/compiler/order-by.d.ts +9 -0
  75. package/dist/esm/query/compiler/order-by.js +89 -0
  76. package/dist/esm/query/compiler/order-by.js.map +1 -0
  77. package/dist/esm/query/compiler/select.d.ts +15 -0
  78. package/dist/esm/query/compiler/select.js +57 -0
  79. package/dist/esm/query/compiler/select.js.map +1 -0
  80. package/dist/esm/query/index.d.ts +6 -5
  81. package/dist/esm/query/ir.d.ts +81 -0
  82. package/dist/esm/query/ir.js +57 -0
  83. package/dist/esm/query/ir.js.map +1 -0
  84. package/dist/esm/query/live-query-collection.d.ts +124 -0
  85. package/dist/esm/query/live-query-collection.js +224 -0
  86. package/dist/esm/query/live-query-collection.js.map +1 -0
  87. package/dist/esm/transactions.d.ts +10 -1
  88. package/dist/esm/transactions.js +20 -13
  89. package/dist/esm/transactions.js.map +1 -1
  90. package/dist/esm/types.d.ts +13 -0
  91. package/package.json +3 -4
  92. package/src/collection.ts +152 -129
  93. package/src/index.ts +0 -1
  94. package/src/query/builder/functions.ts +267 -0
  95. package/src/query/builder/index.ts +648 -0
  96. package/src/query/builder/ref-proxy.ts +156 -0
  97. package/src/query/builder/types.ts +278 -0
  98. package/src/query/compiler/evaluators.ts +315 -0
  99. package/src/query/compiler/group-by.ts +428 -0
  100. package/src/query/compiler/index.ts +276 -0
  101. package/src/query/compiler/joins.ts +228 -0
  102. package/src/query/compiler/order-by.ts +139 -0
  103. package/src/query/compiler/select.ts +173 -0
  104. package/src/query/index.ts +64 -5
  105. package/src/query/ir.ts +128 -0
  106. package/src/query/live-query-collection.ts +509 -0
  107. package/src/transactions.ts +27 -16
  108. package/src/types.ts +15 -0
  109. package/dist/cjs/query/compiled-query.cjs +0 -160
  110. package/dist/cjs/query/compiled-query.cjs.map +0 -1
  111. package/dist/cjs/query/compiled-query.d.cts +0 -20
  112. package/dist/cjs/query/evaluators.cjs +0 -161
  113. package/dist/cjs/query/evaluators.cjs.map +0 -1
  114. package/dist/cjs/query/evaluators.d.cts +0 -14
  115. package/dist/cjs/query/extractors.cjs +0 -122
  116. package/dist/cjs/query/extractors.cjs.map +0 -1
  117. package/dist/cjs/query/extractors.d.cts +0 -22
  118. package/dist/cjs/query/functions.cjs +0 -152
  119. package/dist/cjs/query/functions.cjs.map +0 -1
  120. package/dist/cjs/query/functions.d.cts +0 -21
  121. package/dist/cjs/query/group-by.cjs +0 -88
  122. package/dist/cjs/query/group-by.cjs.map +0 -1
  123. package/dist/cjs/query/group-by.d.cts +0 -40
  124. package/dist/cjs/query/joins.cjs +0 -141
  125. package/dist/cjs/query/joins.cjs.map +0 -1
  126. package/dist/cjs/query/joins.d.cts +0 -14
  127. package/dist/cjs/query/order-by.cjs +0 -185
  128. package/dist/cjs/query/order-by.cjs.map +0 -1
  129. package/dist/cjs/query/order-by.d.cts +0 -3
  130. package/dist/cjs/query/pipeline-compiler.cjs +0 -89
  131. package/dist/cjs/query/pipeline-compiler.cjs.map +0 -1
  132. package/dist/cjs/query/pipeline-compiler.d.cts +0 -10
  133. package/dist/cjs/query/query-builder.cjs +0 -307
  134. package/dist/cjs/query/query-builder.cjs.map +0 -1
  135. package/dist/cjs/query/query-builder.d.cts +0 -225
  136. package/dist/cjs/query/schema.d.cts +0 -100
  137. package/dist/cjs/query/select.cjs +0 -130
  138. package/dist/cjs/query/select.cjs.map +0 -1
  139. package/dist/cjs/query/select.d.cts +0 -3
  140. package/dist/cjs/query/types.d.cts +0 -189
  141. package/dist/cjs/query/utils.cjs +0 -154
  142. package/dist/cjs/query/utils.cjs.map +0 -1
  143. package/dist/cjs/query/utils.d.cts +0 -37
  144. package/dist/cjs/utils.cjs +0 -17
  145. package/dist/cjs/utils.cjs.map +0 -1
  146. package/dist/cjs/utils.d.cts +0 -3
  147. package/dist/esm/query/compiled-query.d.ts +0 -20
  148. package/dist/esm/query/compiled-query.js +0 -160
  149. package/dist/esm/query/compiled-query.js.map +0 -1
  150. package/dist/esm/query/evaluators.d.ts +0 -14
  151. package/dist/esm/query/evaluators.js +0 -161
  152. package/dist/esm/query/evaluators.js.map +0 -1
  153. package/dist/esm/query/extractors.d.ts +0 -22
  154. package/dist/esm/query/extractors.js +0 -122
  155. package/dist/esm/query/extractors.js.map +0 -1
  156. package/dist/esm/query/functions.d.ts +0 -21
  157. package/dist/esm/query/functions.js +0 -152
  158. package/dist/esm/query/functions.js.map +0 -1
  159. package/dist/esm/query/group-by.d.ts +0 -40
  160. package/dist/esm/query/group-by.js +0 -88
  161. package/dist/esm/query/group-by.js.map +0 -1
  162. package/dist/esm/query/joins.d.ts +0 -14
  163. package/dist/esm/query/joins.js +0 -141
  164. package/dist/esm/query/joins.js.map +0 -1
  165. package/dist/esm/query/order-by.d.ts +0 -3
  166. package/dist/esm/query/order-by.js +0 -185
  167. package/dist/esm/query/order-by.js.map +0 -1
  168. package/dist/esm/query/pipeline-compiler.d.ts +0 -10
  169. package/dist/esm/query/pipeline-compiler.js +0 -89
  170. package/dist/esm/query/pipeline-compiler.js.map +0 -1
  171. package/dist/esm/query/query-builder.d.ts +0 -225
  172. package/dist/esm/query/query-builder.js +0 -307
  173. package/dist/esm/query/query-builder.js.map +0 -1
  174. package/dist/esm/query/schema.d.ts +0 -100
  175. package/dist/esm/query/select.d.ts +0 -3
  176. package/dist/esm/query/select.js +0 -130
  177. package/dist/esm/query/select.js.map +0 -1
  178. package/dist/esm/query/types.d.ts +0 -189
  179. package/dist/esm/query/utils.d.ts +0 -37
  180. package/dist/esm/query/utils.js +0 -154
  181. package/dist/esm/query/utils.js.map +0 -1
  182. package/dist/esm/utils.d.ts +0 -3
  183. package/dist/esm/utils.js +0 -17
  184. package/dist/esm/utils.js.map +0 -1
  185. package/src/query/compiled-query.ts +0 -234
  186. package/src/query/evaluators.ts +0 -250
  187. package/src/query/extractors.ts +0 -214
  188. package/src/query/functions.ts +0 -297
  189. package/src/query/group-by.ts +0 -139
  190. package/src/query/joins.ts +0 -260
  191. package/src/query/order-by.ts +0 -264
  192. package/src/query/pipeline-compiler.ts +0 -149
  193. package/src/query/query-builder.ts +0 -902
  194. package/src/query/schema.ts +0 -268
  195. package/src/query/select.ts +0 -208
  196. package/src/query/types.ts +0 -418
  197. package/src/query/utils.ts +0 -245
  198. package/src/utils.ts +0 -15
@@ -1,214 +0,0 @@
1
- import { evaluateFunction, isFunctionCall } from "./functions.js"
2
- import type { AllowedFunctionName, ConditionOperand } from "./schema.js"
3
-
4
- /**
5
- * Extracts a value from a nested row structure
6
- * @param namespacedRow The nested row structure
7
- * @param columnRef The column reference (may include table.column format)
8
- * @param mainTableAlias The main table alias to check first for columns without table reference
9
- * @param joinedTableAlias The joined table alias to check second for columns without table reference
10
- * @returns The extracted value or undefined if not found
11
- */
12
- export function extractValueFromNamespacedRow(
13
- namespacedRow: Record<string, unknown>,
14
- columnRef: string,
15
- mainTableAlias?: string,
16
- joinedTableAlias?: string
17
- ): unknown {
18
- // Check if it's a table.column reference
19
- if (columnRef.includes(`.`)) {
20
- const [tableAlias, colName] = columnRef.split(`.`) as [string, string]
21
-
22
- // Get the table data
23
- const tableData = namespacedRow[tableAlias] as
24
- | Record<string, unknown>
25
- | null
26
- | undefined
27
-
28
- if (!tableData) {
29
- return null
30
- }
31
-
32
- // Return the column value from that table
33
- const value = tableData[colName]
34
- return value
35
- } else {
36
- // If no table is specified, first try to find in the main table if provided
37
- if (mainTableAlias && namespacedRow[mainTableAlias]) {
38
- const mainTableData = namespacedRow[mainTableAlias] as Record<
39
- string,
40
- unknown
41
- >
42
- if (typeof mainTableData === `object` && columnRef in mainTableData) {
43
- return mainTableData[columnRef]
44
- }
45
- }
46
-
47
- // Then try the joined table if provided
48
- if (joinedTableAlias && namespacedRow[joinedTableAlias]) {
49
- const joinedTableData = namespacedRow[joinedTableAlias] as Record<
50
- string,
51
- unknown
52
- >
53
- if (typeof joinedTableData === `object` && columnRef in joinedTableData) {
54
- return joinedTableData[columnRef]
55
- }
56
- }
57
-
58
- // If not found in main or joined table, try to find the column in any table
59
- for (const [_tableAlias, tableData] of Object.entries(namespacedRow)) {
60
- if (
61
- tableData &&
62
- typeof tableData === `object` &&
63
- columnRef in (tableData as Record<string, unknown>)
64
- ) {
65
- return (tableData as Record<string, unknown>)[columnRef]
66
- }
67
- }
68
- return undefined
69
- }
70
- }
71
-
72
- /**
73
- * Evaluates an operand against a nested row structure
74
- */
75
- export function evaluateOperandOnNamespacedRow(
76
- namespacedRow: Record<string, unknown>,
77
- operand: ConditionOperand,
78
- mainTableAlias?: string,
79
- joinedTableAlias?: string
80
- ): unknown {
81
- // Handle column references
82
- if (typeof operand === `string` && operand.startsWith(`@`)) {
83
- const columnRef = operand.substring(1)
84
- return extractValueFromNamespacedRow(
85
- namespacedRow,
86
- columnRef,
87
- mainTableAlias,
88
- joinedTableAlias
89
- )
90
- }
91
-
92
- // Handle explicit column references
93
- if (operand && typeof operand === `object` && `col` in operand) {
94
- const colRef = (operand as { col: unknown }).col
95
-
96
- if (typeof colRef === `string`) {
97
- // First try to extract from nested row structure
98
- const nestedValue = extractValueFromNamespacedRow(
99
- namespacedRow,
100
- colRef,
101
- mainTableAlias,
102
- joinedTableAlias
103
- )
104
-
105
- // If not found in nested structure, check if it's a direct property of the row
106
- // This is important for HAVING clauses that reference aggregated values
107
- if (nestedValue === undefined && colRef in namespacedRow) {
108
- return namespacedRow[colRef]
109
- }
110
-
111
- return nestedValue
112
- }
113
-
114
- return undefined
115
- }
116
-
117
- // Handle function calls
118
- if (operand && typeof operand === `object` && isFunctionCall(operand)) {
119
- // Get the function name (the only key in the object)
120
- const functionName = Object.keys(operand)[0] as AllowedFunctionName
121
- // Get the arguments using type assertion with specific function name
122
- const args = (operand as any)[functionName]
123
-
124
- // If the arguments are a reference or another expression, evaluate them first
125
- const evaluatedArgs = Array.isArray(args)
126
- ? args.map((arg) =>
127
- evaluateOperandOnNamespacedRow(
128
- namespacedRow,
129
- arg as ConditionOperand,
130
- mainTableAlias,
131
- joinedTableAlias
132
- )
133
- )
134
- : evaluateOperandOnNamespacedRow(
135
- namespacedRow,
136
- args as ConditionOperand,
137
- mainTableAlias,
138
- joinedTableAlias
139
- )
140
-
141
- // Call the function with the evaluated arguments
142
- return evaluateFunction(
143
- functionName,
144
- evaluatedArgs as ConditionOperand | Array<ConditionOperand>
145
- )
146
- }
147
-
148
- // Handle explicit literals
149
- if (operand && typeof operand === `object` && `value` in operand) {
150
- return (operand as { value: unknown }).value
151
- }
152
-
153
- // Handle literal values
154
- return operand
155
- }
156
-
157
- /**
158
- * Extracts a join key value from a row based on the operand
159
- * @param row The data row (not nested)
160
- * @param operand The operand to extract the key from
161
- * @param defaultTableAlias The default table alias
162
- * @returns The extracted key value
163
- */
164
- export function extractJoinKey<T extends Record<string, unknown>>(
165
- row: T,
166
- operand: ConditionOperand,
167
- defaultTableAlias?: string
168
- ): unknown {
169
- let keyValue: unknown
170
-
171
- // Handle column references (e.g., "@orders.id" or "@id")
172
- if (typeof operand === `string` && operand.startsWith(`@`)) {
173
- const columnRef = operand.substring(1)
174
-
175
- // If it contains a dot, extract the table and column
176
- if (columnRef.includes(`.`)) {
177
- const [tableAlias, colName] = columnRef.split(`.`) as [string, string]
178
- // If this is referencing the current table, extract from row directly
179
- if (tableAlias === defaultTableAlias) {
180
- keyValue = row[colName]
181
- } else {
182
- // This might be a column from another table, return undefined
183
- keyValue = undefined
184
- }
185
- } else {
186
- // No table specified, look directly in the row
187
- keyValue = row[columnRef]
188
- }
189
- } else if (operand && typeof operand === `object` && `col` in operand) {
190
- // Handle explicit column references like { col: "orders.id" } or { col: "id" }
191
- const colRef = (operand as { col: unknown }).col
192
-
193
- if (typeof colRef === `string`) {
194
- if (colRef.includes(`.`)) {
195
- const [tableAlias, colName] = colRef.split(`.`) as [string, string]
196
- // If this is referencing the current table, extract from row directly
197
- if (tableAlias === defaultTableAlias) {
198
- keyValue = row[colName]
199
- } else {
200
- // This might be a column from another table, return undefined
201
- keyValue = undefined
202
- }
203
- } else {
204
- // No table specified, look directly in the row
205
- keyValue = row[colRef]
206
- }
207
- }
208
- } else {
209
- // Handle literals or other types
210
- keyValue = operand
211
- }
212
-
213
- return keyValue
214
- }
@@ -1,297 +0,0 @@
1
- import type { AllowedFunctionName } from "./schema.js"
2
-
3
- /**
4
- * Type for function implementations
5
- */
6
- type FunctionImplementation = (arg: unknown) => unknown
7
-
8
- /**
9
- * Converts a string to uppercase
10
- */
11
- function upperFunction(arg: unknown): string {
12
- if (typeof arg !== `string`) {
13
- throw new Error(`UPPER function expects a string argument`)
14
- }
15
- return arg.toUpperCase()
16
- }
17
-
18
- /**
19
- * Converts a string to lowercase
20
- */
21
- function lowerFunction(arg: unknown): string {
22
- if (typeof arg !== `string`) {
23
- throw new Error(`LOWER function expects a string argument`)
24
- }
25
- return arg.toLowerCase()
26
- }
27
-
28
- /**
29
- * Returns the length of a string or array
30
- */
31
- function lengthFunction(arg: unknown): number {
32
- if (typeof arg === `string` || Array.isArray(arg)) {
33
- return arg.length
34
- }
35
-
36
- throw new Error(`LENGTH function expects a string or array argument`)
37
- }
38
-
39
- /**
40
- * Concatenates multiple strings
41
- */
42
- function concatFunction(arg: unknown): string {
43
- if (!Array.isArray(arg)) {
44
- throw new Error(`CONCAT function expects an array of string arguments`)
45
- }
46
-
47
- if (arg.length === 0) {
48
- return ``
49
- }
50
-
51
- // Check that all arguments are strings
52
- for (let i = 0; i < arg.length; i++) {
53
- if (arg[i] !== null && arg[i] !== undefined && typeof arg[i] !== `string`) {
54
- throw new Error(
55
- `CONCAT function expects all arguments to be strings, but argument at position ${i} is ${typeof arg[i]}`
56
- )
57
- }
58
- }
59
-
60
- // Concatenate strings, treating null and undefined as empty strings
61
- return arg
62
- .map((str) => (str === null || str === undefined ? `` : str))
63
- .join(``)
64
- }
65
-
66
- /**
67
- * Returns the first non-null, non-undefined value from an array
68
- */
69
- function coalesceFunction(arg: unknown): unknown {
70
- if (!Array.isArray(arg)) {
71
- throw new Error(`COALESCE function expects an array of arguments`)
72
- }
73
-
74
- if (arg.length === 0) {
75
- return null
76
- }
77
-
78
- // Return the first non-null, non-undefined value
79
- for (const value of arg) {
80
- if (value !== null && value !== undefined) {
81
- return value
82
- }
83
- }
84
-
85
- // If all values were null or undefined, return null
86
- return null
87
- }
88
-
89
- /**
90
- * Creates or converts a value to a Date object
91
- */
92
- function dateFunction(arg: unknown): Date | null {
93
- // If the argument is already a Date, return it
94
- if (arg instanceof Date) {
95
- return arg
96
- }
97
-
98
- // If the argument is null or undefined, return null
99
- if (arg === null || arg === undefined) {
100
- return null
101
- }
102
-
103
- // Handle string and number conversions
104
- if (typeof arg === `string` || typeof arg === `number`) {
105
- const date = new Date(arg)
106
-
107
- // Check if the date is valid
108
- if (isNaN(date.getTime())) {
109
- throw new Error(`DATE function could not parse "${arg}" as a valid date`)
110
- }
111
-
112
- return date
113
- }
114
-
115
- throw new Error(`DATE function expects a string, number, or Date argument`)
116
- }
117
-
118
- /**
119
- * Extracts a value from a JSON string or object using a path.
120
- * Similar to PostgreSQL's json_extract_path function.
121
- *
122
- * Usage: JSON_EXTRACT([jsonInput, 'path', 'to', 'property'])
123
- * Example: JSON_EXTRACT(['{"user": {"name": "John"}}', 'user', 'name']) returns "John"
124
- */
125
- function jsonExtractFunction(arg: unknown): unknown {
126
- if (!Array.isArray(arg) || arg.length < 1) {
127
- throw new Error(
128
- `JSON_EXTRACT function expects an array with at least one element [jsonInput, ...pathElements]`
129
- )
130
- }
131
-
132
- const [jsonInput, ...pathElements] = arg
133
-
134
- // Handle null or undefined input
135
- if (jsonInput === null || jsonInput === undefined) {
136
- return null
137
- }
138
-
139
- // Parse JSON if it's a string
140
- let jsonData: any
141
-
142
- if (typeof jsonInput === `string`) {
143
- try {
144
- jsonData = JSON.parse(jsonInput)
145
- } catch (error) {
146
- throw new Error(
147
- `JSON_EXTRACT function could not parse JSON string: ${error instanceof Error ? error.message : String(error)}`
148
- )
149
- }
150
- } else if (typeof jsonInput === `object`) {
151
- // If already an object, use it directly
152
- jsonData = jsonInput
153
- } else {
154
- throw new Error(
155
- `JSON_EXTRACT function expects a JSON string or object as the first argument`
156
- )
157
- }
158
-
159
- // If no path elements, return the parsed JSON
160
- if (pathElements.length === 0) {
161
- return jsonData
162
- }
163
-
164
- // Navigate through the path elements
165
- let current = jsonData
166
-
167
- for (let i = 0; i < pathElements.length; i++) {
168
- const pathElement = pathElements[i]
169
-
170
- // Path elements should be strings
171
- if (typeof pathElement !== `string`) {
172
- throw new Error(
173
- `JSON_EXTRACT function expects path elements to be strings, but element at position ${i + 1} is ${typeof pathElement}`
174
- )
175
- }
176
-
177
- // If current node is null or undefined, or not an object, we can't navigate further
178
- if (
179
- current === null ||
180
- current === undefined ||
181
- typeof current !== `object`
182
- ) {
183
- return null
184
- }
185
-
186
- // Access property
187
- current = current[pathElement]
188
- }
189
-
190
- // Return null instead of undefined for consistency
191
- return current === undefined ? null : current
192
- }
193
-
194
- /**
195
- * Placeholder function for ORDER_INDEX
196
- * This function doesn't do anything when called directly, as the actual index
197
- * is provided by the orderBy operator during query execution.
198
- * The argument can be 'numeric', 'fractional', or any truthy value (defaults to 'numeric')
199
- */
200
- function orderIndexFunction(arg: unknown): null {
201
- // This is just a placeholder - the actual index is provided by the orderBy operator
202
- // The function validates that the argument is one of the expected values
203
- if (
204
- arg !== `numeric` &&
205
- arg !== `fractional` &&
206
- arg !== true &&
207
- arg !== `default`
208
- ) {
209
- throw new Error(
210
- `ORDER_INDEX function expects "numeric", "fractional", "default", or true as argument`
211
- )
212
- }
213
- return null
214
- }
215
-
216
- /**
217
- * Map of function names to their implementations
218
- */
219
- const functionImplementations: Record<
220
- AllowedFunctionName,
221
- FunctionImplementation
222
- > = {
223
- // Map function names to their implementation functions
224
- DATE: dateFunction,
225
- JSON_EXTRACT: jsonExtractFunction,
226
- JSON_EXTRACT_PATH: jsonExtractFunction, // Alias for JSON_EXTRACT
227
- UPPER: upperFunction,
228
- LOWER: lowerFunction,
229
- COALESCE: coalesceFunction,
230
- CONCAT: concatFunction,
231
- LENGTH: lengthFunction,
232
- ORDER_INDEX: orderIndexFunction,
233
- }
234
-
235
- /**
236
- * Evaluates a function call with the given name and arguments
237
- * @param functionName The name of the function to evaluate
238
- * @param arg The arguments to pass to the function
239
- * @returns The result of the function call
240
- */
241
- export function evaluateFunction(
242
- functionName: AllowedFunctionName,
243
- arg: unknown
244
- ): unknown {
245
- const implementation = functionImplementations[functionName] as
246
- | FunctionImplementation
247
- | undefined // Double check that the implementation is defined
248
-
249
- if (!implementation) {
250
- throw new Error(`Unknown function: ${functionName}`)
251
- }
252
- return implementation(arg)
253
- }
254
-
255
- /**
256
- * Determines if an object is a function call
257
- * @param obj The object to check
258
- * @returns True if the object is a function call, false otherwise
259
- */
260
- export function isFunctionCall(obj: unknown): boolean {
261
- if (!obj || typeof obj !== `object`) {
262
- return false
263
- }
264
-
265
- const keys = Object.keys(obj)
266
- if (keys.length !== 1) {
267
- return false
268
- }
269
-
270
- const functionName = keys[0] as string
271
-
272
- // Check if the key is one of the allowed function names
273
- return Object.keys(functionImplementations).includes(functionName)
274
- }
275
-
276
- /**
277
- * Extracts the function name and argument from a function call object.
278
- */
279
- export function extractFunctionCall(obj: Record<string, unknown>): {
280
- functionName: AllowedFunctionName
281
- argument: unknown
282
- } {
283
- const keys = Object.keys(obj)
284
- if (keys.length !== 1) {
285
- throw new Error(`Invalid function call: object must have exactly one key`)
286
- }
287
-
288
- const functionName = keys[0] as AllowedFunctionName
289
- if (!Object.keys(functionImplementations).includes(functionName)) {
290
- throw new Error(`Invalid function name: ${functionName}`)
291
- }
292
-
293
- return {
294
- functionName,
295
- argument: obj[functionName],
296
- }
297
- }
@@ -1,139 +0,0 @@
1
- import { groupBy, groupByOperators } from "@electric-sql/d2mini"
2
- import {
3
- evaluateOperandOnNamespacedRow,
4
- extractValueFromNamespacedRow,
5
- } from "./extractors"
6
- import { isAggregateFunctionCall } from "./utils"
7
- import type { ConditionOperand, FunctionCall, Query } from "./schema"
8
- import type { NamespacedAndKeyedStream } from "../types.js"
9
-
10
- const { sum, count, avg, min, max, median, mode } = groupByOperators
11
-
12
- /**
13
- * Process the groupBy clause in a D2QL query
14
- */
15
- export function processGroupBy(
16
- pipeline: NamespacedAndKeyedStream,
17
- query: Query,
18
- mainTableAlias: string
19
- ) {
20
- // Normalize groupBy to an array of column references
21
- const groupByColumns = Array.isArray(query.groupBy)
22
- ? query.groupBy
23
- : [query.groupBy]
24
-
25
- // Create a key extractor function for the groupBy operator
26
- const keyExtractor = ([_oldKey, namespacedRow]: [
27
- string,
28
- Record<string, unknown>,
29
- ]) => {
30
- const key: Record<string, unknown> = {}
31
-
32
- // Extract each groupBy column value
33
- for (const column of groupByColumns) {
34
- if (typeof column === `string` && (column as string).startsWith(`@`)) {
35
- const columnRef = (column as string).substring(1)
36
- const columnName = columnRef.includes(`.`)
37
- ? columnRef.split(`.`)[1]
38
- : columnRef
39
-
40
- key[columnName!] = extractValueFromNamespacedRow(
41
- namespacedRow,
42
- columnRef,
43
- mainTableAlias
44
- )
45
- }
46
- }
47
-
48
- return key
49
- }
50
-
51
- // Create aggregate functions for any aggregated columns in the SELECT clause
52
- const aggregates: Record<string, any> = {}
53
-
54
- if (!query.select) {
55
- throw new Error(`SELECT clause is required for GROUP BY`)
56
- }
57
-
58
- // Scan the SELECT clause for aggregate functions
59
- for (const item of query.select) {
60
- if (typeof item === `object`) {
61
- for (const [alias, expr] of Object.entries(item)) {
62
- if (typeof expr === `object` && isAggregateFunctionCall(expr)) {
63
- // Get the function name (the only key in the object)
64
- const functionName = Object.keys(expr)[0]
65
- // Get the column reference or expression to aggregate
66
- const columnRef = (expr as FunctionCall)[
67
- functionName as keyof FunctionCall
68
- ]
69
-
70
- // Add the aggregate function to our aggregates object
71
- aggregates[alias] = getAggregateFunction(
72
- functionName!,
73
- columnRef,
74
- mainTableAlias
75
- )
76
- }
77
- }
78
- }
79
- }
80
-
81
- // Apply the groupBy operator if we have any aggregates
82
- if (Object.keys(aggregates).length > 0) {
83
- pipeline = pipeline.pipe(groupBy(keyExtractor, aggregates))
84
- }
85
-
86
- return pipeline
87
- }
88
-
89
- /**
90
- * Helper function to get an aggregate function based on the function name
91
- */
92
- export function getAggregateFunction(
93
- functionName: string,
94
- columnRef: string | ConditionOperand,
95
- mainTableAlias: string
96
- ) {
97
- // Create a value extractor function for the column to aggregate
98
- const valueExtractor = ([_oldKey, namespacedRow]: [
99
- string,
100
- Record<string, unknown>,
101
- ]) => {
102
- let value: unknown
103
- if (typeof columnRef === `string` && columnRef.startsWith(`@`)) {
104
- value = extractValueFromNamespacedRow(
105
- namespacedRow,
106
- columnRef.substring(1),
107
- mainTableAlias
108
- )
109
- } else {
110
- value = evaluateOperandOnNamespacedRow(
111
- namespacedRow,
112
- columnRef as ConditionOperand,
113
- mainTableAlias
114
- )
115
- }
116
- // Ensure we return a number for aggregate functions
117
- return typeof value === `number` ? value : 0
118
- }
119
-
120
- // Return the appropriate aggregate function
121
- switch (functionName.toUpperCase()) {
122
- case `SUM`:
123
- return sum(valueExtractor)
124
- case `COUNT`:
125
- return count() // count() doesn't need a value extractor
126
- case `AVG`:
127
- return avg(valueExtractor)
128
- case `MIN`:
129
- return min(valueExtractor)
130
- case `MAX`:
131
- return max(valueExtractor)
132
- case `MEDIAN`:
133
- return median(valueExtractor)
134
- case `MODE`:
135
- return mode(valueExtractor)
136
- default:
137
- throw new Error(`Unsupported aggregate function: ${functionName}`)
138
- }
139
- }