metal-orm 1.0.59 → 1.0.60

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.
@@ -2,20 +2,20 @@ import { TableDef } from '../schema/table.js';
2
2
  import { ColumnDef } from '../schema/column-types.js';
3
3
  import { OrderingTerm, SelectQueryNode, SetOperationKind } from '../core/ast/query.js';
4
4
  import { HydrationPlan } from '../core/hydration/types.js';
5
- import {
6
- ColumnNode,
7
- ExpressionNode,
8
- FunctionNode,
9
- BinaryExpressionNode,
10
- CaseExpressionNode,
11
- WindowFunctionNode,
12
- and,
13
- exists,
14
- notExists,
15
- OperandNode
16
- } from '../core/ast/expression.js';
17
- import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
18
- import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
5
+ import {
6
+ ColumnNode,
7
+ ExpressionNode,
8
+ FunctionNode,
9
+ BinaryExpressionNode,
10
+ CaseExpressionNode,
11
+ WindowFunctionNode,
12
+ and,
13
+ exists,
14
+ notExists,
15
+ OperandNode
16
+ } from '../core/ast/expression.js';
17
+ import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
18
+ import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
19
19
 
20
20
  type SelectDialectInput = Dialect | DialectKey;
21
21
 
@@ -27,65 +27,63 @@ import {
27
27
  SelectQueryBuilderDependencies,
28
28
  SelectQueryBuilderEnvironment
29
29
  } from './select-query-builder-deps.js';
30
- import { ColumnSelector } from './column-selector.js';
31
- import { RelationIncludeOptions, RelationTargetColumns, TypedRelationIncludeOptions } from './relation-types.js';
32
- import { RelationKinds } from '../schema/relation.js';
30
+ import { ColumnSelector } from './column-selector.js';
31
+ import { RelationIncludeOptions, RelationTargetColumns, TypedRelationIncludeOptions } from './relation-types.js';
32
+ import { RelationKinds } from '../schema/relation.js';
33
33
  import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
34
- import { EntityInstance, RelationMap } from '../schema/types.js';
35
- import { OrmSession } from '../orm/orm-session.ts';
36
- import { ExecutionContext } from '../orm/execution-context.js';
37
- import { HydrationContext } from '../orm/hydration-context.js';
38
- import { executeHydrated, executeHydratedWithContexts } from '../orm/execute.js';
39
- import { resolveSelectQuery } from './query-resolution.js';
40
- import {
41
- applyOrderBy,
42
- buildWhereHasPredicate,
43
- executeCount,
44
- executePagedQuery,
45
- RelationCallback,
46
- WhereHasOptions
47
- } from './select/select-operations.js';
48
- import { SelectFromFacet } from './select/from-facet.js';
49
- import { SelectJoinFacet } from './select/join-facet.js';
50
- import { SelectProjectionFacet } from './select/projection-facet.js';
51
- import { SelectPredicateFacet } from './select/predicate-facet.js';
52
- import { SelectCTEFacet } from './select/cte-facet.js';
53
- import { SelectSetOpFacet } from './select/setop-facet.js';
54
- import { SelectRelationFacet } from './select/relation-facet.js';
55
-
34
+ import { EntityInstance, RelationMap } from '../schema/types.js';
35
+ import { OrmSession } from '../orm/orm-session.ts';
36
+ import { ExecutionContext } from '../orm/execution-context.js';
37
+ import { HydrationContext } from '../orm/hydration-context.js';
38
+ import { executeHydrated, executeHydratedWithContexts } from '../orm/execute.js';
39
+ import { resolveSelectQuery } from './query-resolution.js';
40
+ import {
41
+ applyOrderBy,
42
+ buildWhereHasPredicate,
43
+ executeCount,
44
+ executePagedQuery,
45
+ RelationCallback,
46
+ WhereHasOptions
47
+ } from './select/select-operations.js';
48
+ import { SelectFromFacet } from './select/from-facet.js';
49
+ import { SelectJoinFacet } from './select/join-facet.js';
50
+ import { SelectProjectionFacet } from './select/projection-facet.js';
51
+ import { SelectPredicateFacet } from './select/predicate-facet.js';
52
+ import { SelectCTEFacet } from './select/cte-facet.js';
53
+ import { SelectSetOpFacet } from './select/setop-facet.js';
54
+ import { SelectRelationFacet } from './select/relation-facet.js';
56
55
 
57
56
  type ColumnSelectionValue = ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode;
58
57
 
59
- type DeepSelectEntry<TTable extends TableDef> = {
60
- type: 'root';
61
- columns: (keyof TTable['columns'] & string)[];
62
- } | {
63
- type: 'relation';
64
- relationName: keyof TTable['relations'] & string;
65
- columns: string[];
66
- };
58
+ type DeepSelectEntry<TTable extends TableDef> = {
59
+ type: 'root';
60
+ columns: (keyof TTable['columns'] & string)[];
61
+ } | {
62
+ type: 'relation';
63
+ relationName: keyof TTable['relations'] & string;
64
+ columns: string[];
65
+ };
67
66
 
68
67
  type DeepSelectConfig<TTable extends TableDef> = DeepSelectEntry<TTable>[];
69
68
 
70
-
71
69
  /**
72
70
  * Main query builder class for constructing SQL SELECT queries
73
71
  * @typeParam T - Result type for projections (unused)
74
72
  * @typeParam TTable - Table definition being queried
75
73
  */
