metal-orm 1.0.42 → 1.0.44

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 (122) hide show
  1. package/README.md +195 -37
  2. package/dist/index.cjs +1014 -538
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1267 -371
  5. package/dist/index.d.ts +1267 -371
  6. package/dist/index.js +1012 -536
  7. package/dist/index.js.map +1 -1
  8. package/package.json +8 -2
  9. package/scripts/run-eslint.mjs +34 -0
  10. package/src/codegen/typescript.ts +32 -15
  11. package/src/core/ast/adapters.ts +8 -2
  12. package/src/core/ast/builders.ts +105 -76
  13. package/src/core/ast/expression-builders.ts +430 -392
  14. package/src/core/ast/expression-nodes.ts +14 -5
  15. package/src/core/ast/expression-visitor.ts +56 -14
  16. package/src/core/ast/helpers.ts +23 -0
  17. package/src/core/ast/join-node.ts +18 -2
  18. package/src/core/ast/query.ts +6 -6
  19. package/src/core/ast/window-functions.ts +10 -2
  20. package/src/core/ddl/dialects/base-schema-dialect.ts +37 -4
  21. package/src/core/ddl/dialects/index.ts +1 -0
  22. package/src/core/ddl/dialects/mssql-schema-dialect.ts +5 -0
  23. package/src/core/ddl/dialects/mysql-schema-dialect.ts +3 -0
  24. package/src/core/ddl/dialects/postgres-schema-dialect.ts +14 -1
  25. package/src/core/ddl/dialects/render-reference.test.ts +69 -0
  26. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +10 -0
  27. package/src/core/ddl/introspect/catalogs/index.ts +1 -0
  28. package/src/core/ddl/introspect/catalogs/postgres.ts +2 -0
  29. package/src/core/ddl/introspect/context.ts +6 -0
  30. package/src/core/ddl/introspect/functions/postgres.ts +13 -0
  31. package/src/core/ddl/introspect/mssql.ts +53 -8
  32. package/src/core/ddl/introspect/mysql.ts +32 -6
  33. package/src/core/ddl/introspect/postgres.ts +102 -34
  34. package/src/core/ddl/introspect/registry.ts +14 -0
  35. package/src/core/ddl/introspect/run-select.ts +19 -4
  36. package/src/core/ddl/introspect/sqlite.ts +78 -11
  37. package/src/core/ddl/introspect/types.ts +0 -1
  38. package/src/core/ddl/introspect/utils.ts +21 -3
  39. package/src/core/ddl/naming-strategy.ts +6 -0
  40. package/src/core/ddl/schema-dialect.ts +20 -6
  41. package/src/core/ddl/schema-diff.ts +22 -0
  42. package/src/core/ddl/schema-generator.ts +26 -12
  43. package/src/core/ddl/schema-plan-executor.ts +6 -0
  44. package/src/core/ddl/schema-types.ts +6 -0
  45. package/src/core/ddl/sql-writing.ts +4 -4
  46. package/src/core/dialect/abstract.ts +19 -7
  47. package/src/core/dialect/base/function-table-formatter.ts +3 -2
  48. package/src/core/dialect/base/join-compiler.ts +5 -3
  49. package/src/core/dialect/base/returning-strategy.ts +1 -0
  50. package/src/core/dialect/base/sql-dialect.ts +3 -3
  51. package/src/core/dialect/mssql/functions.ts +24 -25
  52. package/src/core/dialect/mssql/index.ts +1 -4
  53. package/src/core/dialect/mysql/functions.ts +0 -1
  54. package/src/core/dialect/postgres/functions.ts +33 -34
  55. package/src/core/dialect/postgres/index.ts +1 -0
  56. package/src/core/dialect/sqlite/functions.ts +18 -19
  57. package/src/core/dialect/sqlite/index.ts +2 -0
  58. package/src/core/execution/db-executor.ts +1 -1
  59. package/src/core/execution/executors/mysql-executor.ts +2 -2
  60. package/src/core/execution/executors/postgres-executor.ts +1 -1
  61. package/src/core/execution/pooling/pool.ts +12 -5
  62. package/src/core/functions/datetime.ts +58 -34
  63. package/src/core/functions/numeric.ts +96 -31
  64. package/src/core/functions/standard-strategy.ts +35 -0
  65. package/src/core/functions/text.ts +84 -23
  66. package/src/core/functions/types.ts +23 -8
  67. package/src/decorators/bootstrap.ts +42 -11
  68. package/src/decorators/column.ts +20 -11
  69. package/src/decorators/decorator-metadata.ts +30 -9
  70. package/src/decorators/entity.ts +29 -5
  71. package/src/decorators/index.ts +3 -0
  72. package/src/decorators/relations.ts +34 -11
  73. package/src/orm/als.ts +34 -9
  74. package/src/orm/entity-context.ts +62 -8
  75. package/src/orm/entity-meta.ts +8 -8
  76. package/src/orm/entity-metadata.ts +131 -16
  77. package/src/orm/entity.ts +28 -29
  78. package/src/orm/execute.ts +19 -4
  79. package/src/orm/hydration.ts +42 -39
  80. package/src/orm/identity-map.ts +1 -1
  81. package/src/orm/lazy-batch.ts +74 -104
  82. package/src/orm/orm-session.ts +24 -23
  83. package/src/orm/orm.ts +2 -5
  84. package/src/orm/relation-change-processor.ts +12 -11
  85. package/src/orm/relations/belongs-to.ts +11 -11
  86. package/src/orm/relations/has-many.ts +54 -10
  87. package/src/orm/relations/has-one.ts +8 -7
  88. package/src/orm/relations/many-to-many.ts +13 -13
  89. package/src/orm/runtime-types.ts +4 -4
  90. package/src/orm/save-graph.ts +31 -25
  91. package/src/orm/unit-of-work.ts +17 -17
  92. package/src/query/index.ts +74 -0
  93. package/src/query/target.ts +46 -0
  94. package/src/query-builder/delete-query-state.ts +30 -0
  95. package/src/query-builder/delete.ts +64 -18
  96. package/src/query-builder/hydration-manager.ts +52 -5
  97. package/src/query-builder/insert-query-state.ts +30 -0
  98. package/src/query-builder/insert.ts +58 -10
  99. package/src/query-builder/query-ast-service.ts +7 -2
  100. package/src/query-builder/query-resolution.ts +78 -0
  101. package/src/query-builder/raw-column-parser.ts +7 -1
  102. package/src/query-builder/relation-alias.ts +7 -0
  103. package/src/query-builder/relation-conditions.ts +61 -48
  104. package/src/query-builder/relation-service.ts +68 -63
  105. package/src/query-builder/relation-utils.ts +3 -0
  106. package/src/query-builder/select/cte-facet.ts +40 -0
  107. package/src/query-builder/select/from-facet.ts +80 -0
  108. package/src/query-builder/select/join-facet.ts +62 -0
  109. package/src/query-builder/select/predicate-facet.ts +103 -0
  110. package/src/query-builder/select/projection-facet.ts +69 -0
  111. package/src/query-builder/select/relation-facet.ts +81 -0
  112. package/src/query-builder/select/setop-facet.ts +36 -0
  113. package/src/query-builder/select-helpers.ts +15 -2
  114. package/src/query-builder/select-query-builder-deps.ts +19 -1
  115. package/src/query-builder/select-query-state.ts +2 -1
  116. package/src/query-builder/select.ts +795 -1163
  117. package/src/query-builder/update-query-state.ts +52 -0
  118. package/src/query-builder/update.ts +69 -18
  119. package/src/schema/column.ts +26 -26
  120. package/src/schema/table-guards.ts +31 -0
  121. package/src/schema/table.ts +47 -18
  122. package/src/schema/types.ts +22 -22
