metal-orm 1.0.14 → 1.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 (115) hide show
  1. package/README.md +40 -45
  2. package/dist/decorators/index.cjs +1600 -27
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +6 -2
  5. package/dist/decorators/index.d.ts +6 -2
  6. package/dist/decorators/index.js +1599 -27
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +4608 -3429
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +511 -159
  11. package/dist/index.d.ts +511 -159
  12. package/dist/index.js +4526 -3415
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-CCp1oz9p.d.cts → select-Bkv8g8u_.d.cts} +193 -67
  15. package/dist/{select-CCp1oz9p.d.ts → select-Bkv8g8u_.d.ts} +193 -67
  16. package/package.json +1 -1
  17. package/src/codegen/typescript.ts +38 -35
  18. package/src/core/ast/adapters.ts +21 -0
  19. package/src/core/ast/aggregate-functions.ts +13 -13
  20. package/src/core/ast/builders.ts +56 -43
  21. package/src/core/ast/expression-builders.ts +34 -34
  22. package/src/core/ast/expression-nodes.ts +18 -16
  23. package/src/core/ast/expression-visitor.ts +122 -69
  24. package/src/core/ast/expression.ts +6 -4
  25. package/src/core/ast/join-metadata.ts +15 -0
  26. package/src/core/ast/join-node.ts +22 -20
  27. package/src/core/ast/join.ts +5 -5
  28. package/src/core/ast/query.ts +52 -88
  29. package/src/core/ast/types.ts +20 -0
  30. package/src/core/ast/window-functions.ts +55 -55
  31. package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
  32. package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
  33. package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
  34. package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
  35. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
  36. package/src/core/ddl/introspect/catalogs/index.ts +1 -0
  37. package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
  38. package/src/core/ddl/introspect/context.ts +9 -0
  39. package/src/core/ddl/introspect/functions/postgres.ts +26 -0
  40. package/src/core/ddl/introspect/mssql.ts +149 -149
  41. package/src/core/ddl/introspect/mysql.ts +99 -99
  42. package/src/core/ddl/introspect/postgres.ts +245 -154
  43. package/src/core/ddl/introspect/registry.ts +26 -0
  44. package/src/core/ddl/introspect/run-select.ts +25 -0
  45. package/src/core/ddl/introspect/sqlite.ts +7 -7
  46. package/src/core/ddl/introspect/types.ts +23 -19
  47. package/src/core/ddl/introspect/utils.ts +1 -1
  48. package/src/core/ddl/naming-strategy.ts +10 -0
  49. package/src/core/ddl/schema-dialect.ts +41 -0
  50. package/src/core/ddl/schema-diff.ts +211 -179
  51. package/src/core/ddl/schema-generator.ts +16 -90
  52. package/src/core/ddl/schema-introspect.ts +25 -32
  53. package/src/core/ddl/schema-plan-executor.ts +17 -0
  54. package/src/core/ddl/schema-types.ts +46 -39
  55. package/src/core/ddl/sql-writing.ts +170 -0
  56. package/src/core/dialect/abstract.ts +144 -126
  57. package/src/core/dialect/base/cte-compiler.ts +33 -0
  58. package/src/core/dialect/base/function-table-formatter.ts +132 -0
  59. package/src/core/dialect/base/groupby-compiler.ts +21 -0
  60. package/src/core/dialect/base/join-compiler.ts +26 -0
  61. package/src/core/dialect/base/orderby-compiler.ts +21 -0
  62. package/src/core/dialect/base/pagination-strategy.ts +32 -0
  63. package/src/core/dialect/base/returning-strategy.ts +56 -0
  64. package/src/core/dialect/base/sql-dialect.ts +181 -204
  65. package/src/core/dialect/dialect-factory.ts +91 -0
  66. package/src/core/dialect/mssql/functions.ts +101 -0
  67. package/src/core/dialect/mssql/index.ts +128 -126
  68. package/src/core/dialect/mysql/functions.ts +101 -0
  69. package/src/core/dialect/mysql/index.ts +20 -18
  70. package/src/core/dialect/postgres/functions.ts +95 -0
  71. package/src/core/dialect/postgres/index.ts +30 -28
  72. package/src/core/dialect/sqlite/functions.ts +115 -0
  73. package/src/core/dialect/sqlite/index.ts +30 -28
  74. package/src/core/driver/database-driver.ts +11 -0
  75. package/src/core/driver/mssql-driver.ts +20 -0
  76. package/src/core/driver/mysql-driver.ts +20 -0
  77. package/src/core/driver/postgres-driver.ts +20 -0
  78. package/src/core/driver/sqlite-driver.ts +20 -0
  79. package/src/core/execution/db-executor.ts +63 -0
  80. package/src/core/execution/executors/mssql-executor.ts +39 -0
  81. package/src/core/execution/executors/mysql-executor.ts +47 -0
  82. package/src/core/execution/executors/postgres-executor.ts +32 -0
  83. package/src/core/execution/executors/sqlite-executor.ts +31 -0
  84. package/src/core/functions/datetime.ts +132 -0
  85. package/src/core/functions/numeric.ts +179 -0
  86. package/src/core/functions/standard-strategy.ts +47 -0
  87. package/src/core/functions/text.ts +147 -0
  88. package/src/core/functions/types.ts +18 -0
  89. package/src/core/hydration/types.ts +57 -0
  90. package/src/decorators/bootstrap.ts +10 -0
  91. package/src/decorators/relations.ts +15 -0
  92. package/src/index.ts +30 -19
  93. package/src/orm/entity-metadata.ts +7 -0
  94. package/src/orm/entity.ts +58 -27
  95. package/src/orm/hydration.ts +25 -17
  96. package/src/orm/lazy-batch.ts +46 -2
  97. package/src/orm/orm-context.ts +60 -60
  98. package/src/orm/query-logger.ts +1 -1
  99. package/src/orm/relation-change-processor.ts +43 -2
  100. package/src/orm/relations/has-one.ts +139 -0
  101. package/src/orm/transaction-runner.ts +1 -1
  102. package/src/orm/unit-of-work.ts +60 -60
  103. package/src/query-builder/delete.ts +22 -5
  104. package/src/query-builder/hydration-manager.ts +2 -1
  105. package/src/query-builder/hydration-planner.ts +8 -7
  106. package/src/query-builder/insert.ts +22 -5
  107. package/src/query-builder/relation-conditions.ts +9 -8
  108. package/src/query-builder/relation-service.ts +3 -2
  109. package/src/query-builder/select.ts +66 -61
  110. package/src/query-builder/update.ts +22 -5
  111. package/src/schema/column.ts +246 -246
  112. package/src/schema/relation.ts +35 -1
  113. package/src/schema/table.ts +28 -28
  114. package/src/schema/types.ts +41 -31
  115. package/src/orm/db-executor.ts +0 -11