76
74
  export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef> {
77
- private readonly env: SelectQueryBuilderEnvironment;
78
- private readonly context: SelectQueryBuilderContext;
79
- private readonly columnSelector: ColumnSelector;
80
- private readonly fromFacet: SelectFromFacet;
81
- private readonly joinFacet: SelectJoinFacet;
82
- private readonly projectionFacet: SelectProjectionFacet;
83
- private readonly predicateFacet: SelectPredicateFacet;
84
- private readonly cteFacet: SelectCTEFacet;
85
- private readonly setOpFacet: SelectSetOpFacet;
86
- private readonly relationFacet: SelectRelationFacet;
87
- private readonly lazyRelations: Set<string>;
88
- private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
75
+ private readonly env: SelectQueryBuilderEnvironment;
76
+ private readonly context: SelectQueryBuilderContext;
77
+ private readonly columnSelector: ColumnSelector;
78
+ private readonly fromFacet: SelectFromFacet;
79
+ private readonly joinFacet: SelectJoinFacet;
80
+ private readonly projectionFacet: SelectProjectionFacet;
81
+ private readonly predicateFacet: SelectPredicateFacet;
82
+ private readonly cteFacet: SelectCTEFacet;
83
+ private readonly setOpFacet: SelectSetOpFacet;
84
+ private readonly relationFacet: SelectRelationFacet;
85
+ private readonly lazyRelations: Set<string>;
86
+ private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
89
87
 
90
88
  /**
91
89
  * Creates a new SelectQueryBuilder instance
@@ -101,28 +99,28 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
101
99
  dependencies?: Partial<SelectQueryBuilderDependencies>,
102
100
  lazyRelations?: Set<string>,
103
101
  lazyRelationOptions?: Map<string, RelationIncludeOptions>
104
- ) {
105
- const deps = resolveSelectQueryBuilderDependencies(dependencies);
106
- this.env = { table, deps };
107
- const createAstService = (nextState: SelectQueryState) => deps.createQueryAstService(table, nextState);
108
- const initialState = state ?? deps.createState(table);
109
- const initialHydration = hydration ?? deps.createHydration(table);
110
- this.context = {
111
- state: initialState,
112
- hydration: initialHydration
113
- };
114
- this.lazyRelations = new Set(lazyRelations ?? []);
115
- this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
116
- this.columnSelector = deps.createColumnSelector(this.env);
117
- const relationManager = deps.createRelationManager(this.env);
118
- this.fromFacet = new SelectFromFacet(this.env, createAstService);
119
- this.joinFacet = new SelectJoinFacet(this.env, createAstService);
120
- this.projectionFacet = new SelectProjectionFacet(this.columnSelector);
121
- this.predicateFacet = new SelectPredicateFacet(this.env, createAstService);
122
- this.cteFacet = new SelectCTEFacet(this.env, createAstService);
123
- this.setOpFacet = new SelectSetOpFacet(this.env, createAstService);
124
- this.relationFacet = new SelectRelationFacet(relationManager);
125
- }
102
+ ) {
103
+ const deps = resolveSelectQueryBuilderDependencies(dependencies);
104
+ this.env = { table, deps };
105
+ const createAstService = (nextState: SelectQueryState) => deps.createQueryAstService(table, nextState);
106
+ const initialState = state ?? deps.createState(table);
107
+ const initialHydration = hydration ?? deps.createHydration(table);
108
+ this.context = {
109
+ state: initialState,
110
+ hydration: initialHydration
111
+ };
112
+ this.lazyRelations = new Set(lazyRelations ?? []);
113
+ this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
114
+ this.columnSelector = deps.createColumnSelector(this.env);
115
+ const relationManager = deps.createRelationManager(this.env);
116
+ this.fromFacet = new SelectFromFacet(this.env, createAstService);
117
+ this.joinFacet = new SelectJoinFacet(this.env, createAstService);
118
+ this.projectionFacet = new SelectProjectionFacet(this.columnSelector);
119
+ this.predicateFacet = new SelectPredicateFacet(this.env, createAstService);
120
+ this.cteFacet = new SelectCTEFacet(this.env, createAstService);
121
+ this.setOpFacet = new SelectSetOpFacet(this.env, createAstService);
122
+ this.relationFacet = new SelectRelationFacet(relationManager);
123
+ }
126
124
 
127
125
  /**
128
126
  * Creates a new SelectQueryBuilder instance with updated context and lazy relations
@@ -148,13 +146,13 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
148
146
  /**
149
147
  * Applies an alias to the root FROM table.
150
148
  * @param alias - Alias to apply
149
+ * @example
150
+ * const qb = new SelectQueryBuilder(userTable).as('u');
151
151
  */
152
- as(alias: string): SelectQueryBuilder<T, TTable> {
153
- const nextContext = this.fromFacet.as(this.context, alias);
154
- return this.clone(nextContext);
155
- }
156
-
157
-
152
+ as(alias: string): SelectQueryBuilder<T, TTable> {
153
+ const nextContext = this.fromFacet.as(this.context, alias);
154
+ return this.clone(nextContext);
155
+ }
158
156
 
159
157
  /**
160
158
  * Applies correlation expression to the query AST
@@ -180,26 +178,34 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
180
178
  return new SelectQueryBuilder(table, undefined, undefined, this.env.deps);
181
179
  }
182
180
 
183
- /**
184
- * Applies a set operation to the query
185
- * @param operator - Set operation kind
186
- * @param query - Query to combine with
187
- * @returns Updated query context with set operation
188
- */
189
- private applySetOperation<TSub extends TableDef>(
190
- operator: SetOperationKind,
191
- query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
192
- ): SelectQueryBuilderContext {
193
- const subAst = resolveSelectQuery(query);
194
- return this.setOpFacet.applySetOperation(this.context, operator, subAst);
195
- }
196
-
181
+ /**
182
+ * Applies a set operation to the query
183
+ * @param operator - Set operation kind
184
+ * @param query - Query to combine with
185
+ * @returns Updated query context with set operation
186
+ */
187
+ private applySetOperation<TSub extends TableDef>(
188
+ operator: SetOperationKind,
189
+ query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
190
+ ): SelectQueryBuilderContext {
191
+ const subAst = resolveSelectQuery(query);
192
+ return this.setOpFacet.applySetOperation(this.context, operator, subAst);
193
+ }
197
194
 
198
195
  /**
199
196
  * Selects columns for the query (unified overloaded method).
200
197
  * Can be called with column names or a projection object.
201
198
  * @param args - Column names or projection object
202
199
  * @returns New query builder instance with selected columns
200
+ * @example
201
+ * // Select specific columns
202
+ * qb.select('id', 'name', 'email');
203
+ * @example
204
+ * // Select with aliases and expressions
205
+ * qb.select({
206
+ * id: userTable.columns.id,
207
+ * fullName: concat(userTable.columns.firstName, ' ', userTable.columns.lastName)
208
+ * });
203
209
  */
204
210
  select<K extends keyof TTable['columns'] & string>(
205
211
  ...args: K[]
@@ -209,10 +215,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
209
215
  ...args: K[] | [Record<string, ColumnSelectionValue>]
210
216
  ): SelectQueryBuilder<T, TTable> {
211
217
  // If first arg is an object (not a string), treat as projection map
212
- if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && typeof args[0] !== 'string') {
213
- const columns = args[0] as Record<string, ColumnSelectionValue>;
214
- return this.clone(this.projectionFacet.select(this.context, columns));
215
- }
218
+ if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && typeof args[0] !== 'string') {
219
+ const columns = args[0] as Record<string, ColumnSelectionValue>;
220
+ return this.clone(this.projectionFacet.select(this.context, columns));
221
+ }
216
222
 
