metal-orm 1.0.39 → 1.0.41

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 (52) hide show
  1. package/dist/index.cjs +1466 -189
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +723 -51
  4. package/dist/index.d.ts +723 -51
  5. package/dist/index.js +1457 -189
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/codegen/typescript.ts +66 -5
  9. package/src/core/ast/aggregate-functions.ts +15 -15
  10. package/src/core/ast/expression-builders.ts +378 -316
  11. package/src/core/ast/expression-nodes.ts +210 -186
  12. package/src/core/ast/expression-visitor.ts +40 -30
  13. package/src/core/ast/query.ts +164 -132
  14. package/src/core/ast/window-functions.ts +86 -86
  15. package/src/core/dialect/abstract.ts +509 -479
  16. package/src/core/dialect/base/groupby-compiler.ts +6 -6
  17. package/src/core/dialect/base/join-compiler.ts +9 -12
  18. package/src/core/dialect/base/orderby-compiler.ts +20 -6
  19. package/src/core/dialect/base/sql-dialect.ts +237 -138
  20. package/src/core/dialect/mssql/index.ts +164 -185
  21. package/src/core/dialect/sqlite/index.ts +39 -34
  22. package/src/core/execution/db-executor.ts +46 -6
  23. package/src/core/execution/executors/mssql-executor.ts +39 -22
  24. package/src/core/execution/executors/mysql-executor.ts +23 -6
  25. package/src/core/execution/executors/sqlite-executor.ts +29 -3
  26. package/src/core/execution/pooling/pool-types.ts +30 -0
  27. package/src/core/execution/pooling/pool.ts +268 -0
  28. package/src/core/functions/standard-strategy.ts +46 -37
  29. package/src/decorators/bootstrap.ts +7 -7
  30. package/src/index.ts +6 -0
  31. package/src/orm/domain-event-bus.ts +49 -0
  32. package/src/orm/entity-metadata.ts +9 -9
  33. package/src/orm/entity.ts +58 -0
  34. package/src/orm/orm-session.ts +465 -270
  35. package/src/orm/orm.ts +61 -11
  36. package/src/orm/pooled-executor-factory.ts +131 -0
  37. package/src/orm/query-logger.ts +6 -12
  38. package/src/orm/relation-change-processor.ts +75 -0
  39. package/src/orm/relations/many-to-many.ts +4 -2
  40. package/src/orm/save-graph.ts +303 -0
  41. package/src/orm/transaction-runner.ts +3 -3
  42. package/src/orm/unit-of-work.ts +128 -0
  43. package/src/query-builder/delete-query-state.ts +67 -38
  44. package/src/query-builder/delete.ts +37 -1
  45. package/src/query-builder/hydration-manager.ts +93 -79
  46. package/src/query-builder/insert-query-state.ts +131 -61
  47. package/src/query-builder/insert.ts +27 -1
  48. package/src/query-builder/query-ast-service.ts +207 -170
  49. package/src/query-builder/select-query-state.ts +169 -162
  50. package/src/query-builder/select.ts +15 -23
  51. package/src/query-builder/update-query-state.ts +114 -77
  52. package/src/query-builder/update.ts +38 -1
