workers-qb 1.12.0 → 1.14.0

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.
package/dist/index.d.mts CHANGED
@@ -16,7 +16,18 @@ declare enum ConflictTypes {
16
16
  declare enum JoinTypes {
17
17
  INNER = "INNER",
18
18
  LEFT = "LEFT",
19
- CROSS = "CROSS"
19
+ CROSS = "CROSS",
20
+ RIGHT = "RIGHT",
21
+ FULL = "FULL",
22
+ NATURAL = "NATURAL"
23
+ }
24
+ declare enum SetOperationType {
25
+ UNION = "UNION",
26
+ UNION_ALL = "UNION ALL",
27
+ INTERSECT = "INTERSECT",
28
+ INTERSECT_ALL = "INTERSECT ALL",
29
+ EXCEPT = "EXCEPT",
30
+ EXCEPT_ALL = "EXCEPT ALL"
20
31
  }
21
32
 
22
33
  /**
@@ -88,8 +99,8 @@ type SchemaAware<S extends TableSchema, Strict, Loose> = IsEmptySchema<S> extend
88
99
 
89
100
  declare class Raw {
90
101
  isRaw: boolean;
91
- content: any;
92
- constructor(content: any);
102
+ content: string;
103
+ constructor(content: string);
93
104
  }
94
105
  declare class Query<Result = any, IsAsync extends boolean = true> {
95
106
  executeMethod: (query: Query<Result, IsAsync>) => MaybeAsync<IsAsync, Result>;
@@ -99,6 +110,28 @@ declare class Query<Result = any, IsAsync extends boolean = true> {
99
110
  constructor(executeMethod: (query: Query<Result, IsAsync>) => MaybeAsync<IsAsync, Result>, query: string, args?: Primitive[], fetchType?: FetchTypes);
100
111
  execute(): MaybeAsync<IsAsync, Result>;
101
112
  toObject(): RawQuery;
113
+ /**
114
+ * Returns the SQL query string and parameters without executing.
115
+ * Useful for debugging and logging.
116
+ *
117
+ * @example
118
+ * const { sql, params } = qb.select('users').where('id = ?', 1).getQueryAll().toSQL()
119
+ * // sql: "SELECT * FROM users WHERE id = ?"
120
+ * // params: [1]
121
+ */
122
+ toSQL(): {
123
+ sql: string;
124
+ params: Primitive[];
125
+ };
126
+ /**
127
+ * Returns the SQL query with parameters interpolated for debugging purposes.
128
+ * WARNING: This should NEVER be used to execute queries as it bypasses parameterization.
129
+ *
130
+ * @example
131
+ * const debugSql = qb.select('users').where('id = ?', 1).getQueryAll().toDebugSQL()
132
+ * // "SELECT * FROM users WHERE id = 1"
133
+ */
134
+ toDebugSQL(): string;
102
135
  }
103
136
  declare class QueryWithExtra<GenericResultWrapper, Result = any, IsAsync extends boolean = true> extends Query<Result, IsAsync> {
104
137
  private countQuery;
@@ -107,6 +140,10 @@ declare class QueryWithExtra<GenericResultWrapper, Result = any, IsAsync extends
107
140
  }
108
141
  declare function trimQuery(query: string): string;
109
142
 
143
+ interface PaginateOptions {
144
+ page: number;
145
+ perPage: number;
146
+ }
110
147
  interface SelectExecuteOptions {
111
148
  lazy?: boolean;
112
149
  }
@@ -119,9 +156,330 @@ declare class SelectBuilder<Schema extends TableSchema = {}, GenericResultWrappe
119
156
  setDebugger(state: boolean): void;
120
157
  tableName(tableName: SelectAll['tableName']): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
121
158
  fields(fields: SelectAll['fields']): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
159
+ /**
160
+ * Enable DISTINCT selection to remove duplicate rows from results.
161
+ *
162
+ * @param columns - Optional array of columns for DISTINCT ON (PostgreSQL only).
163
+ * If not provided, applies simple DISTINCT.
164
+ *
165
+ * @example
166
+ * // Simple DISTINCT
167
+ * qb.select('users').distinct().execute()
168
+ * // SELECT DISTINCT * FROM users
169
+ *
170
+ * @example
171
+ * // DISTINCT ON specific columns (PostgreSQL)
172
+ * qb.select('users').distinct(['department']).fields(['department', 'name']).execute()
173
+ * // SELECT DISTINCT ON (department) department, name FROM users
174
+ */
175
+ distinct(columns?: Array<string>): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
122
176
  where(conditions: string | Array<string>, params?: Primitive | Primitive[]): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