217
223
  // Otherwise, treat as column names
218
224
  const cols = args as K[];
@@ -225,17 +231,19 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
225
231
  selection[key] = col;
226
232
  }
227
233
 
228
- return this.clone(this.projectionFacet.select(this.context, selection));
229
- }
234
+ return this.clone(this.projectionFacet.select(this.context, selection));
235
+ }
230
236
 
231
237
  /**
232
238
  * Selects raw column expressions
233
239
  * @param cols - Column expressions as strings
234
240
  * @returns New query builder instance with raw column selections
241
+ * @example
242
+ * qb.selectRaw('COUNT(*) as total', 'UPPER(name) as upper_name');
235
243
  */
236
- selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
237
- return this.clone(this.projectionFacet.selectRaw(this.context, cols));
238
- }
244
+ selectRaw(...cols: string[]): SelectQueryBuilder<T, TTable> {
245
+ return this.clone(this.projectionFacet.selectRaw(this.context, cols));
246
+ }
239
247
 
240
248
  /**
241
249
  * Adds a Common Table Expression (CTE) to the query
@@ -243,12 +251,18 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
243
251
  * @param query - Query builder or query node for the CTE
244
252
  * @param columns - Optional column names for the CTE
245
253
  * @returns New query builder instance with the CTE
246
- */
247
- with<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
248
- const subAst = resolveSelectQuery(query);
249
- const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, false);
250
- return this.clone(nextContext);
251
- }
254
+ * @example
255
+ * const recentUsers = new SelectQueryBuilder(userTable)
256
+ * .where(gt(userTable.columns.createdAt, subDays(now(), 30)));
257
+ * const qb = new SelectQueryBuilder(userTable)
258
+ * .with('recent_users', recentUsers)
259
+ * .from('recent_users');
260
+ */
261
+ with<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
262
+ const subAst = resolveSelectQuery(query);
263
+ const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, false);
264
+ return this.clone(nextContext);
265
+ }
252
266
 
253
267
  /**
254
268
  * Adds a recursive Common Table Expression (CTE) to the query
@@ -256,12 +270,25 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
256
270
  * @param query - Query builder or query node for the CTE
257
271
  * @param columns - Optional column names for the CTE
258
272
  * @returns New query builder instance with the recursive CTE
259
- */
260
- withRecursive<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
261
- const subAst = resolveSelectQuery(query);
262
- const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, true);
263
- return this.clone(nextContext);
264
- }
273
+ * @example
274
+ * // Base case: select root nodes
275
+ * const baseQuery = new SelectQueryBuilder(orgTable)
276
+ * .where(eq(orgTable.columns.parentId, 1));
277
+ * // Recursive case: join with the CTE itself
278
+ * const recursiveQuery = new SelectQueryBuilder(orgTable)
279
+ * .join('org_hierarchy', 'oh', eq(orgTable.columns.parentId, col('oh.id')));
280
+ * // Combine base and recursive parts
281
+ * const orgHierarchy = baseQuery.union(recursiveQuery);
282
+ * // Use in main query
283
+ * const qb = new SelectQueryBuilder(orgTable)
284
+ * .withRecursive('org_hierarchy', orgHierarchy)
285
+ * .from('org_hierarchy');
286
+ */
287
+ withRecursive<TSub extends TableDef>(name: string, query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode, columns?: string[]): SelectQueryBuilder<T, TTable> {
288
+ const subAst = resolveSelectQuery(query);
289
+ const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, true);
290
+ return this.clone(nextContext);
291
+ }
265
292
 
266
293
  /**
267
294
  * Replaces the FROM clause with a derived table (subquery with alias)
@@ -269,16 +296,21 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
269
296
  * @param alias - Alias for the derived table
270
297
  * @param columnAliases - Optional column alias list
271
298
  * @returns New query builder instance with updated FROM
299
+ * @example
300
+ * const subquery = new SelectQueryBuilder(userTable)
301
+ * .select('id', 'name')
302
+ * .where(gt(userTable.columns.score, 100));
303
+ * qb.fromSubquery(subquery, 'high_scorers', ['userId', 'userName']);
272
304
  */
273
- fromSubquery<TSub extends TableDef>(
274
- subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
275
- alias: string,
276
- columnAliases?: string[]
277
- ): SelectQueryBuilder<T, TTable> {
278
- const subAst = resolveSelectQuery(subquery);
279
- const nextContext = this.fromFacet.fromSubquery(this.context, subAst, alias, columnAliases);
280
- return this.clone(nextContext);
281
- }
305
+ fromSubquery<TSub extends TableDef>(
306
+ subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
307
+ alias: string,
308
+ columnAliases?: string[]
309
+ ): SelectQueryBuilder<T, TTable> {
310
+ const subAst = resolveSelectQuery(subquery);
311
+ const nextContext = this.fromFacet.fromSubquery(this.context, subAst, alias, columnAliases);
312
+ return this.clone(nextContext);
313
+ }
282
314
 
283
315
  /**
284
316
  * Replaces the FROM clause with a function table expression.
@@ -286,27 +318,40 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
286
318
  * @param args - Optional function arguments
287
319
  * @param alias - Optional alias for the function table
288
320
  * @param options - Optional function-table metadata (lateral, ordinality, column aliases, schema)
289
- */
290
- fromFunctionTable(
291
- name: string,
292
- args: OperandNode[] = [],
293
- alias?: string,
294
- options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
295
- ): SelectQueryBuilder<T, TTable> {
296
- const nextContext = this.fromFacet.fromFunctionTable(this.context, name, args, alias, options);
297
- return this.clone(nextContext);
298
- }
321
+ * @example
322
+ * qb.fromFunctionTable(
323
+ * 'generate_series',
324
+ * [literal(1), literal(10), literal(1)],
325
+ * 'series',
326
+ * { columnAliases: ['value'] }
327
+ * );
328
+ */
329
+ fromFunctionTable(
330
+ name: string,
331
+ args: OperandNode[] = [],
332
+ alias?: string,
333
+ options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
334
+ ): SelectQueryBuilder<T, TTable> {
335
+ const nextContext = this.fromFacet.fromFunctionTable(this.context, name, args, alias, options);
336
+ return this.clone(nextContext);
337
+ }
299
338
 
