metal-orm 1.0.17 → 1.0.18

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 (39) hide show
  1. package/README.md +4 -3
  2. package/dist/decorators/index.cjs +192 -46
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +1 -1
  5. package/dist/decorators/index.d.ts +1 -1
  6. package/dist/decorators/index.js +192 -46
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +245 -66
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +16 -29
  11. package/dist/index.d.ts +16 -29
  12. package/dist/index.js +243 -66
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-BPCn6MOH.d.cts → select-BuMpVcVt.d.cts} +83 -11
  15. package/dist/{select-BPCn6MOH.d.ts → select-BuMpVcVt.d.ts} +83 -11
  16. package/package.json +4 -1
  17. package/src/codegen/naming-strategy.ts +15 -10
  18. package/src/core/ast/builders.ts +23 -3
  19. package/src/core/ast/expression-builders.ts +14 -1
  20. package/src/core/ast/expression-nodes.ts +11 -9
  21. package/src/core/ast/join-node.ts +5 -3
  22. package/src/core/ast/join.ts +16 -16
  23. package/src/core/ast/query.ts +44 -29
  24. package/src/core/ddl/dialects/mssql-schema-dialect.ts +18 -0
  25. package/src/core/ddl/dialects/mysql-schema-dialect.ts +11 -0
  26. package/src/core/ddl/dialects/postgres-schema-dialect.ts +9 -0
  27. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +9 -0
  28. package/src/core/dialect/base/sql-dialect.ts +58 -46
  29. package/src/core/dialect/mssql/index.ts +53 -28
  30. package/src/core/dialect/sqlite/index.ts +22 -13
  31. package/src/query-builder/column-selector.ts +9 -7
  32. package/src/query-builder/query-ast-service.ts +59 -38
  33. package/src/query-builder/relation-conditions.ts +38 -34
  34. package/src/query-builder/relation-manager.ts +8 -3
  35. package/src/query-builder/relation-service.ts +59 -46
  36. package/src/query-builder/select-query-state.ts +19 -7
  37. package/src/query-builder/select.ts +215 -135
  38. package/src/schema/column.ts +75 -39
  39. package/src/schema/types.ts +1 -0
@@ -1,6 +1,6 @@
1
1
  import { TableDef } from '../schema/table.js';
2
2
  import { ColumnDef } from '../schema/column.js';
3
- import { SelectQueryNode, CommonTableExpressionNode, SetOperationKind, SetOperationNode } from '../core/ast/query.js';
3
+ import { SelectQueryNode, CommonTableExpressionNode, SetOperationKind, SetOperationNode, TableSourceNode } from '../core/ast/query.js';
4
4
  import { buildColumnNode } from '../core/ast/builders.js';