177
+ /**
178
+ * Add an OR WHERE condition to the query.
179
+ * All previously added WHERE conditions are grouped together and ORed with the new condition.
180
+ * Subsequent `.where()` calls after `.orWhere()` are ANDed as independent conditions.
181
+ *
182
+ * @param conditions - The SQL condition string(s) (can use ? placeholders)
183
+ * @param params - The parameter(s) to bind to the ? placeholders
184
+ *
185
+ * @example
186
+ * // Simple OR condition
187
+ * qb.select('users').where('status = ?', 'active').orWhere('status = ?', 'pending').execute()
188
+ * // SELECT * FROM users WHERE (status = ?) OR (status = ?)
189
+ *
190
+ * @example
191
+ * // Multiple where conditions ORed together
192
+ * qb.select('users')
193
+ * .where('tenant_id = ?', 1)
194
+ * .where('status = ?', 'active')
195
+ * .orWhere('role = ?', 'superadmin')
196
+ * .execute()
197
+ * // SELECT * FROM users WHERE ((tenant_id = ?) AND (status = ?)) OR (role = ?)
198
+ */
199
+ orWhere(conditions: string | Array<string>, params?: Primitive | Primitive[]): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
123
200
  whereIn<T extends string | Array<string>, P extends T extends Array<string> ? Primitive[][] : Primitive[]>(fields: T, values: P): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