@@ -20,7 +20,7 @@ export class HydrationManager {
20
20
  constructor(
21
21
  private readonly table: TableDef,
22
22
  private readonly planner: HydrationPlanner
23
- ) {}
23
+ ) { }
24
24
 
25
25
  /**
26
26
  * Creates a new HydrationManager with updated planner
@@ -113,6 +113,11 @@ export class HydrationManager {
113
113
  return hasPagination && this.hasMultiplyingRelations(plan);
114
114
  }
115
115
 
116
+ /**
117
+ * Checks if the hydration plan contains relations that multiply rows
118
+ * @param plan - Hydration plan to check
119
+ * @returns True if plan has HasMany or BelongsToMany relations
120
+ */
116
121
  private hasMultiplyingRelations(plan: HydrationPlan): boolean {
117
122
  return plan.relations.some(
118
123
  rel => rel.type === RelationKinds.HasMany || rel.type === RelationKinds.BelongsToMany
@@ -203,6 +208,12 @@ export class HydrationManager {
203
208
  };
204
209
  }
205
210
 
211
+ /**
212
+ * Generates a unique CTE name by appending a suffix if needed
213
+ * @param existing - Existing CTE nodes
214
+ * @param baseName - Base name for the CTE
215
+ * @returns Unique CTE name
216
+ */
206
217
  private nextCteName(existing: CommonTableExpressionNode[] | undefined, baseName: string): string {
207
218
  const names = new Set((existing ?? []).map(cte => cte.name));
208
219
  let candidate = baseName;
@@ -216,16 +227,27 @@ export class HydrationManager {
216
227
  return candidate;
217
228
  }
218
229
 
230
+ /**
231
+ * Extracts projection names from column nodes
232
+ * @param columns - Projection nodes
233
+ * @returns Array of names or undefined if any column lacks name/alias
234
+ */
219
235
  private getProjectionNames(columns: ProjectionNode[]): string[] | undefined {
220
236
  const names: string[] = [];
221
237
  for (const col of columns) {
222
- const alias = (col as any).alias ?? (col as any).name;
238
+ const node = col as { alias?: string; name?: string };
239
+ const alias = node.alias ?? node.name;
223
240
  if (!alias) return undefined;
224
241
  names.push(alias);
225
242
  }
226
243
  return names;
227
244
  }
228
245
 
246
+ /**
247
+ * Builds a map of column keys to their aliases from projection nodes
248
+ * @param columns - Projection nodes
249
+ * @returns Map of 'table.name' to alias
250
+ */
229
251
  private buildProjectionAliasMap(columns: ProjectionNode[]): Map<string, string> {
230
252
  const map = new Map<string, string>();
231
253
  for (const col of columns) {
@@ -237,6 +259,15 @@ export class HydrationManager {
237
259
  return map;
238
260
  }
239
261
 
262
+ /**
263
+ * Maps order by nodes to use base CTE alias
264
+ * @param orderBy - Original order by nodes
265
+ * @param plan - Hydration plan
266
+ * @param projectionAliases - Map of column aliases
267
+ * @param baseAlias - Base CTE alias
268
+ * @param availableColumns - Set of available column names
269
+ * @returns Mapped order by nodes, null if cannot map
270
+ */
240
271
  private mapOrderBy(
241
272
  orderBy: OrderByNode[] | undefined,
242
273
  plan: HydrationPlan,
@@ -260,6 +291,15 @@ export class HydrationManager {
260
291
  return mapped;
261
292
  }
262
293
 
294
+ /**
295
+ * Maps a single ordering term to use base CTE alias
296
+ * @param term - Ordering term to map
297
+ * @param plan - Hydration plan
298
+ * @param projectionAliases - Map of column aliases
299
+ * @param baseAlias - Base CTE alias
300
+ * @param availableColumns - Set of available column names
301
+ * @returns Mapped term or null if cannot map
302
+ */
263
303
  private mapOrderingTerm(
264
304
  term: OrderByNode['term'],
265
305
  plan: HydrationPlan,
@@ -267,7 +307,7 @@ export class HydrationManager {
267
307
  baseAlias: string,
268
308
  availableColumns: Set<string>
269
309
  ): OrderByNode['term'] | null {
270
- if ((term as any).type === 'Column') {
310
+ if (term.type === 'Column') {
271
311
  const col = term as ColumnNode;
272
312
  if (col.table !== plan.rootTable) return null;
273
313
  const alias = projectionAliases.get(`${col.table}.${col.name}`) ?? col.name;
@@ -275,8 +315,8 @@ export class HydrationManager {
275
315
  return { type: 'Column', table: baseAlias, name: alias };
276
316
  }
277
317
 
278
- if ((term as any).type === 'AliasRef') {
279
- const aliasName = (term as any).name;
318
+ if (term.type === 'AliasRef') {
319
+ const aliasName = term.name;
280
320
  if (!availableColumns.has(aliasName)) return null;
281
321
  return { type: 'Column', table: baseAlias, name: aliasName };
282
322
  }
@@ -284,6 +324,13 @@ export class HydrationManager {
284
324
  return null;
285
325
  }
286
326
 
327
+ /**
328
+ * Builds column nodes for paging CTE
329
+ * @param primaryKey - Primary key name
330
+ * @param orderBy - Order by nodes
331
+ * @param tableAlias - Table alias for columns
332
+ * @returns Array of column nodes for paging
333
+ */
287
334
  private buildPagingColumns(primaryKey: string, orderBy: OrderByNode[] | undefined, tableAlias: string): ColumnNode[] {
288
335
  const columns: ColumnNode[] = [{ type: 'Column', table: tableAlias, name: primaryKey, alias: primaryKey }];
289
336
 
@@ -20,6 +20,11 @@ export class InsertQueryState {
20
20
  public readonly table: TableDef;
21
21
  public readonly ast: InsertQueryNode;
22
22
 
23
+ /**
24
+ * Creates a new InsertQueryState instance
25
+ * @param table - The table definition for the INSERT query
26
+ * @param ast - Optional initial AST node, defaults to a basic INSERT query
27
+ */
23
28
  constructor(table: TableDef, ast?: InsertQueryNode) {
24
29
  this.table = table;
25
30
  this.ast = ast ?? {
@@ -55,6 +60,13 @@ export class InsertQueryState {
55
60
  return buildColumnNodes(this.table, names);
56
61
  }
57
62
 
63
+ /**
64
+ * Adds VALUES clause to the INSERT query
65
+ * @param rows - Array of row objects to insert
66
+ * @returns A new InsertQueryState with the VALUES clause added
67
+ * @throws Error if mixing VALUES with SELECT source
68
+ * @throws Error if invalid values are provided
69
+ */
58
70
  withValues(rows: Record<string, unknown>[]): InsertQueryState {
59
71
  if (!rows.length) return this;
60
72
 
@@ -88,6 +100,11 @@ export class InsertQueryState {
88
100
  });
89
101
  }
90
102
 
103
+ /**
104
+ * Sets the columns for the INSERT query
105
+ * @param columns - Column nodes to insert into
106
+ * @returns A new InsertQueryState with the specified columns
107
+ */
91
108
  withColumns(columns: ColumnNode[]): InsertQueryState {
92
109
  if (!columns.length) return this;
93
110
  return this.clone({
@@ -96,6 +113,14 @@ export class InsertQueryState {
96
113
  });
97
114
  }
98
115
 
116
+ /**
117
+ * Adds SELECT source to the INSERT query
118
+ * @param query - The SELECT query to use as source
119
+ * @param columns - Target columns for the INSERT
120
+ * @returns A new InsertQueryState with the SELECT source
121
+ * @throws Error if mixing SELECT with VALUES source
122
+ * @throws Error if no destination columns specified
123
+ */
99
124
  withSelect(query: SelectQueryNode, columns: ColumnNode[]): InsertQueryState {
100
125
  const targetColumns =
101
126
  columns.length
@@ -122,6 +147,11 @@ export class InsertQueryState {
122
147
  });
123
148
  }
124
149
 
150
+ /**
151
+ * Adds a RETURNING clause to the INSERT query
152
+ * @param columns - Columns to return after insertion
153
+ * @returns A new InsertQueryState with the RETURNING clause added
154
+ */
125
155
  withReturning(columns: ColumnNode[]): InsertQueryState {
126
156
  return this.clone({
127
157
  ...this.ast,
@@ -17,6 +17,11 @@ export class InsertQueryBuilder<T> {
17
17
  private readonly table: TableDef;
18
18
  private readonly state: InsertQueryState;
19
19
 
20
+ /**
21
+ * Creates a new InsertQueryBuilder instance
22
+ * @param table - The table definition for the INSERT query
23
+ * @param state - Optional initial query state, defaults to a new InsertQueryState
24
+ */
20
25
  constructor(table: TableDef, state?: InsertQueryState) {
21
26
  this.table = table;
22
27
  this.state = state ?? new InsertQueryState(table);
@@ -26,19 +31,36 @@ export class InsertQueryBuilder<T> {
26
31
  return new InsertQueryBuilder(this.table, state);
27
32
  }
28
33
 
34
+ /**
35
+ * Adds VALUES to the INSERT query
36
+ * @param rowOrRows - Single row object or array of row objects to insert
37
+ * @returns A new InsertQueryBuilder with the VALUES clause added
38
+ */
29
39
  values(rowOrRows: Record<string, unknown> | Record<string, unknown>[]): InsertQueryBuilder<T> {
30
40
  const rows = Array.isArray(rowOrRows) ? rowOrRows : [rowOrRows];
31
41
  if (!rows.length) return this;
32
42
  return this.clone(this.state.withValues(rows));
33
43
  }
34
44
 
45
+ /**
46
+ * Specifies the columns for the INSERT query
47
+ * @param columns - Column definitions or nodes to insert into
48
+ * @returns A new InsertQueryBuilder with the specified columns
49
+ */
35
50
  columns(...columns: (ColumnDef | ColumnNode)[]): InsertQueryBuilder<T> {
36
51
  if (!columns.length) return this;
37
52
  return this.clone(this.state.withColumns(this.resolveColumnNodes(columns)));
38
53
  }
39
54
 
40
- fromSelect(
41
- query: SelectQueryNode | SelectQueryBuilder<any, TableDef<any>>,
55
+ /**
56
+ * Sets the source of the INSERT query to a SELECT query
57
+ * @template TSource - The source table type
58
+ * @param query - The SELECT query or query builder to use as source
59
+ * @param columns - Optional target columns for the INSERT
60
+ * @returns A new InsertQueryBuilder with the SELECT source
61
+ */
62
+ fromSelect<TSource extends TableDef>(
63
+ query: SelectQueryNode | SelectQueryBuilder<unknown, TSource>,
42
64
  columns: (ColumnDef | ColumnNode)[] = []
43
65
  ): InsertQueryBuilder<T> {
44
66
  const ast = this.resolveSelectQuery(query);
@@ -46,6 +68,11 @@ export class InsertQueryBuilder<T> {
46
68
  return this.clone(this.state.withSelect(ast, nodes));
47
69
  }
48
70
 
71
+ /**
72
+ * Adds a RETURNING clause to the INSERT query
73
+ * @param columns - Columns to return after insertion
74
+ * @returns A new InsertQueryBuilder with the RETURNING clause added
75
+ */
49
76
  returning(...columns: (ColumnDef | ColumnNode)[]): InsertQueryBuilder<T> {
50
77
  if (!columns.length) return this;
51
78
  const nodes = columns.map(column => buildColumnNode(this.table, column));
@@ -57,23 +84,35 @@ export class InsertQueryBuilder<T> {
57
84
  return columns.map(column => buildColumnNode(this.table, column));
58
85
  }
59
86
 
60
- private resolveSelectQuery(query: SelectQueryNode | SelectQueryBuilder<any>): SelectQueryNode {
61
- return typeof (query as any).getAST === 'function'
62
- ? (query as SelectQueryBuilder<any>).getAST()
87
+ private resolveSelectQuery<TSource extends TableDef>(
88
+ query: SelectQueryNode | SelectQueryBuilder<unknown, TSource>
89
+ ): SelectQueryNode {
90
+ const candidate = query as { getAST?: () => SelectQueryNode };
91
+ return typeof candidate.getAST === 'function' && candidate.getAST
92
+ ? candidate.getAST()
63
93
  : (query as SelectQueryNode);
64
94
  }
65
95
 
66
96
  // Existing compiler-based compile stays, but we add a new overload.
67
97
 
68
- // 1) Keep the old behavior (used internally / tests, if any):
98
+ /**
99
+ * Compiles the INSERT query
100
+ * @param compiler - The INSERT compiler to use
101
+ * @returns The compiled query with SQL and parameters
102
+ */
69
103
  compile(compiler: InsertCompiler): CompiledQuery;
70
- // 2) New ergonomic overload:
104
+ /**
105
+ * Compiles the INSERT query for the specified dialect
106
+ * @param dialect - The SQL dialect to compile for
107
+ * @returns The compiled query with SQL and parameters
108
+ */
71
109
  compile(dialect: InsertDialectInput): CompiledQuery;
72
110
 
73
111
  compile(arg: InsertCompiler | InsertDialectInput): CompiledQuery {
74
- if (typeof (arg as any).compileInsert === 'function') {
112
+ const candidate = arg as { compileInsert?: (ast: InsertQueryNode) => CompiledQuery };
113
+ if (typeof candidate.compileInsert === 'function') {
75
114
  // InsertCompiler path – old behavior
76
- return (arg as InsertCompiler).compileInsert(this.state.ast);
115
+ return candidate.compileInsert(this.state.ast);
77
116
  }
78
117
 
79
118
  // Dialect | string path – new behavior
@@ -81,10 +120,19 @@ export class InsertQueryBuilder<T> {
81
120
  return dialect.compileInsert(this.state.ast);
82
121
  }
83
122
 
123
+ /**
124
+ * Returns the SQL string for the INSERT query
125
+ * @param arg - The compiler or dialect to generate SQL for
126
+ * @returns The SQL string representation of the query
127
+ */
84
128
  toSql(arg: InsertCompiler | InsertDialectInput): string {
85
- return this.compile(arg as any).sql;
129
+ return this.compile(arg as InsertCompiler).sql;
86
130
  }
87
131
 
132
+ /**
133
+ * Returns the Abstract Syntax Tree (AST) representation of the query
134
+ * @returns The AST node for the INSERT query
135
+ */
88
136
  getAST(): InsertQueryNode {
89
137
  return this.state.ast;
90
138
  }
@@ -49,7 +49,7 @@ export class QueryAstService {
49
49
  * @param table - Table definition
50
50
  * @param state - Current query state
51
51
  */
52
- constructor(private readonly table: TableDef, private readonly state: SelectQueryState) {}
52
+ constructor(private readonly table: TableDef, private readonly state: SelectQueryState) { }
53
53
 
54
54
  /**
55
55
  * Selects columns for the query
@@ -251,10 +251,15 @@ export class QueryAstService {
251
251
  return existing ? and(existing, next) : next;
252
252
  }
253
253
 
254
+ /**
255
+ * Normalizes an ordering term to a standard OrderingTerm
256
+ * @param term - Column definition or ordering term to normalize
257
+ * @returns Normalized ordering term
258
+ */
254
259
  private normalizeOrderingTerm(term: ColumnDef | OrderingTerm): OrderingTerm {
255
260
  const from = this.state.ast.from;
256
261
  const tableRef = from.type === 'Table' && from.alias ? { ...this.table, alias: from.alias } : this.table;
257
- const termType = (term as any)?.type;
262
+ const termType = (term as { type?: string }).type;
258
263
  if (termType === 'Column') {
259
264
  return term as ColumnNode;
260
265
  }
@@ -0,0 +1,78 @@
1
+ import { SelectQueryNode, UpdateQueryNode, DeleteQueryNode, TableSourceNode } from '../core/ast/query.js';
2
+ import { TableDef } from '../schema/table.js';
3
+ import type { SelectQueryBuilder } from './select.js';
4
+ import type { UpdateQueryBuilder } from './update.js';
5
+ import type { DeleteQueryBuilder } from './delete.js';
6
+
7
+ /**
8
+ * Resolves a SelectQueryBuilder or SelectQueryNode to a SelectQueryNode AST
9
+ * @param query - Query builder or AST node
10
+ * @returns SelectQueryNode AST
11
+ */
12
+ export function resolveSelectQuery<TSub extends TableDef>(
13
+ query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
14
+ ): SelectQueryNode {
15
+ const candidate = query as { getAST?: () => SelectQueryNode };
16
+ return typeof candidate.getAST === 'function' && candidate.getAST
17
+ ? candidate.getAST()
18
+ : (query as SelectQueryNode);
19
+ }
20
+
21
+ /**
22
+ * Resolves a UpdateQueryBuilder or UpdateQueryNode to a UpdateQueryNode AST
23
+ * @param query - Query builder or AST node
24
+ * @returns UpdateQueryNode AST
25
+ */
26
+ export function resolveUpdateQuery<T>(
27
+ query: UpdateQueryBuilder<T> | UpdateQueryNode
28
+ ): UpdateQueryNode {
29
+ const candidate = query as { getAST?: () => UpdateQueryNode };
30
+ return typeof candidate.getAST === 'function' && candidate.getAST
31
+ ? candidate.getAST()
32
+ : (query as UpdateQueryNode);
33
+ }
34
+
35
+ /**
36
+ * Resolves a DeleteQueryBuilder or DeleteQueryNode to a DeleteQueryNode AST
37
+ * @param query - Query builder or AST node
38
+ * @returns DeleteQueryNode AST
39
+ */
40
+ export function resolveDeleteQuery<T>(
41
+ query: DeleteQueryBuilder<T> | DeleteQueryNode
42
+ ): DeleteQueryNode {
43
+ const candidate = query as { getAST?: () => DeleteQueryNode };
44
+ return typeof candidate.getAST === 'function' && candidate.getAST
45
+ ? candidate.getAST()
46
+ : (query as DeleteQueryNode);
47
+ }
48
+
49
+ /**
50
+ * Resolves a TableDef or TableSourceNode to a TableSourceNode
51
+ * @param source - Table definition or source node
52
+ * @returns TableSourceNode
53
+ */
54
+ export function resolveTableSource(source: TableDef | TableSourceNode): TableSourceNode {
55
+ if (isTableSourceNode(source)) {
56
+ return source;
57
+ }
58
+ return { type: 'Table', name: source.name, schema: source.schema };
59
+ }
60
+
61
+ /**
62
+ * Resolves a join target (TableDef, TableSourceNode, or string relation name)
63
+ * @param table - Join target
64
+ * @returns TableSourceNode or string
65
+ */
66
+ export function resolveJoinTarget(table: TableDef | TableSourceNode | string): TableSourceNode | string {
67
+ if (typeof table === 'string') return table;
68
+ return resolveTableSource(table);
69
+ }
70
+
71
+ /**
72
+ * Type guard to check if a value is a TableSourceNode
73
+ * @param source - Value to check
74
+ * @returns True if value is a TableSourceNode
75
+ */
76
+ function isTableSourceNode(source: TableDef | TableSourceNode): source is TableSourceNode {
77
+ return typeof (source as TableSourceNode).type === 'string';
78
+ }
@@ -4,6 +4,11 @@ import { CommonTableExpressionNode } from '../core/ast/query.js';
4
4
  /**
5
5
  * Best-effort helper that tries to convert a raw column expression into a `ColumnNode`.
6
6
  * This parser is intentionally limited; use it only for simple references or function calls.
7
+ *
8
+ * @param col - Raw column expression string (e.g., "column", "table.column", "COUNT(column)")
9
+ * @param tableName - Default table name to use when no table is specified
10
+ * @param ctes - Optional array of CTEs for context when parsing column references
11
+ * @returns A ColumnNode representing the parsed column expression
7
12
  */
8
13
  export const parseRawColumn = (
9
14
  col: string,
@@ -11,7 +16,8 @@ export const parseRawColumn = (
11
16
  ctes?: CommonTableExpressionNode[]
12
17
  ): ColumnNode => {
13
18
  if (col.includes('(')) {
14
- const [fn, rest] = col.split('(');
19
+ const [_fn, rest] = col.split('(');
20
+ void _fn;
15
21
  const colName = rest.replace(')', '');
16
22
  const [table, name] = colName.includes('.') ? colName.split('.') : [tableName, colName];
17
23
  return { type: 'Column', table, name, alias: col };
@@ -19,6 +19,9 @@ export interface RelationAliasParts {
19
19
 
20
20
  /**
21
21
  * Builds a relation alias from the relation name and column name components.
22
+ * @param relationName - The name of the relation
23
+ * @param columnName - The name of the column within the relation
24
+ * @returns A relation alias string in the format "relationName__columnName"
22
25
  */
23
26
  export const makeRelationAlias = (relationName: string, columnName: string): string =>
24
27
  `${relationName}${RELATION_SEPARATOR}${columnName}`;
@@ -26,6 +29,8 @@ export const makeRelationAlias = (relationName: string, columnName: string): str
26
29
  /**
27
30
  * Parses a relation alias into its relation/column components.
28
31
  * Returns `null` when the alias does not follow the `relation__column` pattern.
32
+ * @param alias - The relation alias string to parse
33
+ * @returns Parsed relation alias parts or null if not a valid relation alias
29
34
  */
30
35
  export const parseRelationAlias = (alias: string): RelationAliasParts | null => {
31
36
  const idx = alias.indexOf(RELATION_SEPARATOR);
@@ -38,6 +43,8 @@ export const parseRelationAlias = (alias: string): RelationAliasParts | null =>
38
43
 
39
44
  /**
40
45
  * Determines whether an alias represents a relation column by checking the `__` convention.
46
+ * @param alias - The alias string to check
47
+ * @returns True if the alias follows the relation alias pattern
41
48
  */
42
49
  export const isRelationAlias = (alias?: string): boolean =>
43
50
  !!alias && alias.includes(RELATION_SEPARATOR);
@@ -21,26 +21,26 @@ const assertNever = (value: never): never => {
21
21
  * @param relation - Relation definition
22
22
  * @returns Expression node representing the join condition
23
23
  */
24
- const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
25
- const rootTable = rootAlias || root.name;
26
- const defaultLocalKey =
27
- relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne
28
- ? findPrimaryKey(root)
29
- : findPrimaryKey(relation.target);
30
- const localKey = relation.localKey || defaultLocalKey;
24
+ const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
25
+ const rootTable = rootAlias || root.name;
26
+ const defaultLocalKey =
27
+ relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne
28
+ ? findPrimaryKey(root)
29
+ : findPrimaryKey(relation.target);
30
+ const localKey = relation.localKey || defaultLocalKey;
31
31
 
32
32
  switch (relation.type) {
33
- case RelationKinds.HasMany:
34
- case RelationKinds.HasOne:
35
- return eq(
36
- { type: 'Column', table: relation.target.name, name: relation.foreignKey },
37
- { type: 'Column', table: rootTable, name: localKey }
38
- );
39
- case RelationKinds.BelongsTo:
40
- return eq(
41
- { type: 'Column', table: relation.target.name, name: localKey },
42
- { type: 'Column', table: rootTable, name: relation.foreignKey }
43
- );
33
+ case RelationKinds.HasMany:
34
+ case RelationKinds.HasOne:
35
+ return eq(
36
+ { type: 'Column', table: relation.target.name, name: relation.foreignKey },
37
+ { type: 'Column', table: rootTable, name: localKey }
38
+ );
39
+ case RelationKinds.BelongsTo:
40
+ return eq(
41
+ { type: 'Column', table: relation.target.name, name: localKey },
42
+ { type: 'Column', table: rootTable, name: relation.foreignKey }
43
+ );
44
44
  case RelationKinds.BelongsToMany:
45
45
  throw new Error('BelongsToMany relations do not support the standard join condition builder');
46
46
  default:
@@ -50,25 +50,36 @@ const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?
50
50
 
51
51
  /**
52
52
  * Builds the join nodes required to include a BelongsToMany relation.
53
+ * @param root - The root table definition
54
+ * @param relationName - Name of the relation being joined
55
+ * @param relation - The BelongsToMany relation definition
56
+ * @param joinKind - The type of join to perform
57
+ * @param extra - Optional additional conditions for the target join
58
+ * @param rootAlias - Optional alias for the root table
59
+ * @returns Array of join nodes for the pivot and target tables
53
60
  */
54
- export const buildBelongsToManyJoins = (
55
- root: TableDef,
56
- relationName: string,
57
- relation: BelongsToManyRelation,
58
- joinKind: JoinKind,
59
- extra?: ExpressionNode,
60
- rootAlias?: string
61
- ): JoinNode[] => {
62
- const rootKey = relation.localKey || findPrimaryKey(root);
63
- const targetKey = relation.targetKey || findPrimaryKey(relation.target);
64
- const rootTable = rootAlias || root.name;
65
-
66
- const pivotCondition = eq(
67
- { type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
68
- { type: 'Column', table: rootTable, name: rootKey }
69
- );
61
+ export const buildBelongsToManyJoins = (
62
+ root: TableDef,
63
+ relationName: string,
64
+ relation: BelongsToManyRelation,
65
+ joinKind: JoinKind,
66
+ extra?: ExpressionNode,
67
+ rootAlias?: string
68
+ ): JoinNode[] => {
69
+ const rootKey = relation.localKey || findPrimaryKey(root);
70
+ const targetKey = relation.targetKey || findPrimaryKey(relation.target);
71
+ const rootTable = rootAlias || root.name;
70
72
 
71
- const pivotJoin = createJoinNode(joinKind, relation.pivotTable.name, pivotCondition);
73
+ const pivotCondition = eq(
74
+ { type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
75
+ { type: 'Column', table: rootTable, name: rootKey }
76
+ );
77
+
78
+ const pivotJoin = createJoinNode(
79
+ joinKind,
80
+ { type: 'Table', name: relation.pivotTable.name, schema: relation.pivotTable.schema },
81
+ pivotCondition
82
+ );
72
83
 
73
84
  let targetCondition: ExpressionNode = eq(
74
85
  { type: 'Column', table: relation.target.name, name: targetKey },
@@ -81,7 +92,7 @@ export const buildBelongsToManyJoins = (
81
92
 
82
93
  const targetJoin = createJoinNode(
83
94
  joinKind,
84
- relation.target.name,
95
+ { type: 'Table', name: relation.target.name, schema: relation.target.schema },
85
96
  targetCondition,
86
97
  relationName
87
98
  );
@@ -94,24 +105,26 @@ export const buildBelongsToManyJoins = (
94
105
  * @param root - Root table definition
95
106
  * @param relation - Relation definition
96
107
  * @param extra - Optional additional expression to combine with AND
108
+ * @param rootAlias - Optional alias for the root table
97
109
  * @returns Expression node representing the complete join condition
98
110
  */
99
- export const buildRelationJoinCondition = (
100
- root: TableDef,
101
- relation: RelationDef,
102
- extra?: ExpressionNode,
103
- rootAlias?: string
104
- ): ExpressionNode => {
105
- const base = baseRelationCondition(root, relation, rootAlias);
106
- return extra ? and(base, extra) : base;
107
- };
111
+ export const buildRelationJoinCondition = (
112
+ root: TableDef,
113
+ relation: RelationDef,
114
+ extra?: ExpressionNode,
115
+ rootAlias?: string
116
+ ): ExpressionNode => {
117
+ const base = baseRelationCondition(root, relation, rootAlias);
118
+ return extra ? and(base, extra) : base;
119
+ };
108
120
 
109
121
  /**
110
122
  * Builds a relation correlation condition for subqueries
111
123
  * @param root - Root table definition
112
124
  * @param relation - Relation definition
125
+ * @param rootAlias - Optional alias for the root table
113
126
  * @returns Expression node representing the correlation condition
114
127
  */
115
- export const buildRelationCorrelation = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
116
- return baseRelationCondition(root, relation, rootAlias);
117
- };
128
+ export const buildRelationCorrelation = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
129
+ return baseRelationCondition(root, relation, rootAlias);
130
+ };