metal-orm 1.0.40 → 1.0.42

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 (45) hide show
  1. package/README.md +53 -14
  2. package/dist/index.cjs +1298 -126
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +676 -30
  5. package/dist/index.d.ts +676 -30
  6. package/dist/index.js +1293 -126
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/codegen/typescript.ts +6 -2
  10. package/src/core/ast/expression-builders.ts +25 -4
  11. package/src/core/ast/expression-nodes.ts +3 -1
  12. package/src/core/ast/expression.ts +2 -2
  13. package/src/core/ast/query.ts +24 -2
  14. package/src/core/dialect/abstract.ts +6 -2
  15. package/src/core/dialect/base/join-compiler.ts +9 -12
  16. package/src/core/dialect/base/sql-dialect.ts +98 -17
  17. package/src/core/dialect/mssql/index.ts +30 -62
  18. package/src/core/dialect/sqlite/index.ts +39 -34
  19. package/src/core/execution/db-executor.ts +46 -6
  20. package/src/core/execution/executors/mssql-executor.ts +39 -22
  21. package/src/core/execution/executors/mysql-executor.ts +23 -6
  22. package/src/core/execution/executors/sqlite-executor.ts +29 -3
  23. package/src/core/execution/pooling/pool-types.ts +30 -0
  24. package/src/core/execution/pooling/pool.ts +268 -0
  25. package/src/decorators/bootstrap.ts +7 -7
  26. package/src/index.ts +6 -0
  27. package/src/orm/domain-event-bus.ts +49 -0
  28. package/src/orm/entity-metadata.ts +9 -9
  29. package/src/orm/entity.ts +58 -0
  30. package/src/orm/orm-session.ts +465 -270
  31. package/src/orm/orm.ts +61 -11
  32. package/src/orm/pooled-executor-factory.ts +131 -0
  33. package/src/orm/query-logger.ts +6 -12
  34. package/src/orm/relation-change-processor.ts +75 -0
  35. package/src/orm/relations/many-to-many.ts +4 -2
  36. package/src/orm/save-graph.ts +303 -0
  37. package/src/orm/transaction-runner.ts +3 -3
  38. package/src/orm/unit-of-work.ts +128 -0
  39. package/src/query-builder/delete-query-state.ts +67 -38
  40. package/src/query-builder/delete.ts +37 -1
  41. package/src/query-builder/insert-query-state.ts +131 -61
  42. package/src/query-builder/insert.ts +27 -1
  43. package/src/query-builder/update-query-state.ts +114 -77
  44. package/src/query-builder/update.ts +38 -1
  45. package/src/schema/table.ts +210 -115
@@ -1,38 +1,67 @@
1
- import { TableDef } from '../schema/table.js';
2
- import { ColumnNode, ExpressionNode } from '../core/ast/expression.js';
3
- import { TableNode, DeleteQueryNode } from '../core/ast/query.js';
4
- import { createTableNode } from '../core/ast/builders.js';
5
-
6
- /**
7
- * Maintains immutable state for DELETE queries
8
- */
9
- export class DeleteQueryState {
10
- public readonly table: TableDef;
11
- public readonly ast: DeleteQueryNode;
12
-
13
- constructor(table: TableDef, ast?: DeleteQueryNode) {
14
- this.table = table;
15
- this.ast = ast ?? {
16
- type: 'DeleteQuery',
17
- from: createTableNode(table)
18
- };
19
- }
20
-
21
- private clone(nextAst: DeleteQueryNode): DeleteQueryState {
22
- return new DeleteQueryState(this.table, nextAst);
23
- }
24
-
25
- withWhere(expr: ExpressionNode): DeleteQueryState {
26
- return this.clone({
27
- ...this.ast,
28
- where: expr
29
- });
30
- }
31
-
32
- withReturning(columns: ColumnNode[]): DeleteQueryState {
33
- return this.clone({
34
- ...this.ast,
35
- returning: [...columns]
36
- });
37
- }
38
- }
1
+ import { TableDef } from '../schema/table.js';
2
+ import { ColumnNode, ExpressionNode } from '../core/ast/expression.js';
3
+ import {
4
+ DeleteQueryNode,
5
+ TableSourceNode
6
+ } from '../core/ast/query.js';
7
+ import { JoinNode } from '../core/ast/join.js';
8
+ import { createTableNode } from '../core/ast/builders.js';
9
+
10
+ /**
11
+ * Maintains immutable state for DELETE queries
12
+ */
13
+ export class DeleteQueryState {
14
+ public readonly table: TableDef;
15
+ public readonly ast: DeleteQueryNode;
16
+
17
+ constructor(table: TableDef, ast?: DeleteQueryNode) {
18
+ this.table = table;
19
+ this.ast = ast ?? {
20
+ type: 'DeleteQuery',
21
+ from: createTableNode(table),
22
+ joins: []
23
+ };
24
+ }
25
+
26
+ private clone(nextAst: DeleteQueryNode): DeleteQueryState {
27
+ return new DeleteQueryState(this.table, nextAst);
28
+ }
29
+
30
+ withWhere(expr: ExpressionNode): DeleteQueryState {
31
+ return this.clone({
32
+ ...this.ast,
33
+ where: expr
34
+ });
35
+ }
36
+
37
+ withReturning(columns: ColumnNode[]): DeleteQueryState {
38
+ return this.clone({
39
+ ...this.ast,
40
+ returning: [...columns]
41
+ });
42
+ }
43
+
44
+ withUsing(source: TableSourceNode): DeleteQueryState {
45
+ return this.clone({
46
+ ...this.ast,
47
+ using: source
48
+ });
49
+ }
50
+
51
+ withJoin(join: JoinNode): DeleteQueryState {
52
+ return this.clone({
53
+ ...this.ast,
54
+ joins: [...(this.ast.joins ?? []), join]
55
+ });
56
+ }
57
+
58
+ withTableAlias(alias: string): DeleteQueryState {
59
+ return this.clone({
60
+ ...this.ast,
61
+ from: {
62
+ ...this.ast.from,
63
+ alias
64
+ }
65
+ });
66
+ }
67
+ }
@@ -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, DeleteCompiler, Dialect } from '../core/dialect/abstract.js';
5
6
  import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