201
+ /**
202
+ * Conditionally apply query modifications based on a runtime value.
203
+ * If condition is truthy, the callback is invoked with the current builder
204
+ * and its return value is used. If condition is falsy and an otherwise callback
205
+ * is provided, it is invoked instead. Otherwise, the builder is returned unchanged.
206
+ *
207
+ * @param condition - A value to check for truthiness
208
+ * @param callback - Function that receives the builder and returns a modified builder
209
+ * @param otherwise - Optional function applied when condition is falsy
210
+ *
211
+ * @example
212
+ * qb.select('users')
213
+ * .when(nameFilter, q => q.where('name LIKE ?', [`%${nameFilter}%`]))
214
+ * .when(sortByDate, q => q.orderBy({ created_at: 'DESC' }))
215
+ * .execute()
216
+ *
217
+ * @example
218
+ * // With otherwise callback
219
+ * qb.select('products')
220
+ * .when(
221
+ * inStock,
222
+ * q => q.where('stock > ?', 0),
223
+ * q => q.where('stock = ?', 0)
224
+ * )
225
+ * .execute()
226
+ */
227
+ when<T>(condition: T | undefined | null | false | 0 | '', callback: (builder: SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>) => SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>, otherwise?: (builder: SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>) => SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
228
+ /**
229
+ * Add a WHERE column IS NULL condition.
230
+ *
231
+ * @param column - The column name to check for NULL
232
+ *
233
+ * @example
234
+ * qb.select('users').whereNull('deleted_at').execute()
235
+ * // SELECT * FROM users WHERE deleted_at IS NULL
236
+ */
237
+ whereNull(column: string): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
238
+ /**
239
+ * Add a WHERE column IS NOT NULL condition.
240
+ *
241
+ * @param column - The column name to check for NOT NULL
242
+ *
243
+ * @example
244
+ * qb.select('users').whereNotNull('email_verified_at').execute()
245
+ * // SELECT * FROM users WHERE email_verified_at IS NOT NULL
246
+ */
247
+ whereNotNull(column: string): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
248
+ /**
249
+ * Add a WHERE column BETWEEN min AND max condition.
250
+ *
251
+ * @param column - The column name
252
+ * @param range - Tuple of [min, max] values
253
+ *
254
+ * @example
255
+ * qb.select('products').whereBetween('price', [10, 100]).execute()
256
+ * // SELECT * FROM products WHERE price BETWEEN ? AND ?
257
+ */
258
+ whereBetween(column: string, range: [Primitive, Primitive]): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
259
+ /**
260
+ * Add a WHERE column NOT BETWEEN min AND max condition.
261
+ *
262
+ * @param column - The column name
263
+ * @param range - Tuple of [min, max] values
264
+ *
265
+ * @example
266
+ * qb.select('products').whereNotBetween('price', [10, 100]).execute()
267
+ * // SELECT * FROM products WHERE price NOT BETWEEN ? AND ?
268
+ */
269
+ whereNotBetween(column: string, range: [Primitive, Primitive]): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
270
+ /**
271
+ * Add an OR WHERE column IS NULL condition.
272
+ *
273
+ * @param column - The column name to check for NULL
274
+ *
275
+ * @example
276
+ * qb.select('users').where('active = ?', true).orWhereNull('deleted_at').execute()
277
+ * // SELECT * FROM users WHERE (active = ?) OR (deleted_at IS NULL)
278
+ */
279
+ orWhereNull(column: string): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
280
+ /**
281
+ * Add an OR WHERE column IS NOT NULL condition.
282
+ *
283
+ * @param column - The column name to check for NOT NULL
284
+ *
285
+ * @example
286
+ * qb.select('users').whereNull('deleted_at').orWhereNotNull('verified_at').execute()
287
+ * // SELECT * FROM users WHERE (deleted_at IS NULL) OR (verified_at IS NOT NULL)
288
+ */
289
+ orWhereNotNull(column: string): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
290
+ /**
291
+ * Add an OR WHERE column BETWEEN min AND max condition.
292
+ *
293
+ * @param column - The column name
294
+ * @param range - Tuple of [min, max] values
295
+ *
296
+ * @example
297
+ * qb.select('products').where('active = ?', true).orWhereBetween('price', [10, 100]).execute()
298
+ * // SELECT * FROM products WHERE (active = ?) OR (price BETWEEN ? AND ?)
299
+ */
300
+ orWhereBetween(column: string, range: [Primitive, Primitive]): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
301
+ /**
302
+ * Add an OR WHERE column NOT BETWEEN min AND max condition.
303
+ *
304
+ * @param column - The column name
305
+ * @param range - Tuple of [min, max] values
306
+ *
307
+ * @example
308
+ * qb.select('products').where('featured = ?', true).orWhereNotBetween('price', [10, 100]).execute()
309
+ * // SELECT * FROM products WHERE (featured = ?) OR (price NOT BETWEEN ? AND ?)
310
+ */
311
+ orWhereNotBetween(column: string, range: [Primitive, Primitive]): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
312
+ /**
313
+ * Add a WHERE column LIKE pattern condition.
314
+ *
315
+ * @param column - The column name
316
+ * @param pattern - The LIKE pattern (e.g., '%search%')
317
+ *
318
+ * @example
319
+ * qb.select('users').whereLike('name', '%john%').execute()
320
+ * // SELECT * FROM users WHERE name LIKE ?
321
+ */
322
+ whereLike(column: string, pattern: string): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
323
+ /**
324
+ * Add a WHERE column NOT LIKE pattern condition.
325
+ *
326
+ * @param column - The column name
327
+ * @param pattern - The LIKE pattern
328
+ *
329
+ * @example
330
+ * qb.select('users').whereNotLike('email', '%@spam.com').execute()
331
+ * // SELECT * FROM users WHERE email NOT LIKE ?
332
+ */
333
+ whereNotLike(column: string, pattern: string): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
334
+ /**
335
+ * Add an OR WHERE column LIKE pattern condition.
336
+ *
337
+ * @param column - The column name
338
+ * @param pattern - The LIKE pattern (e.g., '%search%')
339
+ *
340
+ * @example
341
+ * qb.select('users').whereLike('name', '%john%').orWhereLike('email', '%john%').execute()
342
+ * // SELECT * FROM users WHERE (name LIKE ?) OR (email LIKE ?)
343
+ */
344
+ orWhereLike(column: string, pattern: string): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
345
+ /**
346
+ * Add an OR WHERE column NOT LIKE pattern condition.
347
+ *
348
+ * @param column - The column name
349
+ * @param pattern - The LIKE pattern
350
+ *
351
+ * @example
352
+ * qb.select('users').where('active = ?', true).orWhereNotLike('email', '%@spam.com').execute()
353
+ * // SELECT * FROM users WHERE (active = ?) OR (email NOT LIKE ?)
354
+ */
355
+ orWhereNotLike(column: string, pattern: string): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
356
+ /**
357
+ * Add a WHERE column NOT IN (values) condition.
358
+ *
359
+ * @param fields - Column name(s) to check
360
+ * @param values - Values to exclude
361
+ *
362
+ * @example
363
+ * qb.select('users').whereNotIn('status', ['banned', 'suspended']).execute()
364
+ * // SELECT * FROM users WHERE (status) NOT IN (VALUES (?), (?))
365
+ */
366
+ whereNotIn<T extends string | Array<string>, P extends T extends Array<string> ? Primitive[][] : Primitive[]>(fields: T, values: P): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
124
367
  join(join: SelectAll['join']): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
368
+ /**
369
+ * Add an INNER JOIN to the query.
370
+ */
371
+ innerJoin(params: {
372
+ table: string;
373
+ on: string;
374
+ alias?: string;
375
+ }): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
376
+ /**
377
+ * Add a LEFT JOIN to the query.
378
+ */
379
+ leftJoin(params: {
380
+ table: string;
381
+ on: string;
382
+ alias?: string;
383
+ }): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
384
+ /**
385
+ * Add a RIGHT JOIN to the query.
386
+ */
387
+ rightJoin(params: {
388
+ table: string;
389
+ on: string;
390
+ alias?: string;
391
+ }): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
392
+ /**
393
+ * Add a FULL OUTER JOIN to the query.
394
+ */
395
+ fullJoin(params: {
396
+ table: string;
397
+ on: string;
398
+ alias?: string;
399
+ }): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
400
+ /**
401
+ * Add a CROSS JOIN to the query.
402
+ */
403
+ crossJoin(params: {
404
+ table: string;
405
+ alias?: string;
406
+ }): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
407
+ /**
408
+ * Add a NATURAL JOIN to the query.
409
+ * Natural joins automatically match columns with the same name.
410
+ */
411
+ naturalJoin(table: string): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
412
+ /**
413
+ * Define a Common Table Expression (CTE) using the WITH clause.
414
+ * CTEs allow you to define named temporary result sets that can be referenced
415
+ * in the main query, making complex queries more readable.
416
+ *
417
+ * @param name - The name of the CTE
418
+ * @param query - The query that defines the CTE
419
+ * @param columns - Optional column names for the CTE
420
+ *
421
+ * @example
422
+ * // Simple CTE
423
+ * qb.select('orders')
424
+ * .with('active_users', qb.select('users').where('status = ?', 'active'))
425
+ * .join({ table: 'active_users', on: 'orders.user_id = active_users.id' })
426
+ * .execute()
427
+ *
428
+ * @example
429
+ * // Multiple CTEs
430
+ * qb.select('combined')
431
+ * .with('cte1', qb.select('table1').where('x = ?', 1))
432
+ * .with('cte2', qb.select('table2').where('y = ?', 2))
433
+ * .execute()
434
+ */
435
+ with(name: string, query: SelectBuilder<any, any, any, any> | SelectAll, columns?: string[]): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
436
+ /**
437
+ * Combine results with another query using UNION (removes duplicates).
438
+ *
439
+ * @param query - The query to union with
440
+ * @param all - If true, uses UNION ALL to keep duplicates
441
+ *
442
+ * @example
443
+ * qb.select('active_users').fields(['id', 'name'])
444
+ * .union(qb.select('archived_users').fields(['id', 'name']))
445
+ * .execute()
446
+ */
447
+ union(query: SelectBuilder<any, any, any, any> | SelectAll, all?: boolean): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
448
+ /**
449
+ * Combine results with another query using UNION ALL (keeps duplicates).
450
+ *
451
+ * @param query - The query to union with
452
+ *
453
+ * @example
454
+ * qb.select('table1').fields(['id'])
455
+ * .unionAll(qb.select('table2').fields(['id']))
456
+ * .execute()
457
+ */
458
+ unionAll(query: SelectBuilder<any, any, any, any> | SelectAll): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
459
+ /**
460
+ * Return only rows present in both queries using INTERSECT.
461
+ *
462
+ * @param query - The query to intersect with
463
+ * @param all - If true, uses INTERSECT ALL
464
+ *
465
+ * @example
466
+ * qb.select('users').fields(['id'])
467
+ * .intersect(qb.select('admins').fields(['user_id']))
468
+ * .execute()
469
+ */
470
+ intersect(query: SelectBuilder<any, any, any, any> | SelectAll, all?: boolean): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
471
+ /**
472
+ * Return rows from the first query that are not in the second query using EXCEPT.
473
+ *
474
+ * @param query - The query to except
475
+ * @param all - If true, uses EXCEPT ALL
476
+ *
477
+ * @example
478
+ * qb.select('all_users').fields(['id'])
479
+ * .except(qb.select('blocked_users').fields(['user_id']))
480
+ * .execute()
481
+ */
482
+ except(query: SelectBuilder<any, any, any, any> | SelectAll, all?: boolean): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
125
483
  groupBy(groupBy: SelectAll['groupBy']): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
126
484
  having(conditions: string | Array<string>, params?: Primitive | Primitive[]): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
127
485
  orderBy(orderBy: SelectAll['orderBy']): SelectBuilder<Schema, GenericResultWrapper, GenericResult, IsAsync>;
@@ -140,7 +498,69 @@ declare class SelectBuilder<Schema extends TableSchema = {}, GenericResultWrappe
140
498
  } ? true : false>;