@@ -1,43 +1,56 @@
1
- import { ColumnDef } from '../../schema/column.js';
2
- import { TableDef } from '../../schema/table.js';
3
- import { ColumnNode } from './expression-nodes.js';
4
- import { TableNode } from './query.js';
5
-
6
- /**
7
- * Builds or normalizes a column AST node from a column definition or existing node
8
- * @param table - Table definition providing a default table name
9
- * @param column - Column definition or existing column node
10
- */
11
- export const buildColumnNode = (table: TableDef, column: ColumnDef | ColumnNode): ColumnNode => {
12
- if ((column as ColumnNode).type === 'Column') {
13
- return column as ColumnNode;
14
- }
15
-
16
- const def = column as ColumnDef;
17
- return {
18
- type: 'Column',
19
- table: def.table || table.name,
20
- name: def.name
21
- };
22
- };
23
-
24
- /**
25
- * Builds column AST nodes for a list of column names
26
- * @param table - Table definition providing the table name
27
- * @param names - Column names
28
- */
29
- export const buildColumnNodes = (table: TableDef, names: string[]): ColumnNode[] =>
30
- names.map(name => ({
31
- type: 'Column',
32
- table: table.name,
33
- name
34
- }));
35
-
36
- /**
37
- * Builds a table AST node for the provided table definition
38
- * @param table - Table definition
39
- */
40
- export const createTableNode = (table: TableDef): TableNode => ({
41
- type: 'Table',
42
- name: table.name
43
- });
1
+ import { ColumnNode } from './expression-nodes.js';
2
+ import { TableNode, FunctionTableNode } from './query.js';
3
+ import { ColumnRef, TableRef } from './types.js';
4
+
5
+ /**
6
+ * Builds or normalizes a column AST node from a column definition or existing node
7
+ * @param table - Table definition providing a default table name
8
+ * @param column - Column definition or existing column node
9
+ */
10
+ export const buildColumnNode = (table: TableRef, column: ColumnRef | ColumnNode): ColumnNode => {
11
+ if ((column as ColumnNode).type === 'Column') {
12
+ return column as ColumnNode;
13
+ }
14
+
15
+ const def = column as ColumnRef;
16
+ return {
17
+ type: 'Column',
18
+ table: def.table || table.name,
19
+ name: def.name
20
+ };
21
+ };
22
+
23
+ /**
24
+ * Builds column AST nodes for a list of column names
25
+ * @param table - Table definition providing the table name
26
+ * @param names - Column names
27
+ */
28
+ export const buildColumnNodes = (table: TableRef, names: string[]): ColumnNode[] =>
29
+ names.map(name => ({
30
+ type: 'Column',
31
+ table: table.name,
32
+ name
33
+ }));
34
+
35
+ /**
36
+ * Builds a table AST node for the provided table definition
37
+ * @param table - Table definition
38
+ */
39
+ export const createTableNode = (table: TableRef): TableNode => ({
40
+ type: 'Table',
41
+ name: table.name
42
+ });
43
+
44
+ /**
45
+ * Creates a FunctionTable node for expressions like `function_name(args...)` used in FROM
46
+ */
47
+ export const fnTable = (name: string, args: any[] = [], alias?: string, opts?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }): FunctionTableNode => ({
48
+ type: 'FunctionTable',
49
+ name,
50
+ args,
51
+ alias,
52
+ lateral: opts?.lateral,
53
+ withOrdinality: opts?.withOrdinality,
54
+ columnAliases: opts?.columnAliases,
55
+ schema: opts?.schema
56
+ });
@@ -1,6 +1,6 @@
1
- import { ColumnDef } from '../../schema/column.js';
2
1
  import { SelectQueryNode } from './query.js';