6
- import { DeleteQueryNode } from '../core/ast/query.js';
7
+ import { TableSourceNode, DeleteQueryNode } from '../core/ast/query.js';
7
8
  import { DeleteQueryState } from './delete-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 DeleteDialectInput = Dialect | DialectKey;
@@ -29,12 +31,43 @@ export class DeleteQueryBuilder<T> {
29
31
  return this.clone(this.state.withWhere(expr));
30
32
  }
31
33
 
34
+ as(alias: string): DeleteQueryBuilder<T> {
35
+ return this.clone(this.state.withTableAlias(alias));
36
+ }
37
+
38
+ using(source: TableDef | TableSourceNode): DeleteQueryBuilder<T> {
39
+ return this.clone(this.state.withUsing(this.resolveTableSource(source)));
40
+ }
41
+
42
+ join(
43
+ table: TableDef | TableSourceNode | string,
44
+ condition: ExpressionNode,
45
+ kind: JoinKind = JOIN_KINDS.INNER,
46
+ relationName?: string
47
+ ): DeleteQueryBuilder<T> {
48
+ const target = this.resolveJoinTarget(table);
49
+ const joinNode = createJoinNode(kind, target, condition, relationName);
50
+ return this.clone(this.state.withJoin(joinNode));
51
+ }
52
+
32
53
  returning(...columns: (ColumnDef | ColumnNode)[]): DeleteQueryBuilder<T> {
33
54
  if (!columns.length) return this;
34
55
  const nodes = columns.map(column => buildColumnNode(this.table, column));
35
56
  return this.clone(this.state.withReturning(nodes));
36
57
  }
37
58
 
59
+ private resolveTableSource(source: TableDef | TableSourceNode): TableSourceNode {
60
+ if (isTableSourceNode(source)) {
61
+ return source;
62
+ }
63
+ return { type: 'Table', name: source.name, schema: source.schema };
64
+ }
65
+
66
+ private resolveJoinTarget(table: TableDef | TableSourceNode | string): TableSourceNode | string {
67
+ if (typeof table === 'string') return table;
68
+ return this.resolveTableSource(table);
69
+ }
70
+
38
71
  // Existing compiler-based compile stays, but we add a new overload.
39
72
 
40
73
  // 1) Keep the old behavior (used internally / tests, if any):
@@ -61,3 +94,6 @@ export class DeleteQueryBuilder<T> {
61
94
  return this.state.ast;
62
95
  }
63
96
  }