141
499
  one(): MaybeAsync<IsAsync, OneResult<GenericResultWrapper, GenericResult>>;
142
500
  count(): MaybeAsync<IsAsync, CountResult<GenericResultWrapper>>;
501
+ /**
502
+ * Execute the query with pagination, returning results along with pagination metadata.
503
+ *
504
+ * @param options - Pagination options
505
+ * @param options.page - The page number (1-indexed)
506
+ * @param options.perPage - Number of results per page
507
+ *
508
+ * @example
509
+ * const result = await qb.select('users')
510
+ * .where('active = ?', true)
511
+ * .paginate({ page: 2, perPage: 20 })
512
+ *
513
+ * // Returns:
514
+ * // {
515
+ * // results: [...],
516
+ * // pagination: {
517
+ * // page: 2,
518
+ * // perPage: 20,
519
+ * // total: 150,
520
+ * // totalPages: 8,
521
+ * // hasNext: true,
522
+ * // hasPrev: true
523
+ * // }
524
+ * // }
525
+ */
526
+ paginate(options: PaginateOptions): MaybeAsync<IsAsync, PaginatedResult<GenericResultWrapper, GenericResult>>;
143
527
  getOptions(): SelectAll;
528
+ /**
529
+ * Returns the SQL query string and parameters without executing.
530
+ * Useful for debugging and logging.
531
+ *
532
+ * @example
533
+ * const { sql, params } = qb.select('users').where('id = ?', 1).toSQL()
534
+ * // sql: "SELECT * FROM users WHERE id = ?"
535
+ * // params: [1]
536
+ */
537
+ toSQL(): {
538
+ sql: string;
539
+ params: Primitive[];
540
+ };
541
+ /**
542
+ * Returns the SQL query with parameters interpolated for debugging purposes.
543
+ * WARNING: This should NEVER be used to execute queries as it bypasses parameterization.
544
+ *
545
+ * @example
546
+ * const debugSql = qb.select('users').where('id = ?', 1).toDebugSQL()
547
+ * // "SELECT * FROM users WHERE id = 1"
548
+ */
549
+ toDebugSQL(): string;
550
+ /**
551
+ * Get the query plan for this query using EXPLAIN.
552
+ * Returns the query plan as an array of rows showing how the database will execute the query.
553
+ *
554
+ * @example
555
+ * const plan = await qb.select('users').where('id = ?', 1).explain()
556
+ * // Returns query plan rows
557
+ */
558
+ explain(): MaybeAsync<IsAsync, ArrayResult<GenericResultWrapper, {
559
+ id: number;
560
+ parent: number;
561
+ notused: number;
562
+ detail: string;
563
+ }, IsAsync>>;
144
564
  }