3
2
  import { SqlOperator } from '../sql/sql.js';
3
+ import { ColumnRef } from './types.js';
4
4
  import {
5
5
  ColumnNode,
6
6
  FunctionNode,
@@ -37,9 +37,9 @@ export const valueToOperand = (value: unknown): OperandNode => {
37
37
  return value as OperandNode;
38
38
  };
39
39
 
40
- const toNode = (col: ColumnDef | OperandNode): OperandNode => {
40
+ const toNode = (col: ColumnRef | OperandNode): OperandNode => {
41
41
  if (isOperandNode(col)) return col as OperandNode;
42
- const def = col as ColumnDef;
42
+ const def = col as ColumnRef;
43
43
  return { type: 'Column', table: def.table || 'unknown', name: def.name };
44
44
  };
45
45
 
@@ -48,20 +48,20 @@ const toLiteralNode = (value: string | number | boolean | null): LiteralNode =>
48
48
  value
49
49
  });
50
50
 
51
- const toOperand = (val: OperandNode | ColumnDef | string | number | boolean | null): OperandNode => {
51
+ const toOperand = (val: OperandNode | ColumnRef | string | number | boolean | null): OperandNode => {
52
52
  if (val === null) return { type: 'Literal', value: null };
53
53
  if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
54
54
  return { type: 'Literal', value: val };
55
55
  }
56
- return toNode(val as OperandNode | ColumnDef);
56
+ return toNode(val as OperandNode | ColumnRef);
57
57
  };
58
58
 
59
- export const columnOperand = (col: ColumnDef | ColumnNode): ColumnNode => toNode(col) as ColumnNode;
59
+ export const columnOperand = (col: ColumnRef | ColumnNode): ColumnNode => toNode(col) as ColumnNode;
60
60
 