@@ -1,79 +1,86 @@
1
- import { TableDef } from '../schema/table.js';
2
- import { SelectQueryNode, CommonTableExpressionNode, OrderByNode, SetOperationNode, TableSourceNode } from '../core/ast/query.js';
3
- import {
4
- ColumnNode,
5
- ExpressionNode,
6
- FunctionNode,
7
- ScalarSubqueryNode,
8
- CaseExpressionNode,
9
- WindowFunctionNode
10
- } from '../core/ast/expression.js';
11
- import { JoinNode } from '../core/ast/join.js';
12
-
13
- /**
14
- * Node types that can be used in query projections
15
- */
16
- export type ProjectionNode =
17
- | ColumnNode
18
- | FunctionNode
19
- | ScalarSubqueryNode
20
- | CaseExpressionNode
21
- | WindowFunctionNode;
22
-
23
- /**
24
- * Manages the state of a SELECT query being built
25
- */
26
- export class SelectQueryState {
27
- /**
28
- * Table definition for the query
29
- */
30
- public readonly table: TableDef;
31
- /**
32
- * Abstract Syntax Tree (AST) representation of the query
33
- */
34
- public readonly ast: SelectQueryNode;
35
-
36
- /**
37
- * Creates a new SelectQueryState instance
38
- * @param table - Table definition
39
- * @param ast - Optional existing AST
40
- */
41
- constructor(table: TableDef, ast?: SelectQueryNode) {
42
- this.table = table;
43
- this.ast = ast ?? {
44
- type: 'SelectQuery',
45
- from: { type: 'Table', name: table.name },
46
- columns: [],
47
- joins: []
48
- };
49
- }
50
-
51
- /**
52
- * Creates a new SelectQueryState with updated AST
53
- * @param nextAst - Updated AST
54
- * @returns New SelectQueryState instance
55
- */
56
- private clone(nextAst: SelectQueryNode): SelectQueryState {
57
- return new SelectQueryState(this.table, nextAst);
58
- }
59
-
60
- /**
61
- * Adds columns to the query
62
- * @param newCols - Columns to add
63
- * @returns New SelectQueryState with added columns
64
- */
65
- withColumns(newCols: ProjectionNode[]): SelectQueryState {
66
- return this.clone({
67
- ...this.ast,
68
- columns: [...(this.ast.columns ?? []), ...newCols]
69
- });
70
- }
71
-
72
- /**
73
- * Adds a join to the query
74
- * @param join - Join node to add
75
- * @returns New SelectQueryState with added join
76
- */
1
+ import { TableDef } from '../schema/table.js';
2
+ import {
3
+ SelectQueryNode,
4
+ CommonTableExpressionNode,
5
+ OrderByNode,
6
+ SetOperationNode,
7
+ TableSourceNode,
8
+ OrderingTerm
9
+ } from '../core/ast/query.js';
10
+ import {
11
+ ColumnNode,
12
+ ExpressionNode,
13
+ FunctionNode,
14
+ ScalarSubqueryNode,
15
+ CaseExpressionNode,
16
+ WindowFunctionNode
17
+ } from '../core/ast/expression.js';
18
+ import { JoinNode } from '../core/ast/join.js';
19
+
20
+ /**
21
+ * Node types that can be used in query projections
22
+ */
23
+ export type ProjectionNode =
24
+ | ColumnNode
25
+ | FunctionNode
26
+ | ScalarSubqueryNode
27
+ | CaseExpressionNode
28
+ | WindowFunctionNode;
29
+
30
+ /**
31
+ * Manages the state of a SELECT query being built
32
+ */
33
+ export class SelectQueryState {
34
+ /**
35
+ * Table definition for the query
36
+ */
37
+ public readonly table: TableDef;
38
+ /**
39
+ * Abstract Syntax Tree (AST) representation of the query
40
+ */
41
+ public readonly ast: SelectQueryNode;
42
+
43
+ /**
44
+ * Creates a new SelectQueryState instance
45
+ * @param table - Table definition
46
+ * @param ast - Optional existing AST
47
+ */
48
+ constructor(table: TableDef, ast?: SelectQueryNode) {
49
+ this.table = table;
50
+ this.ast = ast ?? {
51
+ type: 'SelectQuery',
52
+ from: { type: 'Table', name: table.name },
53
+ columns: [],
54
+ joins: []
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Creates a new SelectQueryState with updated AST
60
+ * @param nextAst - Updated AST
61
+ * @returns New SelectQueryState instance
62
+ */
63
+ private clone(nextAst: SelectQueryNode): SelectQueryState {
64
+ return new SelectQueryState(this.table, nextAst);
65
+ }
66
+
67
+ /**
68
+ * Adds columns to the query
69
+ * @param newCols - Columns to add
70
+ * @returns New SelectQueryState with added columns
71
+ */
72
+ withColumns(newCols: ProjectionNode[]): SelectQueryState {
73
+ return this.clone({
74
+ ...this.ast,
75
+ columns: [...(this.ast.columns ?? []), ...newCols]
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Adds a join to the query
81
+ * @param join - Join node to add
82
+ * @returns New SelectQueryState with added join
83
+ */
77
84
  withJoin(join: JoinNode): SelectQueryState {
78
85
  return this.clone({
79
86
  ...this.ast,
@@ -92,92 +99,92 @@ export class SelectQueryState {
92
99
  from
93
100
  });
94
101
  }
95
-
96
- /**
97
- * Adds a WHERE clause to the query
98
- * @param predicate - WHERE predicate expression
99
- * @returns New SelectQueryState with WHERE clause
100
- */
101
- withWhere(predicate: ExpressionNode): SelectQueryState {
102
- return this.clone({
103
- ...this.ast,
104
- where: predicate
105
- });
106
- }
107
-
108
- /**
109
- * Adds a HAVING clause to the query
110
- * @param predicate - HAVING predicate expression
111
- * @returns New SelectQueryState with HAVING clause
112
- */
113
- withHaving(predicate: ExpressionNode): SelectQueryState {
114
- return this.clone({
115
- ...this.ast,
116
- having: predicate
117
- });
118
- }
119
-
120
- /**
121
- * Adds GROUP BY columns to the query
122
- * @param columns - Columns to group by
123
- * @returns New SelectQueryState with GROUP BY clause
124
- */
125
- withGroupBy(columns: ColumnNode[]): SelectQueryState {
126
- return this.clone({
127
- ...this.ast,
128
- groupBy: [...(this.ast.groupBy ?? []), ...columns]
129
- });
130
- }
131
-
132
- /**
133
- * Adds ORDER BY clauses to the query
134
- * @param orderBy - ORDER BY nodes
135
- * @returns New SelectQueryState with ORDER BY clause
136
- */
137
- withOrderBy(orderBy: OrderByNode[]): SelectQueryState {
138
- return this.clone({
139
- ...this.ast,
140
- orderBy: [...(this.ast.orderBy ?? []), ...orderBy]
141
- });
142
- }
143
-
144
- /**
145
- * Adds DISTINCT columns to the query
146
- * @param columns - Columns to make distinct
147
- * @returns New SelectQueryState with DISTINCT clause
148
- */
149
- withDistinct(columns: ColumnNode[]): SelectQueryState {
150
- return this.clone({
151
- ...this.ast,
152
- distinct: [...(this.ast.distinct ?? []), ...columns]
153
- });
154
- }
155
-
156
- /**
157
- * Adds a LIMIT clause to the query
158
- * @param limit - Maximum number of rows to return
159
- * @returns New SelectQueryState with LIMIT clause
160
- */
161
- withLimit(limit: number): SelectQueryState {
162
- return this.clone({
163
- ...this.ast,
164
- limit
165
- });
166
- }
167
-
168
- /**
169
- * Adds an OFFSET clause to the query
170
- * @param offset - Number of rows to skip
171
- * @returns New SelectQueryState with OFFSET clause
172
- */
173
- withOffset(offset: number): SelectQueryState {
174
- return this.clone({
175
- ...this.ast,
176
- offset
177
- });
178
- }
179
-
180
- /**
102
+
103
+ /**
104
+ * Adds a WHERE clause to the query
105
+ * @param predicate - WHERE predicate expression
106
+ * @returns New SelectQueryState with WHERE clause
107
+ */
108
+ withWhere(predicate: ExpressionNode): SelectQueryState {
109
+ return this.clone({
110
+ ...this.ast,
111
+ where: predicate
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Adds a HAVING clause to the query
117
+ * @param predicate - HAVING predicate expression
118
+ * @returns New SelectQueryState with HAVING clause
119
+ */
120
+ withHaving(predicate: ExpressionNode): SelectQueryState {
121
+ return this.clone({
122
+ ...this.ast,
123
+ having: predicate
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Adds GROUP BY columns to the query
129
+ * @param columns - Terms to group by
130
+ * @returns New SelectQueryState with GROUP BY clause
131
+ */
132
+ withGroupBy(columns: OrderingTerm[]): SelectQueryState {
133
+ return this.clone({
134
+ ...this.ast,
135
+ groupBy: [...(this.ast.groupBy ?? []), ...columns]
136
+ });
137
+ }
138
+
139
+ /**
140
+ * Adds ORDER BY clauses to the query
141
+ * @param orderBy - ORDER BY nodes
142
+ * @returns New SelectQueryState with ORDER BY clause
143
+ */
144
+ withOrderBy(orderBy: OrderByNode[]): SelectQueryState {
145
+ return this.clone({
146
+ ...this.ast,
147
+ orderBy: [...(this.ast.orderBy ?? []), ...orderBy]
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Adds DISTINCT columns to the query
153
+ * @param columns - Columns to make distinct
154
+ * @returns New SelectQueryState with DISTINCT clause
155
+ */
156
+ withDistinct(columns: ColumnNode[]): SelectQueryState {
157
+ return this.clone({
158
+ ...this.ast,
159
+ distinct: [...(this.ast.distinct ?? []), ...columns]
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Adds a LIMIT clause to the query
165
+ * @param limit - Maximum number of rows to return
166
+ * @returns New SelectQueryState with LIMIT clause
167
+ */
168
+ withLimit(limit: number): SelectQueryState {
169
+ return this.clone({
170
+ ...this.ast,
171
+ limit
172
+ });
173
+ }
174
+
175
+ /**
176
+ * Adds an OFFSET clause to the query
177
+ * @param offset - Number of rows to skip
178
+ * @returns New SelectQueryState with OFFSET clause
179
+ */
180
+ withOffset(offset: number): SelectQueryState {
181
+ return this.clone({
182
+ ...this.ast,
183
+ offset
184
+ });
185
+ }
186
+
187
+ /**
181
188
  * Adds a Common Table Expression (CTE) to the query
182
189
  * @param cte - CTE node to add
183
190
  * @returns New SelectQueryState with CTE
@@ -2,7 +2,7 @@ import { TableDef } from '../schema/table.js';
2
2
 
3
3
  import { ColumnDef } from '../schema/column.js';
4
4
 
5
- import { SelectQueryNode, SetOperationKind } from '../core/ast/query.js';
5
+ import { OrderingTerm, SelectQueryNode, SetOperationKind } from '../core/ast/query.js';
6
6
 
7
7
  import { HydrationPlan } from '../core/hydration/types.js';
8
8
 
@@ -727,21 +727,13 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
727
727
 
728
728
 
729
729
  /**
730
-
731
730
  * Adds a GROUP BY clause to the query
732
-
733
- * @param col - Column definition or column node to group by
734
-
731
+ * @param term - Column definition or ordering term to group by
735
732
  * @returns New query builder instance with the GROUP BY clause
736
-
737
733
  */
738
-
739
- groupBy(col: ColumnDef | ColumnNode): SelectQueryBuilder<T, TTable> {
740
-
741
- const nextContext = this.applyAst(this.context, service => service.withGroupBy(col));
742
-
734
+ groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
735
+ const nextContext = this.applyAst(this.context, service => service.withGroupBy(term));
743
736
  return this.clone(nextContext);
744
-
745
737
  }
746
738
 
747
739
 
@@ -767,23 +759,23 @@ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
767
759
 
768
760
 
769
761
  /**
770
-
771
762
  * Adds an ORDER BY clause to the query
772
-
773
- * @param col - Column definition or column node to order by
774
-
775
- * @param direction - Order direction (defaults to ASC)
776
-
763
+ * @param term - Column definition or ordering term to order by
764
+ * @param directionOrOptions - Order direction or options (defaults to ASC)
777
765
  * @returns New query builder instance with the ORDER BY clause
778
-
779
766
  */
767
+ orderBy(
768
+ term: ColumnDef | OrderingTerm,
769
+ directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string } = ORDER_DIRECTIONS.ASC
770
+ ): SelectQueryBuilder<T, TTable> {
771
+ const options = typeof directionOrOptions === 'string' ? { direction: directionOrOptions } : directionOrOptions;
772
+ const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
780
773
 
781
- orderBy(col: ColumnDef | ColumnNode, direction: OrderDirection = ORDER_DIRECTIONS.ASC): SelectQueryBuilder<T, TTable> {
782
-
783
- const nextContext = this.applyAst(this.context, service => service.withOrderBy(col, direction));
774
+ const nextContext = this.applyAst(this.context, service =>
775
+ service.withOrderBy(term, dir, options.nulls, options.collation)
776
+ );
784
777
 
785
778
  return this.clone(nextContext);
786
-
787
779
  }
788
780
 
789
781
 
@@ -1,77 +1,114 @@
1
- import { TableDef } from '../schema/table.js';
2
- import { ColumnNode, ExpressionNode, OperandNode, isOperandNode, valueToOperand } from '../core/ast/expression.js';
3
- import { TableNode, UpdateQueryNode, UpdateAssignmentNode } from '../core/ast/query.js';
4
- import { createTableNode } from '../core/ast/builders.js';
5
- type LiteralValue = string | number | boolean | null;
6
- type UpdateValue = OperandNode | LiteralValue;
7
-
8
- const isUpdateValue = (value: unknown): value is UpdateValue => {
9
- if (value === null) return true;
10
- switch (typeof value) {
11
- case 'string':
12
- case 'number':
13
- case 'boolean':
14
- return true;
15
- default:
16
- return isOperandNode(value);
17
- }
18
- };
19
-
20
- /**
21
- * Immutable state for UPDATE queries
22
- */
23
- export class UpdateQueryState {
24
- public readonly table: TableDef;
25
- public readonly ast: UpdateQueryNode;
26
-
27
- constructor(table: TableDef, ast?: UpdateQueryNode) {
28
- this.table = table;
29
- this.ast = ast ?? {
30
- type: 'UpdateQuery',
31
- table: createTableNode(table),
32
- set: []
33
- };
34
- }
35
-
36
- private clone(nextAst: UpdateQueryNode): UpdateQueryState {
37
- return new UpdateQueryState(this.table, nextAst);
38
- }
39
-
40
- withSet(values: Record<string, unknown>): UpdateQueryState {
41
- const assignments: UpdateAssignmentNode[] = Object.entries(values).map(([column, rawValue]) => {
42
- if (!isUpdateValue(rawValue)) {
43
- throw new Error(
44
- `Invalid update value for column "${column}": only primitives, null, or OperandNodes are allowed`
45
- );
46
- }
47
-
48
- return {
49
- column: {
50
- type: 'Column',
51
- table: this.table.name,
52
- name: column
53
- },
54
- value: valueToOperand(rawValue)
55
- };
56
- });
57
-
58
- return this.clone({
59
- ...this.ast,
60
- set: assignments
61
- });
62
- }
63
-
64
- withWhere(expr: ExpressionNode): UpdateQueryState {
65
- return this.clone({
66
- ...this.ast,
67
- where: expr
68
- });
69
- }
70
-
71
- withReturning(columns: ColumnNode[]): UpdateQueryState {
72
- return this.clone({
73
- ...this.ast,
74
- returning: [...columns]
75
- });
76
- }
77
- }
1
+ import { TableDef } from '../schema/table.js';
2
+ import {
3
+ ColumnNode,
4
+ ExpressionNode,
5
+ OperandNode,
6
+ isOperandNode,
7
+ valueToOperand
8
+ } from '../core/ast/expression.js';
9
+ import {
10
+ TableSourceNode,
11
+ UpdateQueryNode,
12
+ UpdateAssignmentNode
13
+ } from '../core/ast/query.js';
14
+ import { JoinNode } from '../core/ast/join.js';
15
+ import { createTableNode } from '../core/ast/builders.js';
16
+
17
+ type LiteralValue = string | number | boolean | null;
18
+ type UpdateValue = OperandNode | LiteralValue;
19
+
20
+ const isUpdateValue = (value: unknown): value is UpdateValue => {
21
+ if (value === null) return true;
22
+ switch (typeof value) {
23
+ case 'string':
24
+ case 'number':
25
+ case 'boolean':
26
+ return true;
27
+ default:
28
+ return isOperandNode(value);
29
+ }
30
+ };
31
+
32
+ /**
33
+ * Immutable state for UPDATE queries
34
+ */
35
+ export class UpdateQueryState {
36
+ public readonly table: TableDef;
37
+ public readonly ast: UpdateQueryNode;
38
+
39
+ constructor(table: TableDef, ast?: UpdateQueryNode) {
40
+ this.table = table;
41
+ this.ast = ast ?? {
42
+ type: 'UpdateQuery',
43
+ table: createTableNode(table),
44
+ set: [],
45
+ joins: []
46
+ };
47
+ }
48
+
49
+ private clone(nextAst: UpdateQueryNode): UpdateQueryState {
50
+ return new UpdateQueryState(this.table, nextAst);
51
+ }
52
+
53
+ withSet(values: Record<string, unknown>): UpdateQueryState {
54
+ const assignments: UpdateAssignmentNode[] = Object.entries(values).map(([column, rawValue]) => {
55
+ if (!isUpdateValue(rawValue)) {
56
+ throw new Error(
57
+ `Invalid update value for column "${column}": only primitives, null, or OperandNodes are allowed`
58
+ );
59
+ }
60
+
61
+ return {
62
+ column: {
63
+ type: 'Column',
64
+ table: this.table.name,
65
+ name: column
66
+ },
67
+ value: valueToOperand(rawValue)
68
+ };
69
+ });
70
+
71
+ return this.clone({
72
+ ...this.ast,
73
+ set: assignments
74
+ });
75
+ }
76
+
77
+ withWhere(expr: ExpressionNode): UpdateQueryState {
78
+ return this.clone({
79
+ ...this.ast,
80
+ where: expr
81
+ });
82
+ }
83
+
84
+ withReturning(columns: ColumnNode[]): UpdateQueryState {
85
+ return this.clone({
86
+ ...this.ast,
87
+ returning: [...columns]
88
+ });
89
+ }
90
+
91
+ withFrom(from: TableSourceNode): UpdateQueryState {
92
+ return this.clone({
93
+ ...this.ast,
94
+ from
95
+ });
96
+ }
97
+
98
+ withJoin(join: JoinNode): UpdateQueryState {
99
+ return this.clone({
100
+ ...this.ast,
101
+ joins: [...(this.ast.joins ?? []), join]
102
+ });
103
+ }
104
+
105
+ withTableAlias(alias: string): UpdateQueryState {
106
+ return this.clone({
107
+ ...this.ast,
108
+ table: {
109
+ ...this.ast.table,
110
+ alias
111
+ }
112
+ });
113
+ }
114
+ }
@@ -1,10 +1,12 @@
1
1
  import { TableDef } from '../schema/table.js';
2
2
  import { ColumnDef } from '../schema/column.js';
3
3
  import { ColumnNode, ExpressionNode } from '../core/ast/expression.js';
4
+ import { JOIN_KINDS, JoinKind } from '../core/sql/sql.js';
4
5
  import { CompiledQuery, UpdateCompiler, Dialect } from '../core/dialect/abstract.js';
5
6
  import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
6
- import { UpdateQueryNode } from '../core/ast/query.js';
7
+ import { TableSourceNode, UpdateQueryNode } from '../core/ast/query.js';
7
8
  import { UpdateQueryState } from './update-query-state.js';
9
+ import { createJoinNode } from '../core/ast/join-node.js';
8
10
  import { buildColumnNode } from '../core/ast/builders.js';
9
11
 
10
12
  type UpdateDialectInput = Dialect | DialectKey;
@@ -25,6 +27,26 @@ export class UpdateQueryBuilder<T> {
25
27
  return new UpdateQueryBuilder(this.table, state);
26
28
  }
27
29
 
30
+ as(alias: string): UpdateQueryBuilder<T> {
31
+ return this.clone(this.state.withTableAlias(alias));
32
+ }
33
+
34
+ from(source: TableDef | TableSourceNode): UpdateQueryBuilder<T> {
35
+ const tableSource = this.resolveTableSource(source);
36
+ return this.clone(this.state.withFrom(tableSource));
37
+ }
38
+
39
+ join(
40
+ table: TableDef | TableSourceNode | string,
41
+ condition: ExpressionNode,
42
+ kind: JoinKind = JOIN_KINDS.INNER,
43
+ relationName?: string
44
+ ): UpdateQueryBuilder<T> {
45
+ const joinTarget = this.resolveJoinTarget(table);
46
+ const joinNode = createJoinNode(kind, joinTarget, condition, relationName);
47
+ return this.clone(this.state.withJoin(joinNode));
48
+ }
49
+
28
50
  set(values: Record<string, unknown>): UpdateQueryBuilder<T> {
29
51
  return this.clone(this.state.withSet(values));
30
52
  }
@@ -39,6 +61,18 @@ export class UpdateQueryBuilder<T> {
39
61
  return this.clone(this.state.withReturning(nodes));
40
62
  }
41
63
 
64
+ private resolveTableSource(source: TableDef | TableSourceNode): TableSourceNode {
65
+ if (isTableSourceNode(source)) {
66
+ return source;
67
+ }
68
+ return { type: 'Table', name: source.name, schema: source.schema };
69
+ }
70
+
71
+ private resolveJoinTarget(table: TableDef | TableSourceNode | string): TableSourceNode | string {
72
+ if (typeof table === 'string') return table;
73
+ return this.resolveTableSource(table);
74
+ }
75
+
42
76
  // Existing compiler-based compile stays, but we add a new overload.
43
77
 
44
78
  // 1) Keep the old behavior (used internally / tests, if any):
@@ -65,3 +99,6 @@ export class UpdateQueryBuilder<T> {
65
99
  return this.state.ast;
66
100
  }
67
101
  }
102
+
103
+ const isTableSourceNode = (source: TableDef | TableSourceNode): source is TableSourceNode =>
104
+ typeof (source as TableSourceNode).type === 'string';