145
565
 
146
566
  type OmitIndexSignature<ObjectType> = {
@@ -161,8 +581,22 @@ type Primitive = null | string | number | boolean | bigint | ArrayBuffer | Raw |
161
581
  type QueryLoggerMeta = {
162
582
  duration?: number;
163
583
  };
584
+ /**
585
+ * Hook called before a query is executed.
586
+ * Can modify the query or cancel execution by throwing.
587
+ */
588
+ type BeforeQueryHook<IsAsync extends boolean = true> = (query: RawQuery, type: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'RAW') => MaybeAsync<IsAsync, RawQuery | void>;
589
+ /**
590
+ * Hook called after a query is executed.
591
+ * Can modify the result or perform side effects.
592
+ */
593
+ type AfterQueryHook<IsAsync extends boolean = true> = (result: any, query: RawQuery, duration: number) => MaybeAsync<IsAsync, any>;
164
594
  type QueryBuilderOptions<IsAsync extends boolean = true> = {
165
595
  logger?: (query: RawQuery, meta: QueryLoggerMeta) => MaybeAsync<IsAsync, void>;
596
+ /** Hook called before each query execution */
597
+ beforeQuery?: BeforeQueryHook<IsAsync>;
598
+ /** Hook called after each query execution */
599
+ afterQuery?: AfterQueryHook<IsAsync>;
166
600
  };
167
601
  type DefaultObject = Record<string, Primitive>;
168
602
  type DefaultReturnObject = Record<string, null | string | number | boolean | bigint | ArrayBuffer>;
@@ -187,6 +621,8 @@ type SelectOne = {
187
621
  offset?: number;
188
622
  subQueryPlaceholders?: Record<string, SelectAll>;
189
623
  subQueryTokenNextId?: number;
624
+ /** Enable DISTINCT selection. Can be true for simple DISTINCT or an array of columns for DISTINCT ON */
625
+ distinct?: boolean | Array<string>;
190
626
  };
191
627
  type RawQuery = {
192
628
  query: string;
@@ -200,9 +636,22 @@ type RawQueryFetchAll = Omit<RawQuery, 'fetchType'> & {
200
636
  fetchType: FetchTypes.ALL;
201
637
  };
202
638
  type RawQueryWithoutFetching = Omit<RawQuery, 'fetchType'>;
639
+ type SetOperation = {
640
+ type: SetOperationType | string;
641
+ query: SelectAll;
642
+ };
643
+ type CTEDefinition = {
644
+ name: string;
645
+ query: SelectAll;
646
+ columns?: string[];
647
+ };
203
648
  type SelectAll = SelectOne & {
204
649
  limit?: number;
205
650
  lazy?: boolean;
651
+ /** Set operations (UNION, INTERSECT, EXCEPT) to combine with this query */
652
+ setOperations?: SetOperation[];
653
+ /** Common Table Expressions (CTEs) for WITH clause */
654
+ cteDefinitions?: CTEDefinition[];
206
655
  };
207
656
  type ConflictUpsert = {
208
657
  column: string | Array<string>;
@@ -293,6 +742,18 @@ type OneResult<ResultWrapper, Result> = Merge<ResultWrapper, {
293
742
  type CountResult<GenericResultWrapper> = OneResult<GenericResultWrapper, {
294
743
  total: number;
295
744
  }>;
745
+ type PaginationMeta = {
746
+ page: number;
747
+ perPage: number;
748
+ total: number;
749
+ totalPages: number;
750
+ hasNext: boolean;
751
+ hasPrev: boolean;
752
+ };
753
+ type PaginatedResult<ResultWrapper, Result> = Merge<ResultWrapper, {
754
+ results?: Array<Result>;
755
+ pagination: PaginationMeta;
756
+ }>;
296
757
  type AsyncType<T> = Promise<T>;
297
758
  type SyncType<T> = T;
298
759
  type MaybeAsync<IsAsync extends boolean, T> = IsAsync extends true ? AsyncType<T> : SyncType<T>;
@@ -354,15 +815,45 @@ type TypedDelete<S extends TableSchema, T extends TableName<S>> = {
354
815
  */
355
816
  type InferResult<S extends TableSchema, T extends TableName<S>, F> = F extends '*' ? S[T] : F extends ColumnName<S, T>[] ? Pick<S[T], F[number]> : F extends ColumnName<S, T> ? Pick<S[T], F> : S[T];
356
817
 
818
+ type LoggerFunction = (query: RawQuery, meta: QueryLoggerMeta) => void | Promise<void>;
357
819
  declare function defaultLogger(query: RawQuery, meta: QueryLoggerMeta): any;
358
- declare function asyncLoggerWrapper<Async extends boolean = true>(query: Query<any, Async> | Query<any, Async>[], loggerFunction: CallableFunction | undefined, innerFunction: () => any): Promise<any>;
359
- declare function syncLoggerWrapper<Async extends boolean = false>(query: Query<any, Async> | Query<any, Async>[], loggerFunction: CallableFunction | undefined, innerFunction: () => any): any;
820
+ declare function asyncLoggerWrapper<Async extends boolean = true>(query: Query<any, Async> | Query<any, Async>[], loggerFunction: LoggerFunction | undefined, innerFunction: () => any): Promise<any>;
821
+ declare function syncLoggerWrapper<Async extends boolean = false>(query: Query<any, Async> | Query<any, Async>[], loggerFunction: LoggerFunction | undefined, innerFunction: () => any): any;
360
822
 
361
823
  declare class QueryBuilder<Schema extends TableSchema = {}, GenericResultWrapper = unknown, IsAsync extends boolean = true> {
362
824
  protected options: QueryBuilderOptions<IsAsync>;
363
825
  loggerWrapper: typeof asyncLoggerWrapper;
364
826
  constructor(options?: QueryBuilderOptions<IsAsync>);
365
827
  setDebugger(state: boolean): void;
828
+ /**
829
+ * Register a hook to be called before each query execution.
830
+ * The hook can modify the query or throw to cancel execution.
831
+ *
832
+ * @param hook - The hook function to call before query execution
833
+ *
834
+ * @example
835
+ * qb.beforeQuery((query, type) => {
836
+ * // Add tenant filter to all SELECT/UPDATE/DELETE queries
837
+ * if (type !== 'INSERT' && type !== 'RAW') {
838
+ * query.query = query.query.replace('WHERE', `WHERE tenant_id = ${tenantId} AND`)
839
+ * }
840
+ * return query
841
+ * })
842
+ */
843
+ beforeQuery(hook: BeforeQueryHook<IsAsync>): this;
844
+ /**
845
+ * Register a hook to be called after each query execution.
846
+ * The hook receives the result and can modify it or perform side effects.
847
+ *
848
+ * @param hook - The hook function to call after query execution
849
+ *
850
+ * @example
851
+ * qb.afterQuery((result, query, duration) => {
852
+ * metrics.record(query.query, duration)
853
+ * return result
854
+ * })
855
+ */
856
+ afterQuery(hook: AfterQueryHook<IsAsync>): this;
366
857
  execute(query: Query<any, IsAsync>): MaybeAsync<IsAsync, any>;
367
858
  batchExecute(queryArray: Query<any, IsAsync>[]): MaybeAsync<IsAsync, any[]>;
368
859
  lazyExecute(query: Query<any, IsAsync>): IsAsync extends true ? Promise<AsyncIterable<any>> : Iterable<any>;
@@ -404,6 +895,7 @@ declare class QueryBuilder<Schema extends TableSchema = {}, GenericResultWrapper
404
895
  protected _update(params: Update): string;
405
896
  protected _delete(params: Delete): string;
406
897
  protected _select(params: SelectAll, queryArgs?: any[]): string;
898
+ protected _distinct(value?: boolean | Array<string>): string;
407
899
  protected _fields(value?: string | Array<string>): string;
408
900
  protected _where(value: Where | undefined, context?: {
409
901
  subQueryPlaceholders?: Record<string, SelectAll>;
@@ -472,7 +964,24 @@ declare class D1QB<Schema extends TableSchema = {}> extends QueryBuilder<Schema,
472
964
  constructor(db: D1Database, options?: QueryBuilderOptions);
473
965
  migrations(options: MigrationOptions): asyncMigrationsBuilder<D1Result>;
474
966
  execute(query: Query): Promise<any>;
967
+ private _getQueryType;
475
968
  batchExecute(queryArray: Query[]): Promise<any>;
969
+ /**
970
+ * Execute multiple queries atomically as a transaction.
971
+ * D1 uses batching for transactions - all queries succeed or all fail together.
972
+ *
973
+ * @param callback - A function that receives a transaction builder and returns queries to execute
974
+ * @returns Array of results from all queries in the transaction
975
+ *
976
+ * @example
977
+ * const results = await qb.transaction(async (tx) => {
978
+ * return [
979
+ * tx.insert({ tableName: 'orders', data: { user_id: 1, total: 100 } }),
980
+ * tx.update({ tableName: 'users', data: { balance: 50 }, where: { conditions: 'id = ?', params: [1] } }),
981
+ * ]
982
+ * })
983
+ */
984
+ transaction<T extends Query<any, true>[]>(callback: (tx: D1QB<Schema>) => T | Promise<T>): Promise<any[]>;
476
985
  }
477
986
 
478
987
  interface SqlStorage {
@@ -487,17 +996,96 @@ declare class DOQB<Schema extends TableSchema = {}> extends QueryBuilder<Schema,
487
996
  constructor(db: SqlStorage, options?: QueryBuilderOptions<false>);
488
997
  migrations(options: MigrationOptions): syncMigrationsBuilder<DOResult>;
489
998
  execute(query: Query<any, false>): any;
999
+ private _getQueryType;
490
1000
  lazyExecute(query: Query<any, false>): Iterable<any>;
1001
+ /**
1002
+ * Execute multiple queries atomically as a transaction.
1003
+ * Uses SQLite's BEGIN/COMMIT/ROLLBACK for atomicity.
1004
+ * Note: This should be called within blockConcurrencyWhile for proper isolation in Durable Objects.
1005
+ *
1006
+ * @param callback - A function that receives the query builder and executes queries
1007
+ * @returns The return value of the callback
1008
+ *
1009
+ * @example
1010
+ * // Inside a Durable Object
1011
+ * this.ctx.blockConcurrencyWhile(() => {
1012
+ * qb.transaction((tx) => {
1013
+ * tx.insert({ tableName: 'orders', data: { user_id: 1, total: 100 } }).execute()
1014
+ * tx.update({ tableName: 'users', data: { balance: 50 }, where: { conditions: 'id = ?', params: [1] } }).execute()
1015
+ * })
1016
+ * })
1017
+ */
1018
+ transaction<T>(callback: (tx: DOQB<Schema>) => T): T;
491
1019
  }
492
1020
 
1021
+ declare class PGMigrationsBuilder extends asyncMigrationsBuilder<PGResult> {
1022
+ initialize(): Promise<void>;
1023
+ apply(): Promise<Array<{
1024
+ name: string;
1025
+ sql: string;
1026
+ }>>;
1027
+ }
493
1028
  declare class PGQB<Schema extends TableSchema = {}> extends QueryBuilder<Schema, PGResult, true> {
494
1029
  db: any;
495
- _migrationsBuilder: typeof asyncMigrationsBuilder;
496
1030
  constructor(db: any, options?: QueryBuilderOptions);
497
- migrations(options: MigrationOptions): asyncMigrationsBuilder<PGResult>;
1031
+ migrations(options: MigrationOptions): PGMigrationsBuilder;
498
1032
  connect(): Promise<void>;
499
1033
  close(): Promise<void>;
500
1034
  execute(query: Query): Promise<any>;
501
1035
  }
502
1036
 
503
- export { type ArrayResult, type AsyncType, type ColumnName, ConflictTypes, type ConflictUpsert, type CountResult, type D1Meta, D1QB, type D1Result, DOQB, type DOResult, type DefaultObject, type DefaultReturnObject, type Delete, type DeleteReturning, type DeleteWithoutReturning, FetchTypes, type FullArrayResult, type InferResult, type Insert, type InsertData, type InsertMultiple, type InsertOne, type InsertWithoutReturning, type IsEmptySchema, type IterableResult, type Join, JoinTypes, type MaybeAsync, type Migration, type MigrationEntry, type MigrationOptions, type OneResult, OrderTypes, PGQB, type PGResult, type Primitive, Query, QueryBuilder, type QueryBuilderOptions, type QueryLoggerMeta, QueryWithExtra, Raw, type RawQuery, type RawQueryFetchAll, type RawQueryFetchOne, type RawQueryWithoutFetching, type SchemaAware, type SelectAll, type SelectColumns, type SelectOne, type SyncType, type TableName, type TableSchema, type TypedDelete, type TypedInsert, type TypedSelectAll, type TypedSelectOne, type TypedUpdate, type Update, type UpdateData, type UpdateReturning, type UpdateWithoutReturning, type Where, asyncLoggerWrapper, asyncMigrationsBuilder, defaultLogger, syncLoggerWrapper, syncMigrationsBuilder, trimQuery };
1037
+ /**
1038
+ * Custom error class for Query Builder errors with enhanced context.
1039
+ * Provides helpful information about what went wrong and how to fix it.
1040
+ */
1041
+ declare class QueryBuilderError extends Error {
1042
+ query?: string;
1043
+ expectedParams?: number;
1044
+ receivedParams?: number;
1045
+ hint?: string;
1046
+ clause?: string;
1047
+ constructor(message: string, options?: {
1048
+ query?: string;
1049
+ expectedParams?: number;
1050
+ receivedParams?: number;
1051
+ hint?: string;
1052
+ clause?: string;
1053
+ });
1054
+ }
1055
+ /**
1056
+ * Error thrown when there's a parameter count mismatch.
1057
+ */
1058
+ declare class ParameterMismatchError extends QueryBuilderError {
1059
+ constructor(options: {
1060
+ clause: string;
1061
+ query?: string;
1062
+ expectedParams: number;
1063
+ receivedParams: number;
1064
+ });
1065
+ }
1066
+ /**
1067
+ * Error thrown when required data is missing.
1068
+ */
1069
+ declare class MissingDataError extends QueryBuilderError {
1070
+ constructor(operation: string, field: string);
1071
+ }
1072
+ /**
1073
+ * Error thrown when an invalid configuration is detected.
1074
+ */
1075
+ declare class InvalidConfigurationError extends QueryBuilderError {
1076
+ constructor(message: string, hint?: string);
1077
+ }
1078
+ /**
1079
+ * Error thrown when a subquery token is not found.
1080
+ */
1081
+ declare class SubqueryTokenError extends QueryBuilderError {
1082
+ constructor(token: string);
1083
+ }
1084
+ /**
1085
+ * Error thrown when subquery context is missing.
1086
+ */
1087
+ declare class MissingSubqueryContextError extends QueryBuilderError {
1088
+ constructor();
1089
+ }
1090
+
1091
+ export { type AfterQueryHook, type ArrayResult, type AsyncType, type BeforeQueryHook, type CTEDefinition, type ColumnName, ConflictTypes, type ConflictUpsert, type CountResult, type D1Meta, D1QB, type D1Result, DOQB, type DOResult, type DefaultObject, type DefaultReturnObject, type Delete, type DeleteReturning, type DeleteWithoutReturning, FetchTypes, type FullArrayResult, type InferResult, type Insert, type InsertData, type InsertMultiple, type InsertOne, type InsertWithoutReturning, InvalidConfigurationError, type IsEmptySchema, type IterableResult, type Join, JoinTypes, type MaybeAsync, type Migration, type MigrationEntry, type MigrationOptions, MissingDataError, MissingSubqueryContextError, type OneResult, OrderTypes, PGQB, type PGResult, type PaginatedResult, type PaginationMeta, ParameterMismatchError, type Primitive, Query, QueryBuilder, QueryBuilderError, type QueryBuilderOptions, type QueryLoggerMeta, QueryWithExtra, Raw, type RawQuery, type RawQueryFetchAll, type RawQueryFetchOne, type RawQueryWithoutFetching, type SchemaAware, type SelectAll, type SelectColumns, type SelectOne, type SetOperation, SetOperationType, SubqueryTokenError, type SyncType, type TableName, type TableSchema, type TypedDelete, type TypedInsert, type TypedSelectAll, type TypedSelectOne, type TypedUpdate, type Update, type UpdateData, type UpdateReturning, type UpdateWithoutReturning, type Where, asyncLoggerWrapper, asyncMigrationsBuilder, defaultLogger, syncLoggerWrapper, syncMigrationsBuilder, trimQuery };