300
339
  /**
301
340
  * Selects a subquery as a column
302
341
  * @param alias - Alias for the subquery column
303
342
  * @param sub - Query builder or query node for the subquery
304
343
  * @returns New query builder instance with the subquery selection
305
- */
306
- selectSubquery<TSub extends TableDef>(alias: string, sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
307
- const query = resolveSelectQuery(sub);
308
- return this.clone(this.projectionFacet.selectSubquery(this.context, alias, query));
309
- }
344
+ * @example
345
+ * const postCount = new SelectQueryBuilder(postTable)
346
+ * .select(count(postTable.columns.id))
347
+ * .where(eq(postTable.columns.userId, col('u.id')));
348
+ * qb.select('id', 'name')
349
+ * .selectSubquery('postCount', postCount);
350
+ */
351
+ selectSubquery<TSub extends TableDef>(alias: string, sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
352
+ const query = resolveSelectQuery(sub);
353
+ return this.clone(this.projectionFacet.selectSubquery(this.context, alias, query));
354
+ }
310
355
 
311
356
  /**
312
357
  * Adds a JOIN against a derived table (subquery with alias)
@@ -316,18 +361,27 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
316
361
  * @param joinKind - Join kind (defaults to INNER)
317
362
  * @param columnAliases - Optional column alias list for the derived table
318
363
  * @returns New query builder instance with the derived-table join
319
- */
320
- joinSubquery<TSub extends TableDef>(
321
- subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
322
- alias: string,
323
- condition: BinaryExpressionNode,
324
- joinKind: JoinKind = JOIN_KINDS.INNER,
325
- columnAliases?: string[]
326
- ): SelectQueryBuilder<T, TTable> {
327
- const subAst = resolveSelectQuery(subquery);
328
- const nextContext = this.joinFacet.joinSubquery(this.context, subAst, alias, condition, joinKind, columnAliases);
329
- return this.clone(nextContext);
330
- }
364
+ * @example
365
+ * const activeUsers = new SelectQueryBuilder(userTable)
366
+ * .where(eq(userTable.columns.active, true));
367
+ * qb.joinSubquery(
368
+ * activeUsers,
369
+ * 'au',
370
+ * eq(col('t.userId'), col('au.id')),
371
+ * JOIN_KINDS.LEFT
372
+ * );
373
+ */
374
+ joinSubquery<TSub extends TableDef>(
375
+ subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
376
+ alias: string,
377
+ condition: BinaryExpressionNode,
378
+ joinKind: JoinKind = JOIN_KINDS.INNER,
379
+ columnAliases?: string[]
380
+ ): SelectQueryBuilder<T, TTable> {
381
+ const subAst = resolveSelectQuery(subquery);
382
+ const nextContext = this.joinFacet.joinSubquery(this.context, subAst, alias, condition, joinKind, columnAliases);
383
+ return this.clone(nextContext);
384
+ }
331
385
 
332
386
  /**
333
387
  * Adds a join against a function table (e.g., `generate_series`) using `fnTable` internally.
@@ -337,65 +391,91 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
337
391
  * @param condition - Join condition expression
338
392
  * @param joinKind - Kind of join (defaults to INNER)
339
393
  * @param options - Optional metadata (lateral, ordinality, column aliases, schema)
340
- */
341
- joinFunctionTable(
342
- name: string,
343
- args: OperandNode[] = [],
344
- alias: string,
345
- condition: BinaryExpressionNode,
346
- joinKind: JoinKind = JOIN_KINDS.INNER,
347
- options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
348
- ): SelectQueryBuilder<T, TTable> {
349
- const nextContext = this.joinFacet.joinFunctionTable(this.context, name, args, alias, condition, joinKind, options);
350
- return this.clone(nextContext);
351
- }
394
+ * @example
395
+ * qb.joinFunctionTable(
396
+ * 'generate_series',
397
+ * [literal(1), literal(10)],
398
+ * 'gs',
399
+ * eq(col('t.value'), col('gs.value')),
400
+ * JOIN_KINDS.INNER,
401
+ * { columnAliases: ['value'] }
402
+ * );
403
+ */
404
+ joinFunctionTable(
405
+ name: string,
406
+ args: OperandNode[] = [],
407
+ alias: string,
408
+ condition: BinaryExpressionNode,
409
+ joinKind: JoinKind = JOIN_KINDS.INNER,
410
+ options?: { lateral?: boolean; withOrdinality?: boolean; columnAliases?: string[]; schema?: string }
411
+ ): SelectQueryBuilder<T, TTable> {
412
+ const nextContext = this.joinFacet.joinFunctionTable(this.context, name, args, alias, condition, joinKind, options);
413
+ return this.clone(nextContext);
414
+ }
352
415
 
353
416
  /**
354
417
  * Adds an INNER JOIN to the query
355
418
  * @param table - Table to join
356
419
  * @param condition - Join condition expression
357
420
  * @returns New query builder instance with the INNER JOIN
358
- */
359
- innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
360
- const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
361
- return this.clone(nextContext);
362
- }
421
+ * @example
422
+ * qb.innerJoin(
423
+ * postTable,
424
+ * eq(userTable.columns.id, postTable.columns.userId)
425
+ * );
426
+ */
427
+ innerJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
428
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
429
+ return this.clone(nextContext);
430
+ }
363
431
 
364
432
  /**
365
433
  * Adds a LEFT JOIN to the query
366
434
  * @param table - Table to join
367
435
  * @param condition - Join condition expression
368
436
  * @returns New query builder instance with the LEFT JOIN
369
- */
370
- leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
371
- const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
372
- return this.clone(nextContext);
373
- }
437
+ * @example
438
+ * qb.leftJoin(
439
+ * postTable,
440
+ * eq(userTable.columns.id, postTable.columns.userId)
441
+ * );
442
+ */
443
+ leftJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
444
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
445
+ return this.clone(nextContext);
446
+ }
374
447
 
375
448
  /**
376
449
  * Adds a RIGHT JOIN to the query
377
450
  * @param table - Table to join
378
451
  * @param condition - Join condition expression
379
452
  * @returns New query builder instance with the RIGHT JOIN
380
- */
381
- rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
382
- const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
383
- return this.clone(nextContext);
384
- }
453
+ * @example
454
+ * qb.rightJoin(
455
+ * postTable,
456
+ * eq(userTable.columns.id, postTable.columns.userId)
457
+ * );
458
+ */
459
+ rightJoin(table: TableDef, condition: BinaryExpressionNode): SelectQueryBuilder<T, TTable> {
460
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
461
+ return this.clone(nextContext);
462
+ }
385
463
 
386
464
  /**
387
465
  * Matches records based on a relationship
388
466
  * @param relationName - Name of the relationship to match
389
467
  * @param predicate - Optional predicate expression
390
468
  * @returns New query builder instance with the relationship match
469
+ * @example
470
+ * qb.match('posts', eq(postTable.columns.published, true));
391
471
  */
