metal-orm 1.0.4 → 1.0.6

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 (57) hide show
  1. package/README.md +299 -113
  2. package/docs/CHANGES.md +104 -0
  3. package/docs/advanced-features.md +92 -1
  4. package/docs/api-reference.md +13 -4
  5. package/docs/dml-operations.md +156 -0
  6. package/docs/getting-started.md +122 -55
  7. package/docs/hydration.md +77 -3
  8. package/docs/index.md +19 -14
  9. package/docs/multi-dialect-support.md +25 -0
  10. package/docs/query-builder.md +60 -0
  11. package/docs/runtime.md +105 -0
  12. package/docs/schema-definition.md +52 -1
  13. package/package.json +1 -1
  14. package/src/ast/expression.ts +630 -592
  15. package/src/ast/query.ts +110 -49
  16. package/src/builder/delete-query-state.ts +42 -0
  17. package/src/builder/delete.ts +57 -0
  18. package/src/builder/hydration-manager.ts +3 -2
  19. package/src/builder/hydration-planner.ts +163 -107
  20. package/src/builder/insert-query-state.ts +62 -0
  21. package/src/builder/insert.ts +59 -0
  22. package/src/builder/operations/relation-manager.ts +1 -23
  23. package/src/builder/relation-conditions.ts +45 -1
  24. package/src/builder/relation-service.ts +81 -18
  25. package/src/builder/relation-types.ts +15 -0
  26. package/src/builder/relation-utils.ts +12 -0
  27. package/src/builder/select.ts +427 -394
  28. package/src/builder/update-query-state.ts +59 -0
  29. package/src/builder/update.ts +61 -0
  30. package/src/constants/sql-operator-config.ts +3 -0
  31. package/src/constants/sql.ts +38 -32
  32. package/src/dialect/abstract.ts +107 -47
  33. package/src/dialect/mssql/index.ts +31 -6
  34. package/src/dialect/mysql/index.ts +31 -6
  35. package/src/dialect/postgres/index.ts +45 -6
  36. package/src/dialect/sqlite/index.ts +45 -6
  37. package/src/index.ts +22 -11
  38. package/src/playground/features/playground/data/scenarios/hydration.ts +23 -11
  39. package/src/playground/features/playground/data/scenarios/types.ts +18 -15
  40. package/src/playground/features/playground/data/schema.ts +6 -2
  41. package/src/playground/features/playground/services/QueryExecutionService.ts +2 -1
  42. package/src/runtime/entity-meta.ts +52 -0
  43. package/src/runtime/entity.ts +252 -0
  44. package/src/runtime/execute.ts +36 -0
  45. package/src/runtime/hydration.ts +100 -38
  46. package/src/runtime/lazy-batch.ts +205 -0
  47. package/src/runtime/orm-context.ts +539 -0
  48. package/src/runtime/relations/belongs-to.ts +92 -0
  49. package/src/runtime/relations/has-many.ts +111 -0
  50. package/src/runtime/relations/many-to-many.ts +149 -0
  51. package/src/schema/column.ts +15 -1
  52. package/src/schema/relation.ts +105 -40
  53. package/src/schema/table.ts +34 -22
  54. package/src/schema/types.ts +76 -0
  55. package/tests/belongs-to-many.test.ts +57 -0
  56. package/tests/dml.test.ts +206 -0
  57. package/tests/orm-runtime.test.ts +254 -0