97
+
98
+ const isTableSourceNode = (source: TableDef | TableSourceNode): source is TableSourceNode =>
99
+ typeof (source as TableSourceNode).type === 'string';
@@ -1,61 +1,131 @@
1
- import { TableDef } from '../schema/table.js';
2
- import { InsertQueryNode, TableNode } from '../core/ast/query.js';
3
- import { ColumnNode, OperandNode, isValueOperandInput, valueToOperand } from '../core/ast/expression.js';
4
- import { buildColumnNodes, createTableNode } from '../core/ast/builders.js';
5
-
6
- /**
7
- * Maintains immutable state for building INSERT queries
8
- */
9
- export class InsertQueryState {
10
- public readonly table: TableDef;
11
- public readonly ast: InsertQueryNode;
12
-
13
- constructor(table: TableDef, ast?: InsertQueryNode) {
14
- this.table = table;
15
- this.ast = ast ?? {
16
- type: 'InsertQuery',
17
- into: createTableNode(table),
18
- columns: [],
19
- values: []
20
- };
21
- }
22
-
23
- private clone(nextAst: InsertQueryNode): InsertQueryState {
24
- return new InsertQueryState(this.table, nextAst);
25
- }
26
-
27
- withValues(rows: Record<string, unknown>[]): InsertQueryState {
28
- if (!rows.length) return this;
29
-
30
- const definedColumns = this.ast.columns.length
31
- ? this.ast.columns
32
- : buildColumnNodes(this.table, Object.keys(rows[0]));
33
-
34
- const newRows: OperandNode[][] = rows.map((row, rowIndex) =>
35
- definedColumns.map(column => {
36
- const rawValue = row[column.name];
37
-
38
- if (!isValueOperandInput(rawValue)) {
39
- throw new Error(
40
- `Invalid insert value for column "${column.name}" in row ${rowIndex}: only primitives, null, or OperandNodes are allowed`
41
- );
42
- }
43
-
44
- return valueToOperand(rawValue);
45
- })
46
- );
47
-
48
- return this.clone({
49
- ...this.ast,
50
- columns: definedColumns,
51
- values: [...this.ast.values, ...newRows]
52
- });
53
- }
54
-
55
- withReturning(columns: ColumnNode[]): InsertQueryState {
56
- return this.clone({
57
- ...this.ast,
58
- returning: [...columns]
59
- });
60
- }
61
- }
1
+ import { TableDef } from '../schema/table.js';
2
+ import { InsertQueryNode, SelectQueryNode } from '../core/ast/query.js';
3
+ import {
4
+ ColumnNode,
5
+ OperandNode,
6
+ isValueOperandInput,
7
+ valueToOperand
8
+ } from '../core/ast/expression.js';
9
+ import {
10
+ buildColumnNodes,
11
+ createTableNode
12
+ } from '../core/ast/builders.js';
13
+
14
+ type InsertRows = Record<string, unknown>[];
15
+
16
+ /**
17
+ * Maintains immutable state for building INSERT queries
18
+ */
19
+ export class InsertQueryState {
20
+ public readonly table: TableDef;
21
+ public readonly ast: InsertQueryNode;
22
+
23
+ constructor(table: TableDef, ast?: InsertQueryNode) {
24
+ this.table = table;
25
+ this.ast = ast ?? {
26
+ type: 'InsertQuery',
27
+ into: createTableNode(table),
28
+ columns: [],
29
+ source: {
30
+ type: 'InsertValues',
31
+ rows: []
32
+ }
33
+ };
34
+ }
35
+
36
+ private clone(nextAst: InsertQueryNode): InsertQueryState {
37
+ return new InsertQueryState(this.table, nextAst);
38
+ }
39
+
40
+ private ensureColumnsFromRow(rows: InsertRows): ColumnNode[] {
41
+ if (this.ast.columns.length) return this.ast.columns;
42
+ return buildColumnNodes(this.table, Object.keys(rows[0]));
43
+ }
44
+
45
+ private appendValues(rows: OperandNode[][]): OperandNode[][] {
46
+ if (this.ast.source.type === 'InsertValues') {
47
+ return [...this.ast.source.rows, ...rows];
48
+ }
49
+ return rows;
50
+ }
51
+
52
+ private getTableColumns(): ColumnNode[] {
53
+ const names = Object.keys(this.table.columns);
54
+ if (!names.length) return [];
55
+ return buildColumnNodes(this.table, names);
56
+ }
57
+
58
+ withValues(rows: Record<string, unknown>[]): InsertQueryState {
59
+ if (!rows.length) return this;
60
+
61
+ if (this.ast.source.type === 'InsertSelect') {
62
+ throw new Error('Cannot mix INSERT ... VALUES with INSERT ... SELECT source.');
63
+ }
64
+
65
+ const definedColumns = this.ensureColumnsFromRow(rows);
66
+
67
+ const newRows: OperandNode[][] = rows.map((row, rowIndex) =>
68
+ definedColumns.map(column => {
69
+ const rawValue = row[column.name];
70
+
71
+ if (!isValueOperandInput(rawValue)) {
72
+ throw new Error(
73
+ `Invalid insert value for column "${column.name}" in row ${rowIndex}: only primitives, null, or OperandNodes are allowed`
74
+ );
75
+ }
76
+
77
+ return valueToOperand(rawValue);
78
+ })
79
+ );
80
+
81
+ return this.clone({
82
+ ...this.ast,
83
+ columns: definedColumns,
84
+ source: {
85
+ type: 'InsertValues',
86
+ rows: this.appendValues(newRows)
87
+ }
88
+ });
89
+ }
90
+
91
+ withColumns(columns: ColumnNode[]): InsertQueryState {
92
+ if (!columns.length) return this;
93
+ return this.clone({
94
+ ...this.ast,
95
+ columns: [...columns]
96
+ });
97
+ }
98
+
99
+ withSelect(query: SelectQueryNode, columns: ColumnNode[]): InsertQueryState {
100
+ const targetColumns =
101
+ columns.length
102
+ ? columns
103
+ : this.ast.columns.length
104
+ ? this.ast.columns
105
+ : this.getTableColumns();
106
+
107
+ if (!targetColumns.length) {
108
+ throw new Error('INSERT ... SELECT requires specifying destination columns.');
109
+ }
110
+
111
+ if (this.ast.source.type === 'InsertValues' && this.ast.source.rows.length) {
112
+ throw new Error('Cannot mix INSERT ... SELECT with INSERT ... VALUES source.');
113
+ }
114
+
115
+ return this.clone({
116
+ ...this.ast,
117
+ columns: [...targetColumns],
118
+ source: {
119
+ type: 'InsertSelect',
120
+ query
121
+ }
122
+ });
123
+ }
124
+
125
+ withReturning(columns: ColumnNode[]): InsertQueryState {
126
+ return this.clone({
127
+ ...this.ast,
128
+ returning: [...columns]
129
+ });
130
+ }
131
+ }
@@ -1,9 +1,10 @@
1
+ import type { SelectQueryBuilder } from './select.js';
1
2
  import { TableDef } from '../schema/table.js';