392
- match<K extends keyof TTable['relations'] & string>(
393
- relationName: K,
394
- predicate?: ExpressionNode
395
- ): SelectQueryBuilder<T, TTable> {
396
- const nextContext = this.relationFacet.match(this.context, relationName, predicate);
397
- return this.clone(nextContext);
398
- }
472
+ match<K extends keyof TTable['relations'] & string>(
473
+ relationName: K,
474
+ predicate?: ExpressionNode
475
+ ): SelectQueryBuilder<T, TTable> {
476
+ const nextContext = this.relationFacet.match(this.context, relationName, predicate);
477
+ return this.clone(nextContext);
478
+ }
399
479
 
400
480
  /**
401
481
  * Joins a related table
@@ -403,40 +483,58 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
403
483
  * @param joinKind - Type of join (defaults to INNER)
404
484
  * @param extraCondition - Optional additional join condition
405
485
  * @returns New query builder instance with the relationship join
406
- */
407
- joinRelation<K extends keyof TTable['relations'] & string>(
408
- relationName: K,
409
- joinKind: JoinKind = JOIN_KINDS.INNER,
410
- extraCondition?: ExpressionNode
411
- ): SelectQueryBuilder<T, TTable> {
412
- const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
413
- return this.clone(nextContext);
414
- }
486
+ * @example
487
+ * qb.joinRelation('posts', JOIN_KINDS.LEFT);
488
+ * @example
489
+ * qb.joinRelation('posts', JOIN_KINDS.INNER, eq(postTable.columns.published, true));
490
+ */
491
+ joinRelation<K extends keyof TTable['relations'] & string>(
492
+ relationName: K,
493
+ joinKind: JoinKind = JOIN_KINDS.INNER,
494
+ extraCondition?: ExpressionNode
495
+ ): SelectQueryBuilder<T, TTable> {
496
+ const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
497
+ return this.clone(nextContext);
498
+ }
415
499
 
416
500
  /**
417
501
  * Includes related data in the query results
418
502
  * @param relationName - Name of the relationship to include
419
503
  * @param options - Optional include options
420
504
  * @returns New query builder instance with the relationship inclusion
421
- */
422
- include<K extends keyof TTable['relations'] & string>(
423
- relationName: K,
424
- options?: TypedRelationIncludeOptions<TTable['relations'][K]>
425
- ): SelectQueryBuilder<T, TTable> {
426
- const nextContext = this.relationFacet.include(this.context, relationName, options);
427
- return this.clone(nextContext);
428
- }
505
+ * @example
506
+ * qb.include('posts');
507
+ * @example
508
+ * qb.include('posts', { columns: ['id', 'title', 'published'] });
509
+ * @example
510
+ * qb.include('posts', {
511
+ * columns: ['id', 'title'],
512
+ * where: eq(postTable.columns.published, true)
513
+ * });
514
+ */
515
+ include<K extends keyof TTable['relations'] & string>(
516
+ relationName: K,
517
+ options?: TypedRelationIncludeOptions<TTable['relations'][K]>
518
+ ): SelectQueryBuilder<T, TTable> {
519
+ const nextContext = this.relationFacet.include(this.context, relationName, options);
520
+ return this.clone(nextContext);
521
+ }
429
522
 