@@ -1,403 +1,436 @@
1
- import { TableDef } from '../schema/table';
2
- import { ColumnDef } from '../schema/column';
3
- import { SelectQueryNode, HydrationPlan } from '../ast/query';
4
- import {
5
- ColumnNode,
6
- ExpressionNode,
7
- FunctionNode,
8
- LiteralNode,
9
- BinaryExpressionNode,
10
- CaseExpressionNode,
11
- WindowFunctionNode,
12
- exists,
13
- notExists
14
- } from '../ast/expression';
15
- import { CompiledQuery, Dialect } from '../dialect/abstract';
16
- import { SelectQueryState } from './select-query-state';
17
- import { HydrationManager } from './hydration-manager';
18
- import {
19
- defaultSelectQueryBuilderDependencies,
20
- SelectQueryBuilderContext,
21
- SelectQueryBuilderDependencies,
22
- SelectQueryBuilderEnvironment
23
- } from './select-query-builder-deps';
24
- import { ColumnSelector } from './operations/column-selector';
25
- import { CteManager } from './operations/cte-manager';
26
- import { JoinManager } from './operations/join-manager';
27
- import { FilterManager } from './operations/filter-manager';
28
- import { PaginationManager } from './operations/pagination-manager';
29
- import { RelationManager, RelationIncludeOptions } from './operations/relation-manager';
30
- import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../constants/sql';
31
-
32
- /**
33
- * Main query builder class for constructing SQL SELECT queries
34
- * @typeParam T - Type of the result data
35
- */
36
- export class SelectQueryBuilder<T> {
37
- private readonly env: SelectQueryBuilderEnvironment;
38
- private readonly context: SelectQueryBuilderContext;
39
- private readonly columnSelector: ColumnSelector;
40
- private readonly cteManager: CteManager;
41
- private readonly joinManager: JoinManager;
42
- private readonly filterManager: FilterManager;
43
- private readonly paginationManager: PaginationManager;
44
- private readonly relationManager: RelationManager;
45
-
46
- /**
47
- * Creates a new SelectQueryBuilder instance
48
- * @param table - Table definition to query
49
- * @param state - Optional initial query state
50
- * @param hydration - Optional hydration manager
51
- * @param dependencies - Optional query builder dependencies
52
- */
53
- constructor(
54
- table: TableDef,
55
- state?: SelectQueryState,
56
- hydration?: HydrationManager,
57
- dependencies?: SelectQueryBuilderDependencies
58
- ) {
59
- const deps = dependencies ?? defaultSelectQueryBuilderDependencies;
60
- this.env = { table, deps };
61
- const initialState = state ?? deps.createState(table);
62
- const initialHydration = hydration ?? deps.createHydration(table);
63
- this.context = {
64
- state: initialState,
65
- hydration: initialHydration
66
- };
67
- this.columnSelector = new ColumnSelector(this.env);
68
- this.cteManager = new CteManager(this.env);
69
- this.joinManager = new JoinManager(this.env);
70
- this.filterManager = new FilterManager(this.env);
71
- this.paginationManager = new PaginationManager(this.env);
72
- this.relationManager = new RelationManager(this.env);
73
- }
74
-
75
- private clone(context: SelectQueryBuilderContext = this.context): SelectQueryBuilder<T> {
76
- return new SelectQueryBuilder(this.env.table, context.state, context.hydration, this.env.deps);
77
- }
78
-
79
- private resolveQueryNode(query: SelectQueryBuilder<any> | SelectQueryNode): SelectQueryNode {
1
+ import { TableDef } from '../schema/table';
2
+ import { ColumnDef } from '../schema/column';
3
+ import { SelectQueryNode, HydrationPlan } from '../ast/query';
4
+ import {
5
+ ColumnNode,
6
+ ExpressionNode,
7
+ FunctionNode,
8
+ LiteralNode,
9
+ BinaryExpressionNode,
10
+ CaseExpressionNode,
11
+ WindowFunctionNode,
12
+ exists,
13
+ notExists
14
+ } from '../ast/expression';
15
+ import { CompiledQuery, Dialect } from '../dialect/abstract';
16
+ import { SelectQueryState } from './select-query-state';
17
+ import { HydrationManager } from './hydration-manager';
18
+ import {
19
+ defaultSelectQueryBuilderDependencies,
20
+ SelectQueryBuilderContext,
21
+ SelectQueryBuilderDependencies,
22
+ SelectQueryBuilderEnvironment
23
+ } from './select-query-builder-deps';
24
+ import { ColumnSelector } from './operations/column-selector';
25
+ import { CteManager } from './operations/cte-manager';
26
+ import { JoinManager } from './operations/join-manager';
27
+ import { FilterManager } from './operations/filter-manager';
28
+ import { PaginationManager } from './operations/pagination-manager';
29
+ import { RelationManager } from './operations/relation-manager';
30
+ import { RelationIncludeOptions } from './relation-types';
31
+ import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../constants/sql';
32
+ import { Entity, RelationMap } from '../schema/types';
33
+ import { OrmContext } from '../runtime/orm-context';
34
+ import { executeHydrated } from '../runtime/execute';
35
+
36
+ /**
37
+ * Main query builder class for constructing SQL SELECT queries
38
+ * @typeParam T - Result type for projections (unused)
39
+ * @typeParam TTable - Table definition being queried
40
+ */
41
+ export class SelectQueryBuilder<T = any, TTable extends TableDef = TableDef> {
42
+ private readonly env: SelectQueryBuilderEnvironment;
43
+ private readonly context: SelectQueryBuilderContext;
44
+ private readonly columnSelector: ColumnSelector;
45
+ private readonly cteManager: CteManager;
46
+ private readonly joinManager: JoinManager;
47
+ private readonly filterManager: FilterManager;
48
+ private readonly paginationManager: PaginationManager;
49
+ private readonly relationManager: RelationManager;
50
+ private readonly lazyRelations: Set<string>;
51
+
52
+ /**
53
+ * Creates a new SelectQueryBuilder instance
54
+ * @param table - Table definition to query
55
+ * @param state - Optional initial query state
56
+ * @param hydration - Optional hydration manager
57
+ * @param dependencies - Optional query builder dependencies
58
+ */
59
+ constructor(
60
+ table: TTable,
61
+ state?: SelectQueryState,
62
+ hydration?: HydrationManager,
63
+ dependencies?: SelectQueryBuilderDependencies,
64
+ lazyRelations?: Set<string>
65
+ ) {
66
+ const deps = dependencies ?? defaultSelectQueryBuilderDependencies;
67
+ this.env = { table, deps };
68
+ const initialState = state ?? deps.createState(table);
69
+ const initialHydration = hydration ?? deps.createHydration(table);
70
+ this.context = {
71
+ state: initialState,
72
+ hydration: initialHydration
73
+ };
74
+ this.lazyRelations = new Set(lazyRelations ?? []);
75
+ this.columnSelector = new ColumnSelector(this.env);
76
+ this.cteManager = new CteManager(this.env);
77
+ this.joinManager = new JoinManager(this.env);
78
+ this.filterManager = new FilterManager(this.env);
79
+ this.paginationManager = new PaginationManager(this.env);
80
+ this.relationManager = new RelationManager(this.env);
81
+ }
82
+
83
+ private clone(
84
+ context: SelectQueryBuilderContext = this.context,
85
+ lazyRelations = new Set(this.lazyRelations)
86
+ ): SelectQueryBuilder<T, TTable> {
87
+ return new SelectQueryBuilder(this.env.table as TTable, context.state, context.hydration, this.env.deps, lazyRelations);
88
+ }
89
+
90
+ private resolveQueryNode(query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryNode {
80
91
  return typeof (query as any).getAST === 'function'
81
- ? (query as SelectQueryBuilder<any>).getAST()
92
+ ? (query as SelectQueryBuilder<any, TableDef<any>>).getAST()
82
93
  : (query as SelectQueryNode);
83
94
  }
84
-
85
- private createChildBuilder<R>(table: TableDef): SelectQueryBuilder<R> {
95
+
96
+ private createChildBuilder<R, TChild extends TableDef>(table: TChild): SelectQueryBuilder<R, TChild> {
86
97
  return new SelectQueryBuilder(table, undefined, undefined, this.env.deps);
87
98
  }
88
-
89
- /**
90
- * Selects specific columns for the query
91
- * @param columns - Record of column definitions, function nodes, case expressions, or window functions
92
- * @returns New query builder instance with selected columns
93
- */
94
- select(columns: Record<string, ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode>): SelectQueryBuilder<T> {
95
- return this.clone(this.columnSelector.select(this.context, columns));
96
- }
97
-
98
- /**
99
- * Selects raw column expressions
100
- * @param cols - Column expressions as strings
101
- * @returns New query builder instance with raw column selections
102
- */
103
- selectRaw(...cols: string[]): SelectQueryBuilder<T> {
104
- return this.clone(this.columnSelector.selectRaw(this.context, cols));
105
- }
106
-
107
- /**
108
- * Adds a Common Table Expression (CTE) to the query
109
- * @param name - Name of the CTE
110
- * @param query - Query builder or query node for the CTE
111
- * @param columns - Optional column names for the CTE
112
- * @returns New query builder instance with the CTE
113
- */
114
- with(name: string, query: SelectQueryBuilder<any> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T> {
115
- const subAst = this.resolveQueryNode(query);
116
- const nextContext = this.cteManager.withCte(this.context, name, subAst, columns, false);
117
- return this.clone(nextContext);
118
- }
119
-
120
- /**
121
- * Adds a recursive Common Table Expression (CTE) to the query
122
- * @param name - Name of the CTE
123
- * @param query - Query builder or query node for the CTE
124
- * @param columns - Optional column names for the CTE
125
- * @returns New query builder instance with the recursive CTE
126
- */
127
- withRecursive(name: string, query: SelectQueryBuilder<any> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T> {
128
- const subAst = this.resolveQueryNode(query);
129
- const nextContext = this.cteManager.withCte(this.context, name, subAst, columns, true);
130
- return this.clone(nextContext);
131
- }
132
-
133
- /**
134
- * Selects a subquery as a column
135
- * @param alias - Alias for the subquery column
136
- * @param sub - Query builder or query node for the subquery
137
- * @returns New query builder instance with the subquery selection
138
- */
139
- selectSubquery(alias: string, sub: SelectQueryBuilder<any> | SelectQueryNode): SelectQueryBuilder<T> {
140
- const query = this.resolveQueryNode(sub);
141
- return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
142
- }
143
-
144
- /**
145
- * Adds an INNER JOIN to the query
146
- * @param table - Table to join
147
- * @param condition - Join condition expression
148
- * @returns New query builder instance with the INNER JOIN
149
- */
150
- innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T> {
151
- const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.INNER);
152
- return this.clone(nextContext);
153
- }
154
-
155
- /**
156
- * Adds a LEFT JOIN to the query
157
- * @param table - Table to join
158
- * @param condition - Join condition expression
159
- * @returns New query builder instance with the LEFT JOIN
160
- */
161
- leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T> {
162
- const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.LEFT);
163
- return this.clone(nextContext);
164
- }
165
-
166
- /**
167
- * Adds a RIGHT JOIN to the query
168
- * @param table - Table to join
169
- * @param condition - Join condition expression
170
- * @returns New query builder instance with the RIGHT JOIN
171
- */
172
- rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T> {
173
- const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.RIGHT);
174
- return this.clone(nextContext);
175
- }
176
-
177
- /**
178
- * Matches records based on a relationship
179
- * @param relationName - Name of the relationship to match
180
- * @param predicate - Optional predicate expression
181
- * @returns New query builder instance with the relationship match
182
- */
183
- match(relationName: string, predicate?: ExpressionNode): SelectQueryBuilder<T> {
184
- const nextContext = this.relationManager.match(this.context, relationName, predicate);
185
- return this.clone(nextContext);
186
- }
187
-
188
- /**
189
- * Joins a related table
190
- * @param relationName - Name of the relationship to join
191
- * @param joinKind - Type of join (defaults to INNER)
192
- * @param extraCondition - Optional additional join condition
193
- * @returns New query builder instance with the relationship join
194
- */
195
- joinRelation(
196
- relationName: string,
197
- joinKind: JoinKind = JOIN_KINDS.INNER,
198
- extraCondition?: ExpressionNode
199
- ): SelectQueryBuilder<T> {
200
- const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
201
- return this.clone(nextContext);
202
- }
203
-
204
- /**
205
- * Includes related data in the query results
206
- * @param relationName - Name of the relationship to include
207
- * @param options - Optional include options
208
- * @returns New query builder instance with the relationship inclusion
209
- */
210
- include(relationName: string, options?: RelationIncludeOptions): SelectQueryBuilder<T> {
211
- const nextContext = this.relationManager.include(this.context, relationName, options);
212
- return this.clone(nextContext);
213
- }
214
-
215
- /**
216
- * Adds a WHERE condition to the query
217
- * @param expr - Expression for the WHERE clause
218
- * @returns New query builder instance with the WHERE condition
219
- */
220
- where(expr: ExpressionNode): SelectQueryBuilder<T> {
221
- const nextContext = this.filterManager.where(this.context, expr);
222
- return this.clone(nextContext);
223
- }
224
-
225
- /**
226
- * Adds a GROUP BY clause to the query
227
- * @param col - Column definition or column node to group by
228
- * @returns New query builder instance with the GROUP BY clause
229
- */
230
- groupBy(col: ColumnDef | ColumnNode): SelectQueryBuilder<T> {
231
- const nextContext = this.filterManager.groupBy(this.context, col);
232
- return this.clone(nextContext);
233
- }
234
-
235
- /**
236
- * Adds a HAVING condition to the query
237
- * @param expr - Expression for the HAVING clause
238
- * @returns New query builder instance with the HAVING condition
239
- */
240
- having(expr: ExpressionNode): SelectQueryBuilder<T> {
241
- const nextContext = this.filterManager.having(this.context, expr);
242
- return this.clone(nextContext);
243
- }
244
-
245
- /**
246
- * Adds an ORDER BY clause to the query
247
- * @param col - Column definition or column node to order by
248
- * @param direction - Order direction (defaults to ASC)
249
- * @returns New query builder instance with the ORDER BY clause
250
- */
251
- orderBy(col: ColumnDef | ColumnNode, direction: OrderDirection = ORDER_DIRECTIONS.ASC): SelectQueryBuilder<T> {
252
- const nextContext = this.filterManager.orderBy(this.context, col, direction);
253
- return this.clone(nextContext);
254
- }
255
-
256
- /**
257
- * Adds a DISTINCT clause to the query
258
- * @param cols - Columns to make distinct
259
- * @returns New query builder instance with the DISTINCT clause
260
- */
261
- distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T> {
262
- return this.clone(this.columnSelector.distinct(this.context, cols));
263
- }
264
-
265
- /**
266
- * Adds a LIMIT clause to the query
267
- * @param n - Maximum number of rows to return
268
- * @returns New query builder instance with the LIMIT clause
269
- */
270
- limit(n: number): SelectQueryBuilder<T> {
271
- const nextContext = this.paginationManager.limit(this.context, n);
272
- return this.clone(nextContext);
273
- }
274
-
275
- /**
276
- * Adds an OFFSET clause to the query
277
- * @param n - Number of rows to skip
278
- * @returns New query builder instance with the OFFSET clause
279
- */
280
- offset(n: number): SelectQueryBuilder<T> {
281
- const nextContext = this.paginationManager.offset(this.context, n);
282
- return this.clone(nextContext);
283
- }
284
-
285
- /**
286
- * Adds a WHERE EXISTS condition to the query
287
- * @param subquery - Subquery to check for existence
288
- * @returns New query builder instance with the WHERE EXISTS condition
289
- */
290
- whereExists(subquery: SelectQueryBuilder<any> | SelectQueryNode): SelectQueryBuilder<T> {
291
- const subAst = this.resolveQueryNode(subquery);
292
- return this.where(exists(subAst));
293
- }
294
-
295
- /**
296
- * Adds a WHERE NOT EXISTS condition to the query
297
- * @param subquery - Subquery to check for non-existence
298
- * @returns New query builder instance with the WHERE NOT EXISTS condition
299
- */
300
- whereNotExists(subquery: SelectQueryBuilder<any> | SelectQueryNode): SelectQueryBuilder<T> {
301
- const subAst = this.resolveQueryNode(subquery);
302
- return this.where(notExists(subAst));
303
- }
304
-
305
- /**
306
- * Adds a WHERE EXISTS condition based on a relationship
307
- * @param relationName - Name of the relationship to check
308
- * @param callback - Optional callback to modify the relationship query
309
- * @returns New query builder instance with the relationship existence check
310
- */
99
+
100
+ /**
101
+ * Selects specific columns for the query
102
+ * @param columns - Record of column definitions, function nodes, case expressions, or window functions
103
+ * @returns New query builder instance with selected columns
104
+ */
105
+ select(columns: Record<string, ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode>): SelectQueryBuilder<T, TTable> {
106
+ return this.clone(this.columnSelector.select(this.context, columns));
107
+ }
108
+
109
+ /**
110
+ * Selects raw column expressions
111
+ * @param cols - Column expressions as strings
112
+ * @returns New query builder instance with raw column selections
113
+ */
114
+ selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
115
+ return this.clone(this.columnSelector.selectRaw(this.context, cols));
116
+ }
117
+
118
+ /**
119
+ * Adds a Common Table Expression (CTE) to the query
120
+ * @param name - Name of the CTE
121
+ * @param query - Query builder or query node for the CTE
122
+ * @param columns - Optional column names for the CTE
123
+ * @returns New query builder instance with the CTE
124
+ */
125
+ with(name: string, query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
126
+ const subAst = this.resolveQueryNode(query);
127
+ const nextContext = this.cteManager.withCte(this.context, name, subAst, columns, false);
128
+ return this.clone(nextContext);
129
+ }
130
+
131
+ /**
132
+ * Adds a recursive Common Table Expression (CTE) to the query
133
+ * @param name - Name of the CTE
134
+ * @param query - Query builder or query node for the CTE
135
+ * @param columns - Optional column names for the CTE
136
+ * @returns New query builder instance with the recursive CTE
137
+ */
138
+ withRecursive(name: string, query: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
139
+ const subAst = this.resolveQueryNode(query);
140
+ const nextContext = this.cteManager.withCte(this.context, name, subAst, columns, true);
141
+ return this.clone(nextContext);
142
+ }
143
+
144
+ /**
145
+ * Selects a subquery as a column
146
+ * @param alias - Alias for the subquery column
147
+ * @param sub - Query builder or query node for the subquery
148
+ * @returns New query builder instance with the subquery selection
149
+ */
150
+ selectSubquery(alias: string, sub: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
151
+ const query = this.resolveQueryNode(sub);
152
+ return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
153
+ }
154
+
155
+ /**
156
+ * Adds an INNER JOIN to the query
157
+ * @param table - Table to join
158
+ * @param condition - Join condition expression
159
+ * @returns New query builder instance with the INNER JOIN
160
+ */
161
+ innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
162
+ const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.INNER);
163
+ return this.clone(nextContext);
164
+ }
165
+
166
+ /**
167
+ * Adds a LEFT JOIN to the query
168
+ * @param table - Table to join
169
+ * @param condition - Join condition expression
170
+ * @returns New query builder instance with the LEFT JOIN
171
+ */
172
+ leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
173
+ const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.LEFT);
174
+ return this.clone(nextContext);
175
+ }
176
+
177
+ /**
178
+ * Adds a RIGHT JOIN to the query
179
+ * @param table - Table to join
180
+ * @param condition - Join condition expression
181
+ * @returns New query builder instance with the RIGHT JOIN
182
+ */
183
+ rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
184
+ const nextContext = this.joinManager.join(this.context, table, condition, JOIN_KINDS.RIGHT);
185
+ return this.clone(nextContext);
186
+ }
187
+
188
+ /**
189
+ * Matches records based on a relationship
190
+ * @param relationName - Name of the relationship to match
191
+ * @param predicate - Optional predicate expression
192
+ * @returns New query builder instance with the relationship match
193
+ */
194
+ match(relationName: string, predicate?: ExpressionNode): SelectQueryBuilder<T, TTable> {
195
+ const nextContext = this.relationManager.match(this.context, relationName, predicate);
196
+ return this.clone(nextContext);
197
+ }
198
+
199
+ /**
200
+ * Joins a related table
201
+ * @param relationName - Name of the relationship to join
202
+ * @param joinKind - Type of join (defaults to INNER)
203
+ * @param extraCondition - Optional additional join condition
204
+ * @returns New query builder instance with the relationship join
205
+ */
206
+ joinRelation(
207
+ relationName: string,
208
+ joinKind: JoinKind = JOIN_KINDS.INNER,
209
+ extraCondition?: ExpressionNode
210
+ ): SelectQueryBuilder<T, TTable> {
211
+ const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
212
+ return this.clone(nextContext);
213
+ }
214
+
215
+ /**
216
+ * Includes related data in the query results
217
+ * @param relationName - Name of the relationship to include
218
+ * @param options - Optional include options
219
+ * @returns New query builder instance with the relationship inclusion
220
+ */
221
+ include(relationName: string, options?: RelationIncludeOptions): SelectQueryBuilder<T, TTable> {
222
+ const nextContext = this.relationManager.include(this.context, relationName, options);
223
+ return this.clone(nextContext);
224
+ }
225
+
226
+ includeLazy<K extends keyof RelationMap<TTable>>(relationName: K): SelectQueryBuilder<T, TTable> {
227
+ const nextLazy = new Set(this.lazyRelations);
228
+ nextLazy.add(relationName as string);
229
+ return this.clone(this.context, nextLazy);
230
+ }
231
+
232
+ getLazyRelations(): (keyof RelationMap<TTable>)[] {
233
+ return Array.from(this.lazyRelations) as (keyof RelationMap<TTable>)[];
234
+ }
235
+
236
+ getTable(): TTable {
237
+ return this.env.table as TTable;
238
+ }
239
+
240
+ async execute(ctx: OrmContext): Promise<Entity<TTable>[]> {
241
+ return executeHydrated(ctx, this);
242
+ }
243
+
244
+ /**
245
+ * Adds a WHERE condition to the query
246
+ * @param expr - Expression for the WHERE clause
247
+ * @returns New query builder instance with the WHERE condition
248
+ */
249
+ where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
250
+ const nextContext = this.filterManager.where(this.context, expr);
251
+ return this.clone(nextContext);
252
+ }
253
+
254
+ /**
255
+ * Adds a GROUP BY clause to the query
256
+ * @param col - Column definition or column node to group by
257
+ * @returns New query builder instance with the GROUP BY clause
258
+ */
259
+ groupBy(col: ColumnDef | ColumnNode): SelectQueryBuilder<T, TTable> {
260
+ const nextContext = this.filterManager.groupBy(this.context, col);
261
+ return this.clone(nextContext);
262
+ }
263
+
264
+ /**
265
+ * Adds a HAVING condition to the query
266
+ * @param expr - Expression for the HAVING clause
267
+ * @returns New query builder instance with the HAVING condition
268
+ */
269
+ having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
270
+ const nextContext = this.filterManager.having(this.context, expr);
271
+ return this.clone(nextContext);
272
+ }
273
+
274
+ /**
275
+ * Adds an ORDER BY clause to the query
276
+ * @param col - Column definition or column node to order by
277
+ * @param direction - Order direction (defaults to ASC)
278
+ * @returns New query builder instance with the ORDER BY clause
279
+ */
280
+ orderBy(col: ColumnDef | ColumnNode, direction: OrderDirection = ORDER_DIRECTIONS.ASC): SelectQueryBuilder<T, TTable> {
281
+ const nextContext = this.filterManager.orderBy(this.context, col, direction);
282
+ return this.clone(nextContext);
283
+ }
284
+
285
+ /**
286
+ * Adds a DISTINCT clause to the query
287
+ * @param cols - Columns to make distinct
288
+ * @returns New query builder instance with the DISTINCT clause
289
+ */
290
+ distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
291
+ return this.clone(this.columnSelector.distinct(this.context, cols));
292
+ }
293
+
294
+ /**
295
+ * Adds a LIMIT clause to the query
296
+ * @param n - Maximum number of rows to return
297
+ * @returns New query builder instance with the LIMIT clause
298
+ */
299
+ limit(n: number): SelectQueryBuilder<T, TTable> {
300
+ const nextContext = this.paginationManager.limit(this.context, n);
301
+ return this.clone(nextContext);
302
+ }
303
+
304
+ /**
305
+ * Adds an OFFSET clause to the query
306
+ * @param n - Number of rows to skip
307
+ * @returns New query builder instance with the OFFSET clause
308
+ */
309
+ offset(n: number): SelectQueryBuilder<T, TTable> {
310
+ const nextContext = this.paginationManager.offset(this.context, n);
311
+ return this.clone(nextContext);
312
+ }
313
+
314
+ /**
315
+ * Adds a WHERE EXISTS condition to the query
316
+ * @param subquery - Subquery to check for existence
317
+ * @returns New query builder instance with the WHERE EXISTS condition
318
+ */
319
+ whereExists(subquery: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
320
+ const subAst = this.resolveQueryNode(subquery);
321
+ return this.where(exists(subAst));
322
+ }
323
+
324
+ /**
325
+ * Adds a WHERE NOT EXISTS condition to the query
326
+ * @param subquery - Subquery to check for non-existence
327
+ * @returns New query builder instance with the WHERE NOT EXISTS condition
328
+ */
329
+ whereNotExists(subquery: SelectQueryBuilder<any, TableDef<any>> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
330
+ const subAst = this.resolveQueryNode(subquery);
331
+ return this.where(notExists(subAst));
332
+ }
333
+
334
+ /**
335
+ * Adds a WHERE EXISTS condition based on a relationship
336
+ * @param relationName - Name of the relationship to check
337
+ * @param callback - Optional callback to modify the relationship query
338
+ * @returns New query builder instance with the relationship existence check
339
+ */
311
340
  whereHas(
312
341
  relationName: string,
313
- callback?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>
314
- ): SelectQueryBuilder<T> {
315
- const relation = this.env.table.relations[relationName];
316
- if (!relation) {
317
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
318
- }
319
-
320
- let subQb = this.createChildBuilder<any>(relation.target);
321
- if (callback) {
322
- subQb = callback(subQb);
323
- }
324
-
325
- const subAst = subQb.getAST();
326
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
327
- return this.where(exists(finalSubAst));
328
- }
329
-
330
- /**
331
- * Adds a WHERE NOT EXISTS condition based on a relationship
332
- * @param relationName - Name of the relationship to check
333
- * @param callback - Optional callback to modify the relationship query
334
- * @returns New query builder instance with the relationship non-existence check
335
- */
342
+ callback?: <TChildTable extends TableDef>(
343
+ qb: SelectQueryBuilder<any, TChildTable>
344
+ ) => SelectQueryBuilder<any, TChildTable>
345
+ ): SelectQueryBuilder<T, TTable> {
346
+ const relation = this.env.table.relations[relationName];
347
+ if (!relation) {
348
+ throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
349
+ }
350
+
351
+ let subQb = this.createChildBuilder<any, typeof relation.target>(relation.target);
352
+ if (callback) {
353
+ subQb = callback(subQb);
354
+ }
355
+
356
+ const subAst = subQb.getAST();
357
+ const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
358
+ return this.where(exists(finalSubAst));
359
+ }
360
+
361
+ /**
362
+ * Adds a WHERE NOT EXISTS condition based on a relationship
363
+ * @param relationName - Name of the relationship to check
364
+ * @param callback - Optional callback to modify the relationship query
365
+ * @returns New query builder instance with the relationship non-existence check
366
+ */
336
367
  whereHasNot(
337
368
  relationName: string,
338
- callback?: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>
339
- ): SelectQueryBuilder<T> {
340
- const relation = this.env.table.relations[relationName];
341
- if (!relation) {
342
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
343
- }
344
-
345
- let subQb = this.createChildBuilder<any>(relation.target);
346
- if (callback) {
347
- subQb = callback(subQb);
348
- }
349
-
350
- const subAst = subQb.getAST();
351
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
352
- return this.where(notExists(finalSubAst));
353
- }
354
-
355
- /**
356
- * Compiles the query to SQL for a specific dialect
357
- * @param dialect - Database dialect to compile for
358
- * @returns Compiled query with SQL and parameters
359
- */
360
- compile(dialect: Dialect): CompiledQuery {
361
- return dialect.compileSelect(this.context.state.ast);
362
- }
363
-
364
- /**
365
- * Converts the query to SQL string for a specific dialect
366
- * @param dialect - Database dialect to generate SQL for
367
- * @returns SQL string representation of the query
368
- */
369
- toSql(dialect: Dialect): string {
370
- return this.compile(dialect).sql;
371
- }
372
-
373
- /**
374
- * Gets the hydration plan for the query
375
- * @returns Hydration plan or undefined if none exists
376
- */
377
- getHydrationPlan(): HydrationPlan | undefined {
378
- return this.context.hydration.getPlan();
379
- }
380
-
381
- /**
382
- * Gets the Abstract Syntax Tree (AST) representation of the query
383
- * @returns Query AST with hydration applied
384
- */
385
- getAST(): SelectQueryNode {
386
- return this.context.hydration.applyToAst(this.context.state.ast);
387
- }
388
- }
389
-
390
- /**
391
- * Creates a column node for use in expressions
392
- * @param table - Table name
393
- * @param name - Column name
394
- * @returns ColumnNode with the specified table and name
395
- */
396
- export const createColumn = (table: string, name: string): ColumnNode => ({ type: 'Column', table, name });
397
-
398
- /**
399
- * Creates a literal value node for use in expressions
400
- * @param val - Literal value (string or number)
401
- * @returns LiteralNode with the specified value
402
- */
403
- export const createLiteral = (val: string | number): LiteralNode => ({ type: 'Literal', value: val });
369
+ callback?: <TChildTable extends TableDef>(
370
+ qb: SelectQueryBuilder<any, TChildTable>
371
+ ) => SelectQueryBuilder<any, TChildTable>
372
+ ): SelectQueryBuilder<T, TTable> {
373
+ const relation = this.env.table.relations[relationName];
374
+ if (!relation) {
375
+ throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
376
+ }
377
+
378
+ let subQb = this.createChildBuilder<any, typeof relation.target>(relation.target);
379
+ if (callback) {
380
+ subQb = callback(subQb);
381
+ }
382
+
383
+ const subAst = subQb.getAST();
384
+ const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst);
385
+ return this.where(notExists(finalSubAst));
386
+ }
387
+
388
+ /**
389
+ * Compiles the query to SQL for a specific dialect
390
+ * @param dialect - Database dialect to compile for
391
+ * @returns Compiled query with SQL and parameters
392
+ */
393
+ compile(dialect: Dialect): CompiledQuery {
394
+ return dialect.compileSelect(this.context.state.ast);
395
+ }
396
+
397
+ /**
398
+ * Converts the query to SQL string for a specific dialect
399
+ * @param dialect - Database dialect to generate SQL for
400
+ * @returns SQL string representation of the query
401
+ */
402
+ toSql(dialect: Dialect): string {
403
+ return this.compile(dialect).sql;
404
+ }
405
+
406
+ /**
407
+ * Gets the hydration plan for the query
408
+ * @returns Hydration plan or undefined if none exists
409
+ */
410
+ getHydrationPlan(): HydrationPlan | undefined {
411
+ return this.context.hydration.getPlan();
412
+ }
413
+
414
+ /**
415
+ * Gets the Abstract Syntax Tree (AST) representation of the query
416
+ * @returns Query AST with hydration applied
417
+ */
418
+ getAST(): SelectQueryNode {
419
+ return this.context.hydration.applyToAst(this.context.state.ast);
420
+ }
421
+ }
422
+
423
+ /**
424
+ * Creates a column node for use in expressions
425
+ * @param table - Table name
426
+ * @param name - Column name
427
+ * @returns ColumnNode with the specified table and name
428
+ */
429
+ export const createColumn = (table: string, name: string): ColumnNode => ({ type: 'Column', table, name });
430
+
431
+ /**
432
+ * Creates a literal value node for use in expressions
433
+ * @param val - Literal value (string or number)
434
+ * @returns LiteralNode with the specified value
435
+ */
436
+ export const createLiteral = (val: string | number): LiteralNode => ({ type: 'Literal', value: val });