2
3
  import { ColumnDef } from '../schema/column.js';
3
4
  import { ColumnNode } from '../core/ast/expression.js';
4
5
  import { CompiledQuery, InsertCompiler, Dialect } from '../core/dialect/abstract.js';
5
6
  import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
6
- import { InsertQueryNode } from '../core/ast/query.js';
7
+ import { InsertQueryNode, SelectQueryNode } from '../core/ast/query.js';
7
8
  import { InsertQueryState } from './insert-query-state.js';
8
9
  import { buildColumnNode } from '../core/ast/builders.js';
9
10
 
@@ -31,12 +32,37 @@ export class InsertQueryBuilder<T> {
31
32
  return this.clone(this.state.withValues(rows));
32
33
  }
33
34
 
35
+ columns(...columns: (ColumnDef | ColumnNode)[]): InsertQueryBuilder<T> {
36
+ if (!columns.length) return this;
37
+ return this.clone(this.state.withColumns(this.resolveColumnNodes(columns)));
38
+ }
39
+
40
+ fromSelect(
41
+ query: SelectQueryNode | SelectQueryBuilder<any, TableDef<any>>,
42
+ columns: (ColumnDef | ColumnNode)[] = []
43
+ ): InsertQueryBuilder<T> {
44
+ const ast = this.resolveSelectQuery(query);
45
+ const nodes = columns.length ? this.resolveColumnNodes(columns) : [];
46
+ return this.clone(this.state.withSelect(ast, nodes));
47
+ }
48
+
34
49
  returning(...columns: (ColumnDef | ColumnNode)[]): InsertQueryBuilder<T> {
35
50
  if (!columns.length) return this;
36
51
  const nodes = columns.map(column => buildColumnNode(this.table, column));
37
52
  return this.clone(this.state.withReturning(nodes));
38
53
  }
39
54
 
55
+ // Helpers for column/AST resolution
56
+ private resolveColumnNodes(columns: (ColumnDef | ColumnNode)[]): ColumnNode[] {
57
+ return columns.map(column => buildColumnNode(this.table, column));
58
+ }
59
+
60
+ private resolveSelectQuery(query: SelectQueryNode | SelectQueryBuilder<any>): SelectQueryNode {
61
+ return typeof (query as any).getAST === 'function'
62
+ ? (query as SelectQueryBuilder<any>).getAST()
63
+ : (query as SelectQueryNode);
64
+ }
65
+
40
66
  // Existing compiler-based compile stays, but we add a new overload.
41
67
 
42
68
  // 1) Keep the old behavior (used internally / tests, if any):
@@ -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';