430
523
  /**
431
524
  * Includes a relation lazily in the query results
432
525
  * @param relationName - Name of the relation to include lazily
433
526
  * @param options - Optional include options for lazy loading
434
527
  * @returns New query builder instance with lazy relation inclusion
435
- */
436
- includeLazy<K extends keyof RelationMap<TTable>>(
437
- relationName: K,
438
- options?: TypedRelationIncludeOptions<TTable['relations'][K]>
439
- ): SelectQueryBuilder<T, TTable> {
528
+ * @example
529
+ * const qb = new SelectQueryBuilder(userTable).includeLazy('posts');
530
+ * const users = await qb.execute(session);
531
+ * // Access posts later - they will be loaded on demand
532
+ * const posts = await users[0].posts;
533
+ */
534
+ includeLazy<K extends keyof RelationMap<TTable>>(
535
+ relationName: K,
536
+ options?: TypedRelationIncludeOptions<TTable['relations'][K]>
537
+ ): SelectQueryBuilder<T, TTable> {
440
538
  let nextContext = this.context;
441
539
  const relation = this.env.table.relations[relationName as string];
442
540
  if (relation?.type === RelationKinds.BelongsTo) {
@@ -465,33 +563,39 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
465
563
 
466
564
  /**
467
565
  * Convenience alias for including only specific columns from a relation.
468
- */
469
- includePick<
470
- K extends keyof TTable['relations'] & string,
471
- C extends RelationTargetColumns<TTable['relations'][K]>
472
- >(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
473
- const options = { columns: cols as readonly C[] } as unknown as TypedRelationIncludeOptions<TTable['relations'][K]>;
474
- return this.include(relationName, options);
475
- }
476
-
566
+ * @example
567
+ * qb.includePick('posts', ['id', 'title', 'createdAt']);
568
+ */
569
+ includePick<
570
+ K extends keyof TTable['relations'] & string,
571
+ C extends RelationTargetColumns<TTable['relations'][K]>
572
+ >(relationName: K, cols: C[]): SelectQueryBuilder<T, TTable> {
573
+ const options = { columns: cols as readonly C[] } as unknown as TypedRelationIncludeOptions<TTable['relations'][K]>;
574
+ return this.include(relationName, options);
575
+ }
477
576
 
478
577
  /**
479
578
  * Selects columns for the root table and relations from an array of entries
480
579
  * @param config - Configuration array for deep column selection
481
580
  * @returns New query builder instance with deep column selections
581
+ * @example
582
+ * qb.selectColumnsDeep([
583
+ * { type: 'root', columns: ['id', 'name'] },
584
+ * { type: 'relation', relationName: 'posts', columns: ['id', 'title'] }
585
+ * ]);
482
586
  */
483
587
  selectColumnsDeep(config: DeepSelectConfig<TTable>): SelectQueryBuilder<T, TTable> {
484
588
  // eslint-disable-next-line @typescript-eslint/no-this-alias
485
589
  let currBuilder: SelectQueryBuilder<T, TTable> = this;
486
590
 
487
- for (const entry of config) {
488
- if (entry.type === 'root') {
489
- currBuilder = currBuilder.select(...entry.columns);
490
- } else {
491
- const options = { columns: entry.columns } as unknown as TypedRelationIncludeOptions<TTable['relations'][typeof entry.relationName]>;
492
- currBuilder = currBuilder.include(entry.relationName, options);
493
- }
494
- }
591
+ for (const entry of config) {
592
+ if (entry.type === 'root') {
593
+ currBuilder = currBuilder.select(...entry.columns);
594
+ } else {
595
+ const options = { columns: entry.columns } as unknown as TypedRelationIncludeOptions<TTable['relations'][typeof entry.relationName]>;
596
+ currBuilder = currBuilder.include(entry.relationName, options);
597
+ }
598
+ }
495
599
 
496
600
  return currBuilder;
497
601
  }
@@ -524,39 +628,47 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
524
628
  * Executes the query and returns hydrated results
525
629
  * @param ctx - ORM session context
526
630
  * @returns Promise of entity instances
631
+ * @example
632
+ * const users = await qb.select('id', 'name')
633
+ * .where(eq(userTable.columns.active, true))
634
+ * .execute(session);
527
635
  */
528
636
  async execute(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
529
637
  return executeHydrated(ctx, this);
530
638
  }
531
639
 
532
- /**
533
- * Executes a count query for the current builder without LIMIT/OFFSET clauses.
534
- *
535
- * @example
536
- * const total = await qb.count(session);
537
- */
538
- async count(session: OrmSession): Promise<number> {
539
- return executeCount(this.context, this.env, session);
540
- }
541
-
542
- /**
543
- * Executes the query and returns both the paged items and the total.
544
- *
545
- * @example
546
- * const { items, totalItems } = await qb.executePaged(session, { page: 1, pageSize: 20 });
547
- */
548
- async executePaged(
549
- session: OrmSession,
550
- options: { page: number; pageSize: number }
551
- ): Promise<{ items: EntityInstance<TTable>[]; totalItems: number }> {
552
- return executePagedQuery(this, session, options, sess => this.count(sess));
553
- }
640
+ /**
641
+ * Executes a count query for the current builder without LIMIT/OFFSET clauses.
642
+ *
643
+ * @example
644
+ * const total = await qb.count(session);
645
+ */
646
+ async count(session: OrmSession): Promise<number> {
647
+ return executeCount(this.context, this.env, session);
648
+ }
649
+
650
+ /**
651
+ * Executes the query and returns both the paged items and the total.
652
+ *
653
+ * @example
654
+ * const { items, totalItems } = await qb.executePaged(session, { page: 1, pageSize: 20 });
655
+ */
656
+ async executePaged(
657
+ session: OrmSession,
658
+ options: { page: number; pageSize: number }
659
+ ): Promise<{ items: EntityInstance<TTable>[]; totalItems: number }> {
660
+ return executePagedQuery(this, session, options, sess => this.count(sess));
661
+ }
554
662
 
555
663
  /**
556
664
  * Executes the query with provided execution and hydration contexts
557
665
  * @param execCtx - Execution context
558
666
  * @param hydCtx - Hydration context
559
667
  * @returns Promise of entity instances
668
+ * @example
669
+ * const execCtx = new ExecutionContext(session);
670
+ * const hydCtx = new HydrationContext();
671
+ * const users = await qb.executeWithContexts(execCtx, hydCtx);
560
672
  */
561
673
  async executeWithContexts(execCtx: ExecutionContext, hydCtx: HydrationContext): Promise<EntityInstance<TTable>[]> {
562
674
  return executeHydratedWithContexts(execCtx, hydCtx, this);
@@ -566,85 +678,115 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
566
678
  * Adds a WHERE condition to the query
567
679
  * @param expr - Expression for the WHERE clause
568
680
  * @returns New query builder instance with the WHERE condition
569
- */
570
- where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
571
- const nextContext = this.predicateFacet.where(this.context, expr);
572
- return this.clone(nextContext);
573
- }
681
+ * @example
682
+ * qb.where(eq(userTable.columns.id, 1));
683
+ * @example
684
+ * qb.where(and(
685
+ * eq(userTable.columns.active, true),
686
+ * gt(userTable.columns.createdAt, subDays(now(), 30))
687
+ * ));
688
+ */
689
+ where(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
690
+ const nextContext = this.predicateFacet.where(this.context, expr);
691
+ return this.clone(nextContext);
692
+ }
574
693
 
575
694
  /**
576
695
  * Adds a GROUP BY clause to the query
577
696
  * @param term - Column definition or ordering term to group by
578
697
  * @returns New query builder instance with the GROUP BY clause
698
+ * @example
699
+ * qb.select('departmentId', count(userTable.columns.id))
700
+ * .groupBy(userTable.columns.departmentId);
579
701
  */
580
- groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
581
- const nextContext = this.predicateFacet.groupBy(this.context, term);
582
- return this.clone(nextContext);
583
- }
702
+ groupBy(term: ColumnDef | OrderingTerm): SelectQueryBuilder<T, TTable> {
703
+ const nextContext = this.predicateFacet.groupBy(this.context, term);
704
+ return this.clone(nextContext);
705
+ }
584
706
 
585
707
  /**
586
708
  * Adds a HAVING condition to the query
587
709
  * @param expr - Expression for the HAVING clause
588
710
  * @returns New query builder instance with the HAVING condition
589
- */
590
- having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
591
- const nextContext = this.predicateFacet.having(this.context, expr);
592
- return this.clone(nextContext);
593
- }
594
-
711
+ * @example
712
+ * qb.select('departmentId', count(userTable.columns.id))
713
+ * .groupBy(userTable.columns.departmentId)
714
+ * .having(gt(count(userTable.columns.id), 5));
715
+ */
716
+ having(expr: ExpressionNode): SelectQueryBuilder<T, TTable> {
717
+ const nextContext = this.predicateFacet.having(this.context, expr);
718
+ return this.clone(nextContext);
719
+ }
595
720
 
721
+ /**
722
+ * Adds an ORDER BY clause to the query
723
+ * @param term - Column definition or ordering term to order by
724
+ * @param directionOrOptions - Order direction or options (defaults to ASC)
725
+ * @returns New query builder instance with the ORDER BY clause
726
+ *
727
+ * @example
728
+ * qb.orderBy(userTable.columns.createdAt, 'DESC');
729
+ */
730
+ orderBy(
731
+ term: ColumnDef | OrderingTerm,
732
+ directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string } = ORDER_DIRECTIONS.ASC
733
+ ): SelectQueryBuilder<T, TTable> {
734
+ const nextContext = applyOrderBy(this.context, this.predicateFacet, term, directionOrOptions);
596
735
 
597
- /**
598
- * Adds an ORDER BY clause to the query
599
- * @param term - Column definition or ordering term to order by
600
- * @param directionOrOptions - Order direction or options (defaults to ASC)
601
- * @returns New query builder instance with the ORDER BY clause
602
- *
603
- * @example
604
- * qb.orderBy(userTable.columns.createdAt, 'DESC');
605
- */
606
- orderBy(
607
- term: ColumnDef | OrderingTerm,
608
- directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string } = ORDER_DIRECTIONS.ASC
609
- ): SelectQueryBuilder<T, TTable> {
610
- const nextContext = applyOrderBy(this.context, this.predicateFacet, term, directionOrOptions);
611
-
612
- return this.clone(nextContext);
613
- }
736
+ return this.clone(nextContext);
737
+ }
614
738
 