5
5
  import {
6
6
  ColumnNode,
@@ -47,30 +47,36 @@ export class QueryAstService {
47
47
  * @param columns - Columns to select (key: alias, value: column definition or expression)
48
48
  * @returns Column selection result with updated state and added columns
49
49
  */
50
- select(
51
- columns: Record<string, ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode>
52
- ): ColumnSelectionResult {
53
- const existingAliases = new Set(
54
- this.state.ast.columns.map(c => (c as ColumnNode).alias || (c as ColumnNode).name)
55
- );
56
-
57
- const newCols = Object.entries(columns).reduce<ProjectionNode[]>((acc, [alias, val]) => {
58
- if (existingAliases.has(alias)) return acc;
59
-
60
- if (isExpressionSelectionNode(val)) {
61
- acc.push({ ...(val as FunctionNode | CaseExpressionNode | WindowFunctionNode), alias } as ProjectionNode);
62
- return acc;
63
- }
64
-
65
- const colDef = val as ColumnDef;
66
- acc.push({
67
- type: 'Column',
68
- table: colDef.table || this.table.name,
69
- name: colDef.name,
70
- alias
71
- } as ColumnNode);
72
- return acc;
73
- }, []);
50
+ select(
51
+ columns: Record<string, ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode>
52
+ ): ColumnSelectionResult {
53
+ const existingAliases = new Set(
54
+ this.state.ast.columns.map(c => (c as ColumnNode).alias || (c as ColumnNode).name)
55
+ );
56
+ const from = this.state.ast.from;
57
+ const rootTableName = from.type === 'Table' && from.alias ? from.alias : this.table.name;
58
+
59
+ const newCols = Object.entries(columns).reduce<ProjectionNode[]>((acc, [alias, val]) => {
60
+ if (existingAliases.has(alias)) return acc;
61
+
62
+ if (isExpressionSelectionNode(val)) {
63
+ acc.push({ ...(val as FunctionNode | CaseExpressionNode | WindowFunctionNode), alias } as ProjectionNode);
64
+ return acc;
65
+ }
66
+
67
+ const colDef = val as ColumnDef;
68
+ const resolvedTable =
69
+ colDef.table && colDef.table === this.table.name && from.type === 'Table' && from.alias
70
+ ? from.alias
71
+ : colDef.table || rootTableName;
72
+ acc.push({
73
+ type: 'Column',
74
+ table: resolvedTable,
75
+ name: colDef.name,
76
+ alias
77
+ } as ColumnNode);
78
+ return acc;
79
+ }, []);
74
80
 
75
81
  const nextState = this.state.withColumns(newCols);
76
82
  return { state: nextState, addedColumns: newCols };
@@ -81,11 +87,13 @@ export class QueryAstService {
81
87
  * @param cols - Raw column expressions
82
88
  * @returns Column selection result with updated state and added columns
83
89
  */
84
- selectRaw(cols: string[]): ColumnSelectionResult {
85
- const newCols = cols.map(col => parseRawColumn(col, this.table.name, this.state.ast.ctes));
86
- const nextState = this.state.withColumns(newCols);
87
- return { state: nextState, addedColumns: newCols };
88
- }
90
+ selectRaw(cols: string[]): ColumnSelectionResult {
91
+ const from = this.state.ast.from;
92
+ const defaultTable = from.type === 'Table' && from.alias ? from.alias : this.table.name;
93
+ const newCols = cols.map(col => parseRawColumn(col, defaultTable, this.state.ast.ctes));
94
+ const nextState = this.state.withColumns(newCols);
95
+ return { state: nextState, addedColumns: newCols };
96
+ }
89
97
 
90
98
  /**
91
99
  * Adds a Common Table Expression (CTE) to the query
@@ -121,6 +129,15 @@ export class QueryAstService {
121
129
  };
122
130
  return this.state.withSetOperation(op);
123
131
  }
132
+
133
+ /**
134
+ * Replaces the FROM clause for the current query.
135
+ * @param from - Table source to use in the FROM clause
136
+ * @returns Updated query state with new FROM
137
+ */
138
+ withFrom(from: TableSourceNode): SelectQueryState {
139
+ return this.state.withFrom(from);
140
+ }
124
141
 
125
142
  /**
126
143
  * Selects a subquery as a column
@@ -157,10 +174,12 @@ export class QueryAstService {
157
174
  * @param col - Column to group by
158
175
  * @returns Updated query state with GROUP BY clause
159
176
  */
160
- withGroupBy(col: ColumnDef | ColumnNode): SelectQueryState {
161
- const node = buildColumnNode(this.table, col);
162
- return this.state.withGroupBy([node]);
163
- }
177
+ withGroupBy(col: ColumnDef | ColumnNode): SelectQueryState {
178
+ const from = this.state.ast.from;
179
+ const tableRef = from.type === 'Table' && from.alias ? { ...this.table, alias: from.alias } : this.table;
180
+ const node = buildColumnNode(tableRef, col);
181
+ return this.state.withGroupBy([node]);
182
+ }
164
183
 
165
184
  /**
166
185
  * Adds a HAVING clause to the query
@@ -178,10 +197,12 @@ export class QueryAstService {
178
197
  * @param direction - Order direction (ASC/DESC)
179
198
  * @returns Updated query state with ORDER BY clause
180
199
  */
181
- withOrderBy(col: ColumnDef | ColumnNode, direction: OrderDirection): SelectQueryState {
182
- const node = buildColumnNode(this.table, col);
183
- return this.state.withOrderBy([{ type: 'OrderBy', column: node, direction }]);
184
- }
200
+ withOrderBy(col: ColumnDef | ColumnNode, direction: OrderDirection): SelectQueryState {
201
+ const from = this.state.ast.from;
202
+ const tableRef = from.type === 'Table' && from.alias ? { ...this.table, alias: from.alias } : this.table;
203
+ const node = buildColumnNode(tableRef, col);
204
+ return this.state.withOrderBy([{ type: 'OrderBy', column: node, direction }]);
205
+ }
185
206
 
186
207
  /**
187
208
  * Adds a DISTINCT clause to the query
@@ -21,25 +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): ExpressionNode => {
25
- const defaultLocalKey =
24
+ const baseRelationCondition = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
25
+ const rootTable = rootAlias || root.name;
26
+ const defaultLocalKey =
26
27
  relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne
27
28
  ? findPrimaryKey(root)
28
29
  : findPrimaryKey(relation.target);
29
- const localKey = relation.localKey || defaultLocalKey;
30
+ const localKey = relation.localKey || defaultLocalKey;
30
31
 
31
32
  switch (relation.type) {
32
33
  case RelationKinds.HasMany:
33
34
  case RelationKinds.HasOne:
34
35
  return eq(
35
36
  { type: 'Column', table: relation.target.name, name: relation.foreignKey },
36
- { type: 'Column', table: root.name, name: localKey }
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 }
37
43
  );
38
- case RelationKinds.BelongsTo:
39
- return eq(
40
- { type: 'Column', table: relation.target.name, name: localKey },
41
- { type: 'Column', table: root.name, name: relation.foreignKey }
42
- );
43
44
  case RelationKinds.BelongsToMany:
44
45
  throw new Error('BelongsToMany relations do not support the standard join condition builder');
45
46
  default:
@@ -50,20 +51,22 @@ const baseRelationCondition = (root: TableDef, relation: RelationDef): Expressio
50
51
  /**
51
52
  * Builds the join nodes required to include a BelongsToMany relation.
52
53
  */
53
- export const buildBelongsToManyJoins = (
54
- root: TableDef,
55
- relationName: string,
56
- relation: BelongsToManyRelation,
57
- joinKind: JoinKind,
58
- extra?: ExpressionNode
59
- ): JoinNode[] => {
60
- const rootKey = relation.localKey || findPrimaryKey(root);
61
- const targetKey = relation.targetKey || findPrimaryKey(relation.target);
62
-
63
- const pivotCondition = eq(
64
- { type: 'Column', table: relation.pivotTable.name, name: relation.pivotForeignKeyToRoot },
65
- { type: 'Column', table: root.name, name: rootKey }
66
- );
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
+ );
67
70
 
68
71
  const pivotJoin = createJoinNode(joinKind, relation.pivotTable.name, pivotCondition);
69
72
 
@@ -93,14 +96,15 @@ export const buildBelongsToManyJoins = (
93
96
  * @param extra - Optional additional expression to combine with AND
94
97
  * @returns Expression node representing the complete join condition
95
98
  */
96
- export const buildRelationJoinCondition = (
97
- root: TableDef,
98
- relation: RelationDef,
99
- extra?: ExpressionNode
100
- ): ExpressionNode => {
101
- const base = baseRelationCondition(root, relation);
102
- return extra ? and(base, extra) : base;
103
- };
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
+ };
104
108
 
105
109
  /**
106
110
  * Builds a relation correlation condition for subqueries
@@ -108,6 +112,6 @@ export const buildRelationJoinCondition = (
108
112
  * @param relation - Relation definition
109
113
  * @returns Expression node representing the correlation condition
110
114
  */
111
- export const buildRelationCorrelation = (root: TableDef, relation: RelationDef): ExpressionNode => {
112
- return baseRelationCondition(root, relation);
113
- };
115
+ export const buildRelationCorrelation = (root: TableDef, relation: RelationDef, rootAlias?: string): ExpressionNode => {
116
+ return baseRelationCondition(root, relation, rootAlias);
117
+ };
@@ -67,9 +67,14 @@ export class RelationManager {
67
67
  * @param ast - Query AST to modify
68
68
  * @returns Modified query AST with relation correlation
69
69
  */
70
- applyRelationCorrelation(context: SelectQueryBuilderContext, relationName: string, ast: SelectQueryNode): SelectQueryNode {
71
- return this.createService(context).applyRelationCorrelation(relationName, ast);
72
- }
70
+ applyRelationCorrelation(
71
+ context: SelectQueryBuilderContext,
72
+ relationName: string,
73
+ ast: SelectQueryNode,
74
+ additionalCorrelation?: ExpressionNode
75
+ ): SelectQueryNode {
76
+ return this.createService(context).applyRelationCorrelation(relationName, ast, additionalCorrelation);
77
+ }
73
78
 
74
79
  /**
75
80
  * Creates a relation service instance
@@ -70,17 +70,17 @@ export class RelationService {
70
70
  * @param predicate - Optional predicate expression
71
71
  * @returns Relation result with updated state and hydration
72
72
  */
73
- match(
74
- relationName: string,
75
- predicate?: ExpressionNode
76
- ): RelationResult {
77
- const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
78
- const pk = findPrimaryKey(this.table);
79
- const distinctCols: ColumnNode[] = [{ type: 'Column', table: this.table.name, name: pk }];
80
- const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
81
- const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
82
- return { state: nextState, hydration: joined.hydration };
83
- }
73
+ match(
74
+ relationName: string,
75
+ predicate?: ExpressionNode
76
+ ): RelationResult {
77
+ const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
78
+ const pk = findPrimaryKey(this.table);
79
+ const distinctCols: ColumnNode[] = [{ type: 'Column', table: this.rootTableName(), name: pk }];
80
+ const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
81
+ const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
82
+ return { state: nextState, hydration: joined.hydration };
83
+ }
84
84
 
85
85
  /**
86
86
  * Includes a relation in the query result
@@ -190,17 +190,22 @@ export class RelationService {
190
190
  * @param ast - Query AST to modify
191
191
  * @returns Modified query AST with relation correlation
192
192
  */
193
- applyRelationCorrelation(
194
- relationName: string,
195
- ast: SelectQueryNode
196
- ): SelectQueryNode {
197
- const relation = this.getRelation(relationName);
198
- const correlation = buildRelationCorrelation(this.table, relation);
199
- const whereInSubquery = ast.where
200
- ? and(correlation, ast.where)
201
- : correlation;
202
-
203
- return {
193
+ applyRelationCorrelation(
194
+ relationName: string,
195
+ ast: SelectQueryNode,
196
+ additionalCorrelation?: ExpressionNode
197
+ ): SelectQueryNode {
198
+ const relation = this.getRelation(relationName);
199
+ const rootAlias = this.state.ast.from.type === 'Table' ? this.state.ast.from.alias : undefined;
200
+ let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
201
+ if (additionalCorrelation) {
202
+ correlation = and(correlation, additionalCorrelation);
203
+ }
204
+ const whereInSubquery = ast.where
205
+ ? and(correlation, ast.where)
206
+ : correlation;
207
+
208
+ return {
204
209
  ...ast,
205
210
  where: whereInSubquery
206
211
  };
@@ -214,26 +219,28 @@ export class RelationService {
214
219
  * @param extraCondition - Additional join condition
215
220
  * @returns Updated query state with join
216
221
  */
217
- private withJoin(
218
- state: SelectQueryState,
219
- relationName: string,
220
- joinKind: JoinKind,
221
- extraCondition?: ExpressionNode
222
- ): SelectQueryState {
223
- const relation = this.getRelation(relationName);
224
- if (relation.type === RelationKinds.BelongsToMany) {
225
- const joins = buildBelongsToManyJoins(
226
- this.table,
227
- relationName,
228
- relation as BelongsToManyRelation,
229
- joinKind,
230
- extraCondition
231
- );
232
- return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
233
- }
234
-
235
- const condition = buildRelationJoinCondition(this.table, relation, extraCondition);
236
- const joinNode = createJoinNode(joinKind, relation.target.name, condition, relationName);
222
+ private withJoin(
223
+ state: SelectQueryState,
224
+ relationName: string,
225
+ joinKind: JoinKind,
226
+ extraCondition?: ExpressionNode
227
+ ): SelectQueryState {
228
+ const relation = this.getRelation(relationName);
229
+ const rootAlias = state.ast.from.type === 'Table' ? state.ast.from.alias : undefined;
230
+ if (relation.type === RelationKinds.BelongsToMany) {
231
+ const joins = buildBelongsToManyJoins(
232
+ this.table,
233
+ relationName,
234
+ relation as BelongsToManyRelation,
235
+ joinKind,
236
+ extraCondition,
237
+ rootAlias
238
+ );
239
+ return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
240
+ }
241
+
242
+ const condition = buildRelationJoinCondition(this.table, relation, extraCondition, rootAlias);
243
+ const joinNode = createJoinNode(joinKind, relation.target.name, condition, relationName);
237
244
 
238
245
  return this.astService(state).withJoin(joinNode);
239
246
  }
@@ -277,9 +284,15 @@ export class RelationService {
277
284
  * @param state - Current query state
278
285
  * @returns QueryAstService instance
279
286
  */
280
- private astService(state: SelectQueryState = this.state): QueryAstService {
281
- return this.createQueryAstService(this.table, state);
282
- }
283
- }
287
+ private astService(state: SelectQueryState = this.state): QueryAstService {
288
+ return this.createQueryAstService(this.table, state);
289
+ }
290
+
291
+ private rootTableName(): string {
292
+ const from = this.state.ast.from;
293
+ if (from.type === 'Table' && from.alias) return from.alias;
294
+ return this.table.name;
295
+ }
296
+ }
284
297
 
285
298
  export type { RelationResult } from './relation-projection-helper.js';
@@ -1,5 +1,5 @@
1
1
  import { TableDef } from '../schema/table.js';
2
- import { SelectQueryNode, CommonTableExpressionNode, OrderByNode, SetOperationNode } from '../core/ast/query.js';
2
+ import { SelectQueryNode, CommonTableExpressionNode, OrderByNode, SetOperationNode, TableSourceNode } from '../core/ast/query.js';
3
3
  import {
4
4
  ColumnNode,
5
5
  ExpressionNode,
@@ -74,12 +74,24 @@ export class SelectQueryState {
74
74
  * @param join - Join node to add
75
75
  * @returns New SelectQueryState with added join
76
76
  */
77
- withJoin(join: JoinNode): SelectQueryState {
78
- return this.clone({
79
- ...this.ast,
80
- joins: [...(this.ast.joins ?? []), join]
81
- });
82
- }
77
+ withJoin(join: JoinNode): SelectQueryState {
78
+ return this.clone({
79
+ ...this.ast,
80
+ joins: [...(this.ast.joins ?? []), join]
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Replaces the FROM clause.
86
+ * @param from - Table source for the FROM clause
87
+ * @returns New SelectQueryState with updated FROM
88
+ */
89
+ withFrom(from: TableSourceNode): SelectQueryState {
90
+ return this.clone({
91
+ ...this.ast,
92
+ from
93
+ });
94
+ }
83
95
 
84
96
  /**
85
97
  * Adds a WHERE clause to the query