metal-orm 1.0.5 → 1.0.7

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