615
739
  /**
616
740
  * Adds a DISTINCT clause to the query
617
741
  * @param cols - Columns to make distinct
618
742
  * @returns New query builder instance with the DISTINCT clause
743
+ * @example
744
+ * qb.distinct(userTable.columns.email);
745
+ * @example
746
+ * qb.distinct(userTable.columns.firstName, userTable.columns.lastName);
619
747
  */
620
- distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
621
- return this.clone(this.projectionFacet.distinct(this.context, cols));
622
- }
748
+ distinct(...cols: (ColumnDef | ColumnNode)[]): SelectQueryBuilder<T, TTable> {
749
+ return this.clone(this.projectionFacet.distinct(this.context, cols));
750
+ }
623
751
 
624
752
  /**
625
753
  * Adds a LIMIT clause to the query
626
754
  * @param n - Maximum number of rows to return
627
755
  * @returns New query builder instance with the LIMIT clause
628
- */
629
- limit(n: number): SelectQueryBuilder<T, TTable> {
630
- const nextContext = this.predicateFacet.limit(this.context, n);
631
- return this.clone(nextContext);
632
- }
756
+ * @example
757
+ * qb.limit(10);
758
+ * @example
759
+ * qb.limit(20).offset(40); // Pagination: page 3 with 20 items per page
760
+ */
761
+ limit(n: number): SelectQueryBuilder<T, TTable> {
762
+ const nextContext = this.predicateFacet.limit(this.context, n);
763
+ return this.clone(nextContext);
764
+ }
633
765
 
634
766
  /**
635
767
  * Adds an OFFSET clause to the query
636
768
  * @param n - Number of rows to skip
637
769
  * @returns New query builder instance with the OFFSET clause
638
- */
639
- offset(n: number): SelectQueryBuilder<T, TTable> {
640
- const nextContext = this.predicateFacet.offset(this.context, n);
641
- return this.clone(nextContext);
642
- }
770
+ * @example
771
+ * qb.offset(10);
772
+ * @example
773
+ * qb.limit(20).offset(40); // Pagination: page 3 with 20 items per page
774
+ */
775
+ offset(n: number): SelectQueryBuilder<T, TTable> {
776
+ const nextContext = this.predicateFacet.offset(this.context, n);
777
+ return this.clone(nextContext);
778
+ }
643
779
 
644
780
  /**
645
781
  * Combines this query with another using UNION
646
782
  * @param query - Query to union with
647
783
  * @returns New query builder instance with the set operation
784
+ * @example
785
+ * const activeUsers = new SelectQueryBuilder(userTable)
786
+ * .where(eq(userTable.columns.active, true));
787
+ * const inactiveUsers = new SelectQueryBuilder(userTable)
788
+ * .where(eq(userTable.columns.active, false));
789
+ * qb.union(activeUsers).union(inactiveUsers);
648
790
  */
649
791
  union<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
650
792
  return this.clone(this.applySetOperation('UNION', query));
@@ -654,6 +796,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
654
796
  * Combines this query with another using UNION ALL
655
797
  * @param query - Query to union with
656
798
  * @returns New query builder instance with the set operation
799
+ * @example
800
+ * const q1 = new SelectQueryBuilder(userTable).where(gt(userTable.columns.score, 80));
801
+ * const q2 = new SelectQueryBuilder(userTable).where(lt(userTable.columns.score, 20));
802
+ * qb.unionAll(q1).unionAll(q2);
657
803
  */
658
804
  unionAll<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
659
805
  return this.clone(this.applySetOperation('UNION ALL', query));
@@ -663,6 +809,12 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
663
809
  * Combines this query with another using INTERSECT
664
810
  * @param query - Query to intersect with
665
811
  * @returns New query builder instance with the set operation
812
+ * @example
813
+ * const activeUsers = new SelectQueryBuilder(userTable)
814
+ * .where(eq(userTable.columns.active, true));
815
+ * const premiumUsers = new SelectQueryBuilder(userTable)
816
+ * .where(eq(userTable.columns.premium, true));
817
+ * qb.intersect(activeUsers).intersect(premiumUsers);
666
818
  */
667
819
  intersect<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
668
820
  return this.clone(this.applySetOperation('INTERSECT', query));
@@ -672,6 +824,11 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
672
824
  * Combines this query with another using EXCEPT
673
825
  * @param query - Query to subtract
674
826
  * @returns New query builder instance with the set operation
827
+ * @example
828
+ * const allUsers = new SelectQueryBuilder(userTable);
829
+ * const inactiveUsers = new SelectQueryBuilder(userTable)
830
+ * .where(eq(userTable.columns.active, false));
831
+ * qb.except(allUsers).except(inactiveUsers); // Only active users
675
832
  */
676
833
  except<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
677
834
  return this.clone(this.applySetOperation('EXCEPT', query));
@@ -681,6 +838,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
681
838
  * Adds a WHERE EXISTS condition to the query
682
839
  * @param subquery - Subquery to check for existence
683
840
  * @returns New query builder instance with the WHERE EXISTS condition
841
+ * @example
842
+ * const postsQuery = new SelectQueryBuilder(postTable)
843
+ * .where(eq(postTable.columns.userId, col('u.id')));
844
+ * qb.whereExists(postsQuery);
684
845
  */