61
61
  const createBinaryExpression = (
62
62
  operator: SqlOperator,
63
- left: OperandNode | ColumnDef,
64
- right: OperandNode | ColumnDef | string | number | boolean | null,
63
+ left: OperandNode | ColumnRef,
64
+ right: OperandNode | ColumnRef | string | number | boolean | null,
65
65
  escape?: string
66
66
  ): BinaryExpressionNode => {
67
67
  const node: BinaryExpressionNode = {
@@ -84,15 +84,15 @@ const createBinaryExpression = (
84
84
  * @param right - Right operand
85
85
  * @returns Binary expression node with equality operator
86
86
  */
87
- export const eq = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
87
+ export const eq = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number | boolean): BinaryExpressionNode =>
88
88
  createBinaryExpression('=', left, right);
89
89
 
90
90
  /**
91
91
  * Creates a not equal expression (left != right)
92
92
  */
93
93
  export const neq = (
94
- left: OperandNode | ColumnDef,
95
- right: OperandNode | ColumnDef | string | number
94
+ left: OperandNode | ColumnRef,
95
+ right: OperandNode | ColumnRef | string | number | boolean
96
96
  ): BinaryExpressionNode => createBinaryExpression('!=', left, right);
97
97
 
98
98
  /**
@@ -101,13 +101,13 @@ export const neq = (
101
101
  * @param right - Right operand
102
102
  * @returns Binary expression node with greater-than operator
103
103
  */
104
- export const gt = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
104
+ export const gt = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
105
105
  createBinaryExpression('>', left, right);
106
106
 
107
107
  /**
108
108
  * Creates a greater than or equal expression (left >= right)
109
109
  */
110
- export const gte = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
110
+ export const gte = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
111
111
  createBinaryExpression('>=', left, right);
112
112
 
113
113
  /**
@@ -116,13 +116,13 @@ export const gte = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDe
116
116
  * @param right - Right operand
117
117
  * @returns Binary expression node with less-than operator
118
118
  */
119
- export const lt = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
119
+ export const lt = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
120
120
  createBinaryExpression('<', left, right);
121
121
 
122
122
  /**
123
123
  * Creates a less than or equal expression (left <= right)
124
124
  */
125
- export const lte = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDef | string | number): BinaryExpressionNode =>
125
+ export const lte = (left: OperandNode | ColumnRef, right: OperandNode | ColumnRef | string | number): BinaryExpressionNode =>
126
126
  createBinaryExpression('<=', left, right);
127
127
 
128
128
  /**
@@ -132,7 +132,7 @@ export const lte = (left: OperandNode | ColumnDef, right: OperandNode | ColumnDe
132
132
  * @param escape - Optional escape character
133
133
  * @returns Binary expression node with LIKE operator
134
134
  */
135
- export const like = (left: OperandNode | ColumnDef, pattern: string, escape?: string): BinaryExpressionNode =>
135
+ export const like = (left: OperandNode | ColumnRef, pattern: string, escape?: string): BinaryExpressionNode =>
136
136
  createBinaryExpression('LIKE', left, pattern, escape);
137
137
 
138
138
  /**
@@ -142,7 +142,7 @@ export const like = (left: OperandNode | ColumnDef, pattern: string, escape?: st
142
142
  * @param escape - Optional escape character
143
143
  * @returns Binary expression node with NOT LIKE operator
144
144
  */
145
- export const notLike = (left: OperandNode | ColumnDef, pattern: string, escape?: string): BinaryExpressionNode =>
145
+ export const notLike = (left: OperandNode | ColumnRef, pattern: string, escape?: string): BinaryExpressionNode =>
146
146
  createBinaryExpression('NOT LIKE', left, pattern, escape);
147
147
 
148
148
  /**
@@ -172,7 +172,7 @@ export const or = (...operands: ExpressionNode[]): LogicalExpressionNode => ({
172
172
  * @param left - Operand to check for null
173
173
  * @returns Null expression node with IS NULL operator
174
174
  */
175
- export const isNull = (left: OperandNode | ColumnDef): NullExpressionNode => ({
175
+ export const isNull = (left: OperandNode | ColumnRef): NullExpressionNode => ({
176
176
  type: 'NullExpression',
177
177
  left: toNode(left),
178
178
  operator: 'IS NULL'
@@ -183,7 +183,7 @@ export const isNull = (left: OperandNode | ColumnDef): NullExpressionNode => ({
183
183
  * @param left - Operand to check for non-null
184
184
  * @returns Null expression node with IS NOT NULL operator
185
185
  */
186
- export const isNotNull = (left: OperandNode | ColumnDef): NullExpressionNode => ({
186
+ export const isNotNull = (left: OperandNode | ColumnRef): NullExpressionNode => ({
187
187
  type: 'NullExpression',
188
188
  left: toNode(left),
189
189
  operator: 'IS NOT NULL'
@@ -191,7 +191,7 @@ export const isNotNull = (left: OperandNode | ColumnDef): NullExpressionNode =>
191
191
 
192
192
  const createInExpression = (
193
193
  operator: 'IN' | 'NOT IN',
194
- left: OperandNode | ColumnDef,
194
+ left: OperandNode | ColumnRef,
195
195
  values: (string | number | LiteralNode)[]
196
196
  ): InExpressionNode => ({
197
197
  type: 'InExpression',
@@ -206,7 +206,7 @@ const createInExpression = (
206
206
  * @param values - Values to check against
207
207
  * @returns IN expression node
208
208
  */
209
- export const inList = (left: OperandNode | ColumnDef, values: (string | number | LiteralNode)[]): InExpressionNode =>
209
+ export const inList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
210
210
  createInExpression('IN', left, values);
211
211
 
212
212
  /**
@@ -215,14 +215,14 @@ export const inList = (left: OperandNode | ColumnDef, values: (string | number |
215
215
  * @param values - Values to check against
216
216
  * @returns NOT IN expression node
217
217
  */
218
- export const notInList = (left: OperandNode | ColumnDef, values: (string | number | LiteralNode)[]): InExpressionNode =>
218
+ export const notInList = (left: OperandNode | ColumnRef, values: (string | number | LiteralNode)[]): InExpressionNode =>
219
219
  createInExpression('NOT IN', left, values);
220
220
 
221
221
  const createBetweenExpression = (
222
222
  operator: 'BETWEEN' | 'NOT BETWEEN',
223
- left: OperandNode | ColumnDef,
224
- lower: OperandNode | ColumnDef | string | number,
225
- upper: OperandNode | ColumnDef | string | number
223
+ left: OperandNode | ColumnRef,
224
+ lower: OperandNode | ColumnRef | string | number,
225
+ upper: OperandNode | ColumnRef | string | number
226
226
  ): BetweenExpressionNode => ({
227
227
  type: 'BetweenExpression',
228
228
  left: toNode(left),
@@ -239,9 +239,9 @@ const createBetweenExpression = (
239
239
  * @returns BETWEEN expression node
240
240
  */
241
241
  export const between = (
242
- left: OperandNode | ColumnDef,
243
- lower: OperandNode | ColumnDef | string | number,
244
- upper: OperandNode | ColumnDef | string | number
242
+ left: OperandNode | ColumnRef,
243
+ lower: OperandNode | ColumnRef | string | number,
244
+ upper: OperandNode | ColumnRef | string | number
245
245
  ): BetweenExpressionNode => createBetweenExpression('BETWEEN', left, lower, upper);
246
246
 
247
247
  /**
@@ -252,9 +252,9 @@ export const between = (
252
252
  * @returns NOT BETWEEN expression node
253
253
  */
254
254
  export const notBetween = (
255
- left: OperandNode | ColumnDef,
256
- lower: OperandNode | ColumnDef | string | number,
257
- upper: OperandNode | ColumnDef | string | number
255
+ left: OperandNode | ColumnRef,
256
+ lower: OperandNode | ColumnRef | string | number,
257
+ upper: OperandNode | ColumnRef | string | number
258
258
  ): BetweenExpressionNode => createBetweenExpression('NOT BETWEEN', left, lower, upper);
259
259
 
260
260
  /**
@@ -263,7 +263,7 @@ export const notBetween = (
263
263
  * @param path - JSON path expression
264
264
  * @returns JSON path node
265
265
  */
266
- export const jsonPath = (col: ColumnDef | ColumnNode, path: string): JsonPathNode => ({
266
+ export const jsonPath = (col: ColumnRef | ColumnNode, path: string): JsonPathNode => ({
267
267
  type: 'JsonPath',
268
268
  column: columnOperand(col),
269
269
  path
@@ -276,8 +276,8 @@ export const jsonPath = (col: ColumnDef | ColumnNode, path: string): JsonPathNod
276
276
  * @returns CASE expression node
277
277
  */
278
278
  export const caseWhen = (
279
- conditions: { when: ExpressionNode; then: OperandNode | ColumnDef | string | number | boolean | null }[],
280
- elseValue?: OperandNode | ColumnDef | string | number | boolean | null
279
+ conditions: { when: ExpressionNode; then: OperandNode | ColumnRef | string | number | boolean | null }[],
280
+ elseValue?: OperandNode | ColumnRef | string | number | boolean | null
281
281
  ): CaseExpressionNode => ({
282
282
  type: 'CaseExpression',
283
283
  conditions: conditions.map(c => ({
@@ -1,6 +1,6 @@
1
- import { ColumnDef } from '../../schema/column.js';
2
- import type { SelectQueryNode, OrderByNode } from './query.js';
3
- import { SqlOperator } from '../sql/sql.js';
1
+ import type { SelectQueryNode, OrderByNode } from './query.js';
2
+ import { SqlOperator } from '../sql/sql.js';
3
+ import { ColumnRef } from './types.js';
4
4
 
5
5
  /**
6
6
  * AST node representing a literal value
@@ -27,15 +27,17 @@ export interface ColumnNode {
27
27
  /**
28
28
  * AST node representing a function call
29
29
  */
30
- export interface FunctionNode {
31
- type: 'Function';
32
- /** Function name (e.g., COUNT, SUM) */
33
- name: string;
34
- /** Function arguments */
35
- args: (ColumnNode | LiteralNode | JsonPathNode)[];
36
- /** Optional alias for the function result */
37
- alias?: string;
38
- }
30
+ export interface FunctionNode {
31
+ type: 'Function';
32
+ /** Function name (e.g., COUNT, SUM) */
33
+ name: string;
34
+ /** Optional canonical function key for dialect-aware rendering */
35
+ fn?: string;
36
+ /** Function arguments */
37
+ args: OperandNode[];
38
+ /** Optional alias for the function result */
39
+ alias?: string;
40
+ }
39
41
 
40
42
  /**
41
43
  * AST node representing a JSON path expression
@@ -118,10 +120,10 @@ export const isOperandNode = (node: any): node is OperandNode => node && operand
118
120
  export const isFunctionNode = (node: any): node is FunctionNode => node?.type === 'Function';
119
121
  export const isCaseExpressionNode = (node: any): node is CaseExpressionNode => node?.type === 'CaseExpression';
120
122
  export const isWindowFunctionNode = (node: any): node is WindowFunctionNode => node?.type === 'WindowFunction';
121
- export const isExpressionSelectionNode = (
122
- node: ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode
123
- ): node is FunctionNode | CaseExpressionNode | WindowFunctionNode =>
124
- isFunctionNode(node) || isCaseExpressionNode(node) || isWindowFunctionNode(node);
123
+ export const isExpressionSelectionNode = (
124
+ node: ColumnRef | FunctionNode | CaseExpressionNode | WindowFunctionNode
125
+ ): node is FunctionNode | CaseExpressionNode | WindowFunctionNode =>
126
+ isFunctionNode(node) || isCaseExpressionNode(node) || isWindowFunctionNode(node);
125
127
 
126
128
  /**
127
129
  * AST node representing a binary expression (e.g., column = value)
@@ -19,81 +19,134 @@ import {
19
19
  /**
20
20
  * Visitor for expression nodes
21
21
  */
22
- export interface ExpressionVisitor<R> {
23
- visitBinaryExpression(node: BinaryExpressionNode): R;
24
- visitLogicalExpression(node: LogicalExpressionNode): R;
25
- visitNullExpression(node: NullExpressionNode): R;
26
- visitInExpression(node: InExpressionNode): R;
27
- visitExistsExpression(node: ExistsExpressionNode): R;
28
- visitBetweenExpression(node: BetweenExpressionNode): R;
29
- }
30
-
31
- /**
32
- * Visitor for operand nodes
33
- */
34
- export interface OperandVisitor<R> {
35
- visitColumn(node: ColumnNode): R;
36
- visitLiteral(node: LiteralNode): R;
37
- visitFunction(node: FunctionNode): R;
38
- visitJsonPath(node: JsonPathNode): R;
39
- visitScalarSubquery(node: ScalarSubqueryNode): R;
40
- visitCaseExpression(node: CaseExpressionNode): R;
41
- visitWindowFunction(node: WindowFunctionNode): R;
42
- }
43
-
44
- const unsupportedExpression = (node: ExpressionNode): never => {
45
- throw new Error(`Unsupported expression type "${(node as any)?.type ?? 'unknown'}"`);
46
- };
22
+ export interface ExpressionVisitor<R> {
23
+ visitBinaryExpression?(node: BinaryExpressionNode): R;
24
+ visitLogicalExpression?(node: LogicalExpressionNode): R;
25
+ visitNullExpression?(node: NullExpressionNode): R;
26
+ visitInExpression?(node: InExpressionNode): R;
27
+ visitExistsExpression?(node: ExistsExpressionNode): R;
28
+ visitBetweenExpression?(node: BetweenExpressionNode): R;
29
+ otherwise?(node: ExpressionNode): R;
30
+ }
31
+
32
+ /**
33
+ * Visitor for operand nodes
34
+ */
35
+ export interface OperandVisitor<R> {
36
+ visitColumn?(node: ColumnNode): R;
37
+ visitLiteral?(node: LiteralNode): R;
38
+ visitFunction?(node: FunctionNode): R;
39
+ visitJsonPath?(node: JsonPathNode): R;
40
+ visitScalarSubquery?(node: ScalarSubqueryNode): R;
41
+ visitCaseExpression?(node: CaseExpressionNode): R;
42
+ visitWindowFunction?(node: WindowFunctionNode): R;
43
+ otherwise?(node: OperandNode): R;
44
+ }
45
+
46
+ type ExpressionDispatch = <R>(node: any, visitor: ExpressionVisitor<R>) => R;
47
+ type OperandDispatch = <R>(node: any, visitor: OperandVisitor<R>) => R;
48
+
49
+ const expressionDispatchers = new Map<string, ExpressionDispatch>();
50
+ const operandDispatchers = new Map<string, OperandDispatch>();
51
+
52
+ /**
53
+ * Registers a dispatcher for a custom expression node type.
54
+ * Allows new node kinds without modifying the core switch.
55
+ */
56
+ export const registerExpressionDispatcher = (type: string, dispatcher: ExpressionDispatch): void => {
57
+ expressionDispatchers.set(type, dispatcher);
58
+ };
59
+
60
+ /**
61
+ * Registers a dispatcher for a custom operand node type.
62
+ * Allows new node kinds without modifying the core switch.
63
+ */
64
+ export const registerOperandDispatcher = (type: string, dispatcher: OperandDispatch): void => {
65
+ operandDispatchers.set(type, dispatcher);
66
+ };
67
+
68
+ /**
69
+ * Clears all registered dispatchers. Primarily for tests.
70
+ */
71
+ export const clearExpressionDispatchers = (): void => expressionDispatchers.clear();
72
+ export const clearOperandDispatchers = (): void => operandDispatchers.clear();
73
+
74
+ const unsupportedExpression = (node: ExpressionNode): never => {
75
+ throw new Error(`Unsupported expression type "${(node as any)?.type ?? 'unknown'}"`);
76
+ };
47
77
 
48
78
  const unsupportedOperand = (node: OperandNode): never => {
49
79
  throw new Error(`Unsupported operand type "${(node as any)?.type ?? 'unknown'}"`);
50
80
  };
51
81
  /**
52
82
  * Dispatches an expression node to the visitor
53
- * @param node - Expression node to visit
54
- * @param visitor - Visitor implementation
55
- */
56
- export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisitor<R>): R => {
57
- switch (node.type) {
58
- case 'BinaryExpression':
59
- return visitor.visitBinaryExpression(node);
60
- case 'LogicalExpression':
61
- return visitor.visitLogicalExpression(node);
62
- case 'NullExpression':
63
- return visitor.visitNullExpression(node);
64
- case 'InExpression':
65
- return visitor.visitInExpression(node);
66
- case 'ExistsExpression':
67
- return visitor.visitExistsExpression(node);
68
- case 'BetweenExpression':
69
- return visitor.visitBetweenExpression(node);
70
- default:
71
- return unsupportedExpression(node);
72
- }
73
- };
83
+ * @param node - Expression node to visit
84
+ * @param visitor - Visitor implementation
85
+ */
86
+ export const visitExpression = <R>(node: ExpressionNode, visitor: ExpressionVisitor<R>): R => {
87
+ const dynamic = expressionDispatchers.get((node as any)?.type);
88
+ if (dynamic) return dynamic(node as any, visitor);
89
+
90
+ switch (node.type) {
91
+ case 'BinaryExpression':
92
+ if (visitor.visitBinaryExpression) return visitor.visitBinaryExpression(node);
93
+ break;
94
+ case 'LogicalExpression':
95
+ if (visitor.visitLogicalExpression) return visitor.visitLogicalExpression(node);
96
+ break;
97
+ case 'NullExpression':
98
+ if (visitor.visitNullExpression) return visitor.visitNullExpression(node);
99
+ break;
100
+ case 'InExpression':
101
+ if (visitor.visitInExpression) return visitor.visitInExpression(node);
102
+ break;
103
+ case 'ExistsExpression':
104
+ if (visitor.visitExistsExpression) return visitor.visitExistsExpression(node);
105
+ break;
106
+ case 'BetweenExpression':
107
+ if (visitor.visitBetweenExpression) return visitor.visitBetweenExpression(node);
108
+ break;
109
+ default:
110
+ break;
111
+ }
112
+ if (visitor.otherwise) return visitor.otherwise(node);
113
+ return unsupportedExpression(node);
114
+ };
74
115
 
75
116
  /**
76
117
  * Dispatches an operand node to the visitor
77
- * @param node - Operand node to visit
78
- * @param visitor - Visitor implementation
79
- */
80
- export const visitOperand = <R>(node: OperandNode, visitor: OperandVisitor<R>): R => {
81
- switch (node.type) {
82
- case 'Column':
83
- return visitor.visitColumn(node);
84
- case 'Literal':
85
- return visitor.visitLiteral(node);
86
- case 'Function':
87
- return visitor.visitFunction(node);
88
- case 'JsonPath':
89
- return visitor.visitJsonPath(node);
90
- case 'ScalarSubquery':
91
- return visitor.visitScalarSubquery(node);
92
- case 'CaseExpression':
93
- return visitor.visitCaseExpression(node);
94
- case 'WindowFunction':
95
- return visitor.visitWindowFunction(node);
96
- default:
97
- return unsupportedOperand(node);
98
- }
99
- };
118
+ * @param node - Operand node to visit
119
+ * @param visitor - Visitor implementation
120
+ */
121
+ export const visitOperand = <R>(node: OperandNode, visitor: OperandVisitor<R>): R => {
122
+ const dynamic = operandDispatchers.get((node as any)?.type);
123
+ if (dynamic) return dynamic(node as any, visitor);
124
+
125
+ switch (node.type) {
126
+ case 'Column':
127
+ if (visitor.visitColumn) return visitor.visitColumn(node);
128
+ break;
129
+ case 'Literal':
130
+ if (visitor.visitLiteral) return visitor.visitLiteral(node);
131
+ break;
132
+ case 'Function':
133
+ if (visitor.visitFunction) return visitor.visitFunction(node);
134
+ break;
135
+ case 'JsonPath':
136
+ if (visitor.visitJsonPath) return visitor.visitJsonPath(node);
137
+ break;
138
+ case 'ScalarSubquery':
139
+ if (visitor.visitScalarSubquery) return visitor.visitScalarSubquery(node);
140
+ break;
141
+ case 'CaseExpression':
142
+ if (visitor.visitCaseExpression) return visitor.visitCaseExpression(node);
143
+ break;
144
+ case 'WindowFunction':
145
+ if (visitor.visitWindowFunction) return visitor.visitWindowFunction(node);
146
+ break;
147
+ default:
148
+ break;
149
+ }
150
+ if (visitor.otherwise) return visitor.otherwise(node);
151
+ return unsupportedOperand(node);
152
+ };
@@ -1,5 +1,7 @@
1
1
  export * from './expression-nodes.js';
2
- export * from './expression-builders.js';
3
- export * from './window-functions.js';
4
- export * from './aggregate-functions.js';
5
- export * from './expression-visitor.js';
2
+ export * from './expression-builders.js';
3
+ export * from './window-functions.js';
4
+ export * from './aggregate-functions.js';
5
+ export * from './expression-visitor.js';
6
+ export * from './types.js';
7
+ export * from './adapters.js';
@@ -0,0 +1,15 @@
1
+ import { JoinNode } from './join.js';
2
+
3
+ /**
4
+ * Metadata stored on JoinNode.meta for higher-level concerns.
5
+ */
6
+ export interface JoinMetadata {
7
+ relationName?: string;
8
+ [key: string]: unknown;
9
+ }
10
+
11
+ /**
12
+ * Retrieves the relation name from join metadata if present.
13
+ */
14
+ export const getJoinRelationName = (join: JoinNode): string | undefined =>
15
+ (join.meta as JoinMetadata | undefined)?.relationName;
@@ -1,20 +1,22 @@
1
- import { JoinNode } from './join.js';
2
- import { ExpressionNode } from './expression.js';
3
- import { JoinKind } from '../sql/sql.js';
4
-
5
- /**
6
- * Creates a JoinNode ready for AST insertion.
7
- * Centralizing this avoids copy/pasted object literals when multiple services need to synthesize joins.
8
- */
9
- export const createJoinNode = (
10
- kind: JoinKind,
11
- tableName: string,
12
- condition: ExpressionNode,
13
- relationName?: string
14
- ): JoinNode => ({
15
- type: 'Join',
16
- kind,
17
- table: { type: 'Table', name: tableName },
18
- condition,
19
- relationName
20
- });
1
+ import { JoinNode } from './join.js';
2
+ import { ExpressionNode } from './expression.js';
3
+ import { JoinKind } from '../sql/sql.js';
4
+ import { JoinMetadata } from './join-metadata.js';
5
+ import { TableNode, FunctionTableNode } from './query.js';
6
+
7
+ /**
8
+ * Creates a JoinNode ready for AST insertion.
9
+ * Centralizing this avoids copy/pasted object literals when multiple services need to synthesize joins.
10
+ */
11
+ export const createJoinNode = (
12
+ kind: JoinKind,
13
+ tableName: string | TableNode | FunctionTableNode,
14
+ condition: ExpressionNode,
15
+ relationName?: string
16
+ ): JoinNode => ({
17
+ type: 'Join',
18
+ kind,
19
+ table: typeof tableName === 'string' ? { type: 'Table', name: tableName } as TableNode : (tableName as TableNode | FunctionTableNode),
20
+ condition,
21
+ meta: relationName ? ({ relationName } as JoinMetadata) : undefined
22
+ });