685
846
  whereExists<TSub extends TableDef>(
686
847
  subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
@@ -695,6 +856,10 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
695
856
  * Adds a WHERE NOT EXISTS condition to the query
696
857
  * @param subquery - Subquery to check for non-existence
697
858
  * @returns New query builder instance with the WHERE NOT EXISTS condition
859
+ * @example
860
+ * const postsQuery = new SelectQueryBuilder(postTable)
861
+ * .where(eq(postTable.columns.userId, col('u.id')));
862
+ * qb.whereNotExists(postsQuery); // Users without posts
698
863
  */
699
864
  whereNotExists<TSub extends TableDef>(
700
865
  subquery: SelectQueryBuilder<unknown, TSub> | SelectQueryNode,
@@ -705,68 +870,71 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
705
870
  return this.where(notExists(correlated));
706
871
  }
707
872
 
708
- /**
709
- * Adds a WHERE EXISTS condition based on a relationship
710
- * @param relationName - Name of the relationship to check
711
- * @param callback - Optional callback to modify the relationship query
712
- * @returns New query builder instance with the relationship existence check
713
- *
714
- * @example
715
- * qb.whereHas('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
716
- */
717
- whereHas<K extends keyof TTable['relations'] & string>(
718
- relationName: K,
719
- callbackOrOptions?: RelationCallback | WhereHasOptions,
720
- maybeOptions?: WhereHasOptions
721
- ): SelectQueryBuilder<T, TTable> {
722
- const predicate = buildWhereHasPredicate(
723
- this.env,
724
- this.context,
725
- this.relationFacet,
726
- table => this.createChildBuilder(table),
727
- relationName,
728
- callbackOrOptions,
729
- maybeOptions,
730
- false
731
- );
732
-
733
- return this.where(predicate);
734
- }
735
-
736
- /**
737
- * Adds a WHERE NOT EXISTS condition based on a relationship
738
- * @param relationName - Name of the relationship to check
739
- * @param callback - Optional callback to modify the relationship query
740
- * @returns New query builder instance with the relationship non-existence check
741
- *
742
- * @example
743
- * qb.whereHasNot('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
744
- */
745
- whereHasNot<K extends keyof TTable['relations'] & string>(
746
- relationName: K,
747
- callbackOrOptions?: RelationCallback | WhereHasOptions,
748
- maybeOptions?: WhereHasOptions
749
- ): SelectQueryBuilder<T, TTable> {
750
- const predicate = buildWhereHasPredicate(
751
- this.env,
752
- this.context,
753
- this.relationFacet,
754
- table => this.createChildBuilder(table),
755
- relationName,
756
- callbackOrOptions,
757
- maybeOptions,
758
- true
759
- );
760
-
761
- return this.where(predicate);
762
- }
873
+ /**
874
+ * Adds a WHERE EXISTS condition based on a relationship
875
+ * @param relationName - Name of the relationship to check
876
+ * @param callback - Optional callback to modify the relationship query
877
+ * @returns New query builder instance with the relationship existence check
878
+ *
879
+ * @example
880
+ * qb.whereHas('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
881
+ */
882
+ whereHas<K extends keyof TTable['relations'] & string>(
883
+ relationName: K,
884
+ callbackOrOptions?: RelationCallback | WhereHasOptions,
885
+ maybeOptions?: WhereHasOptions
886
+ ): SelectQueryBuilder<T, TTable> {
887
+ const predicate = buildWhereHasPredicate(
888
+ this.env,
889
+ this.context,
890
+ this.relationFacet,
891
+ table => this.createChildBuilder(table),
892
+ relationName,
893
+ callbackOrOptions,
894
+ maybeOptions,
895
+ false
896
+ );
763
897
 
898
+ return this.where(predicate);
899
+ }
764
900
 
901
+ /**
902
+ * Adds a WHERE NOT EXISTS condition based on a relationship
903
+ * @param relationName - Name of the relationship to check
904
+ * @param callback - Optional callback to modify the relationship query
905
+ * @returns New query builder instance with the relationship non-existence check
906
+ *
907
+ * @example
908
+ * qb.whereHasNot('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
909
+ */
910
+ whereHasNot<K extends keyof TTable['relations'] & string>(
911
+ relationName: K,
912
+ callbackOrOptions?: RelationCallback | WhereHasOptions,
913
+ maybeOptions?: WhereHasOptions
914
+ ): SelectQueryBuilder<T, TTable> {
915
+ const predicate = buildWhereHasPredicate(
916
+ this.env,
917
+ this.context,
918
+ this.relationFacet,
919
+ table => this.createChildBuilder(table),
920
+ relationName,
921
+ callbackOrOptions,
922
+ maybeOptions,
923
+ true
924
+ );
925
+
926
+ return this.where(predicate);
927
+ }
765
928
 
766
929
  /**
767
930
  * Compiles the query to SQL for a specific dialect
768
931
  * @param dialect - Database dialect to compile for
769
932
  * @returns Compiled query with SQL and parameters
933
+ * @example
934
+ * const compiled = qb.select('id', 'name')
935
+ * .where(eq(userTable.columns.active, true))
936
+ * .compile('postgres');
937
+ * console.log(compiled.sql); // SELECT "id", "name" FROM "users" WHERE "active" = true
770
938
  */
771
939
  compile(dialect: SelectDialectInput): CompiledQuery {
772
940
  const resolved = resolveDialectInput(dialect);
@@ -777,6 +945,11 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
777
945
  * Converts the query to SQL string for a specific dialect
778
946
  * @param dialect - Database dialect to generate SQL for
779
947
  * @returns SQL string representation of the query
948
+ * @example
949
+ * const sql = qb.select('id', 'name')
950
+ * .where(eq(userTable.columns.active, true))
951
+ * .toSql('postgres');
952
+ * console.log(sql); // SELECT "id", "name" FROM "users" WHERE "active" = true
780
953
  */
781
954
  toSql(dialect: SelectDialectInput): string {
782
955
  return this.compile(dialect).sql;
@@ -785,6 +958,9 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
785
958
  /**
786
959
  * Gets the hydration plan for the query
787
960
  * @returns Hydration plan or undefined if none exists
961
+ * @example
962
+ * const plan = qb.include('posts').getHydrationPlan();
963
+ * console.log(plan?.relations); // Information about included relations
788
964
  */
789
965
  getHydrationPlan(): HydrationPlan | undefined {
790
966
  return this.context.hydration.getPlan();
@@ -793,9 +969,12 @@ export class SelectQueryBuilder<T = unknown, TTable extends TableDef = TableDef>
793
969
  /**
794
970
  * Gets the Abstract Syntax Tree (AST) representation of the query
795
971
  * @returns Query AST with hydration applied
972
+ * @example
973
+ * const ast = qb.select('id', 'name').getAST();
974
+ * console.log(ast.columns); // Array of column nodes
975
+ * console.log(ast.from); // From clause information
796
976
  */
797
977
  getAST(): SelectQueryNode {
798
978
  return this.context.hydration.applyToAst(this.context.state.ast);
799
979
  }
800
980
  }
801
-