relq 1.0.69 → 1.0.71

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.
@@ -149,7 +149,12 @@ function getLoadedDialects() {
149
149
  .map(([dialect]) => dialect);
150
150
  }
151
151
  async function preloadAdapters(dialects) {
152
- await Promise.all(dialects.map(dialect => getAdapter(dialect)));
152
+ const results = await Promise.allSettled(dialects.map(dialect => getAdapter(dialect)));
153
+ const failures = results.filter((r) => r.status === 'rejected');
154
+ if (failures.length > 0) {
155
+ const messages = failures.map(f => f.reason?.message || String(f.reason)).join('; ');
156
+ throw new Error(`Failed to load ${failures.length} adapter(s): ${messages}`);
157
+ }
153
158
  }
154
159
  async function preloadFamily(family) {
155
160
  registerDefaultAdapters();
@@ -146,7 +146,11 @@ async function closeAllPools() {
146
146
  closePromises.push(entry.pool.end());
147
147
  pools.delete(key);
148
148
  }
149
- await Promise.all(closePromises);
149
+ const results = await Promise.allSettled(closePromises);
150
+ const failures = results.filter((r) => r.status === 'rejected');
151
+ if (failures.length > 0) {
152
+ console.warn(`Warning: ${failures.length} pool(s) failed to close gracefully`);
153
+ }
150
154
  }
151
155
  function getActivePoolCount() {
152
156
  return pools.size;
@@ -52,6 +52,13 @@ class ConnectedSelectBuilder {
52
52
  this.builder.orderBy(dbColumn, direction);
53
53
  return this;
54
54
  }
55
+ orderByNulls(column, direction, nulls) {
56
+ const dbColumn = this.relq[methods_1.INTERNAL].hasColumnMapping()
57
+ ? Object.keys(this.relq[methods_1.INTERNAL].transformToDbColumns(this.tableName, { [column]: true }))[0]
58
+ : column;
59
+ this.builder.orderByNulls(dbColumn, direction, nulls);
60
+ return this;
61
+ }
55
62
  limit(count) {
56
63
  this.builder.limit(count);
57
64
  return this;
@@ -114,6 +114,10 @@ class SelectBuilder {
114
114
  this.orderByColumns.push({ column, direction });
115
115
  return this;
116
116
  }
117
+ orderByNulls(column, direction, nulls) {
118
+ this.orderByColumns.push({ column, direction, nulls });
119
+ return this;
120
+ }
117
121
  orderAsc(column) {
118
122
  return this.orderBy(column, 'ASC');
119
123
  }
@@ -325,7 +329,13 @@ class SelectBuilder {
325
329
  }
326
330
  if (this.orderByColumns.length > 0) {
327
331
  query += ' ORDER BY ' + this.orderByColumns
328
- .map(order => (0, pg_format_1.default)('%I %s', order.column, order.direction))
332
+ .map(order => {
333
+ let sql = (0, pg_format_1.default)('%I %s', order.column, order.direction);
334
+ if (order.nulls) {
335
+ sql += ` NULLS ${order.nulls}`;
336
+ }
337
+ return sql;
338
+ })
329
339
  .join(', ');
330
340
  }
331
341
  if (this.limitValue !== undefined) {
@@ -101,7 +101,12 @@ export function getLoadedDialects() {
101
101
  .map(([dialect]) => dialect);
102
102
  }
103
103
  export async function preloadAdapters(dialects) {
104
- await Promise.all(dialects.map(dialect => getAdapter(dialect)));
104
+ const results = await Promise.allSettled(dialects.map(dialect => getAdapter(dialect)));
105
+ const failures = results.filter((r) => r.status === 'rejected');
106
+ if (failures.length > 0) {
107
+ const messages = failures.map(f => f.reason?.message || String(f.reason)).join('; ');
108
+ throw new Error(`Failed to load ${failures.length} adapter(s): ${messages}`);
109
+ }
105
110
  }
106
111
  export async function preloadFamily(family) {
107
112
  registerDefaultAdapters();
@@ -104,7 +104,11 @@ export async function closeAllPools() {
104
104
  closePromises.push(entry.pool.end());
105
105
  pools.delete(key);
106
106
  }
107
- await Promise.all(closePromises);
107
+ const results = await Promise.allSettled(closePromises);
108
+ const failures = results.filter((r) => r.status === 'rejected');
109
+ if (failures.length > 0) {
110
+ console.warn(`Warning: ${failures.length} pool(s) failed to close gracefully`);
111
+ }
108
112
  }
109
113
  export function getActivePoolCount() {
110
114
  return pools.size;
@@ -49,6 +49,13 @@ export class ConnectedSelectBuilder {
49
49
  this.builder.orderBy(dbColumn, direction);
50
50
  return this;
51
51
  }
52
+ orderByNulls(column, direction, nulls) {
53
+ const dbColumn = this.relq[INTERNAL].hasColumnMapping()
54
+ ? Object.keys(this.relq[INTERNAL].transformToDbColumns(this.tableName, { [column]: true }))[0]
55
+ : column;
56
+ this.builder.orderByNulls(dbColumn, direction, nulls);
57
+ return this;
58
+ }
52
59
  limit(count) {
53
60
  this.builder.limit(count);
54
61
  return this;
@@ -108,6 +108,10 @@ export class SelectBuilder {
108
108
  this.orderByColumns.push({ column, direction });
109
109
  return this;
110
110
  }
111
+ orderByNulls(column, direction, nulls) {
112
+ this.orderByColumns.push({ column, direction, nulls });
113
+ return this;
114
+ }
111
115
  orderAsc(column) {
112
116
  return this.orderBy(column, 'ASC');
113
117
  }
@@ -319,7 +323,13 @@ export class SelectBuilder {
319
323
  }
320
324
  if (this.orderByColumns.length > 0) {
321
325
  query += ' ORDER BY ' + this.orderByColumns
322
- .map(order => format('%I %s', order.column, order.direction))
326
+ .map(order => {
327
+ let sql = format('%I %s', order.column, order.direction);
328
+ if (order.nulls) {
329
+ sql += ` NULLS ${order.nulls}`;
330
+ }
331
+ return sql;
332
+ })
323
333
  .join(', ');
324
334
  }
325
335
  if (this.limitValue !== undefined) {
package/dist/index.d.ts CHANGED
@@ -1676,6 +1676,26 @@ export declare class SelectBuilder {
1676
1676
  groupBy(...columns: string[]): SelectBuilder;
1677
1677
  having(callback: (builder: TypedConditionBuilder<any>) => TypedConditionBuilder<any>): SelectBuilder;
1678
1678
  orderBy(column: string, direction?: "ASC" | "DESC"): SelectBuilder;
1679
+ /**
1680
+ * Order by with explicit NULLS FIRST or NULLS LAST placement.
1681
+ * PostgreSQL-specific ordering control for NULL values.
1682
+ *
1683
+ * @param column - Column to order by
1684
+ * @param direction - 'ASC' or 'DESC'
1685
+ * @param nulls - 'FIRST' puts NULLs at the beginning, 'LAST' puts them at the end
1686
+ *
1687
+ * @example
1688
+ * ```typescript
1689
+ * // NULLs at end for ascending order (default PostgreSQL behavior for DESC)
1690
+ * builder.orderByNulls('priority', 'ASC', 'LAST')
1691
+ * // SQL: ORDER BY "priority" ASC NULLS LAST
1692
+ *
1693
+ * // NULLs at start for descending order
1694
+ * builder.orderByNulls('created_at', 'DESC', 'FIRST')
1695
+ * // SQL: ORDER BY "created_at" DESC NULLS FIRST
1696
+ * ```
1697
+ */
1698
+ orderByNulls(column: string, direction: "ASC" | "DESC", nulls: "FIRST" | "LAST"): SelectBuilder;
1679
1699
  orderAsc(column: string): SelectBuilder;
1680
1700
  orderDesc(column: string): SelectBuilder;
1681
1701
  limit(count: number): SelectBuilder;
@@ -7706,9 +7726,31 @@ declare class ConnectedDeleteBuilder<TTable = any> {
7706
7726
  run(): Promise<number>;
7707
7727
  run(withMetadata: true): Promise<RelqRun>;
7708
7728
  /**
7709
- * Set RETURNING clause - returns executor with typed run()
7710
- * Use '*' to return all columns, or specify column names
7711
- * Pass null to skip RETURNING clause (useful for conditional returning)
7729
+ * Set RETURNING clause - returns executor with typed results.
7730
+ * Use '*' to return all columns, or specify column names.
7731
+ * Pass null to skip RETURNING clause (useful for conditional returning).
7732
+ *
7733
+ * **Dialect Support:**
7734
+ * | Dialect | Supported | Notes |
7735
+ * |-------------|-----------|-------|
7736
+ * | PostgreSQL | ✅ | Native RETURNING support |
7737
+ * | CockroachDB | ✅ | Native RETURNING support |
7738
+ * | Nile | ✅ | Native RETURNING support |
7739
+ * | AWS DSQL | ✅ | Native RETURNING support |
7740
+ * | MySQL | ❌ | No RETURNING support |
7741
+ * | SQLite | ✅ | Native RETURNING support (3.35+) |
7742
+ *
7743
+ * @param columns - '*' for all, column name(s), or null to skip
7744
+ * @throws {RelqConfigError} If dialect doesn't support RETURNING
7745
+ *
7746
+ * @example
7747
+ * ```typescript
7748
+ * // Get deleted row data
7749
+ * const deleted = await db.table.users.delete()
7750
+ * .where(q => q.equal('id', userId))
7751
+ * .returning('*')
7752
+ * .run()
7753
+ * ```
7712
7754
  */
7713
7755
  returning(columns: null): this;
7714
7756
  returning(columns: "*"): ReturningExecutor<TTable extends {
@@ -7745,31 +7787,60 @@ declare class ConnectedInsertBuilder<TTable = any> {
7745
7787
  clear(): this;
7746
7788
  get total(): number;
7747
7789
  /**
7748
- * ON CONFLICT DO NOTHING - skip insert if conflict occurs
7790
+ * ON CONFLICT DO NOTHING - skip insert if conflict occurs.
7791
+ * The insert is silently skipped when a unique constraint violation occurs.
7792
+ *
7793
+ * **Dialect Support:**
7794
+ * | Dialect | Supported | Notes |
7795
+ * |-------------|-----------|-------|
7796
+ * | PostgreSQL | ✅ | Native ON CONFLICT support |
7797
+ * | CockroachDB | ✅ | Native ON CONFLICT support |
7798
+ * | Nile | ✅ | Native ON CONFLICT support |
7799
+ * | AWS DSQL | ✅ | Native ON CONFLICT support |
7800
+ * | MySQL | ⚠️ | Uses INSERT IGNORE (different semantics) |
7801
+ * | SQLite | ✅ | Native ON CONFLICT support |
7802
+ *
7749
7803
  * @param columns - Column(s) for conflict detection (array or multiple args)
7804
+ *
7750
7805
  * @example
7751
7806
  * ```typescript
7752
- * .doNothing('email')
7753
- * .doNothing('email', 'userId')
7754
- * .doNothing(['email', 'userId'])
7807
+ * // Single column conflict
7808
+ * await db.table.users.insert({ email: 'test@example.com' })
7809
+ * .doNothing('email')
7810
+ * .execute()
7811
+ *
7812
+ * // Multiple columns (composite unique constraint)
7813
+ * await db.table.userRoles.insert({ userId, roleId })
7814
+ * .doNothing('userId', 'roleId')
7815
+ * .execute()
7755
7816
  * ```
7756
7817
  */
7757
7818
  doNothing(columns: ColumnName<TTable>[]): this;
7758
7819
  doNothing(...columns: ColumnName<TTable>[]): this;
7759
7820
  /**
7760
- * ON CONFLICT DO UPDATE - update row if conflict occurs
7761
- * 100% type-safe, no raw SQL needed
7821
+ * ON CONFLICT DO UPDATE - update row if conflict occurs (UPSERT).
7822
+ * 100% type-safe, no raw SQL needed.
7823
+ *
7824
+ * **Dialect Support:**
7825
+ * | Dialect | Supported | Notes |
7826
+ * |-------------|-----------|-------|
7827
+ * | PostgreSQL | ✅ | Full EXCLUDED.* support |
7828
+ * | CockroachDB | ✅ | Full EXCLUDED.* support |
7829
+ * | Nile | ✅ | Full EXCLUDED.* support |
7830
+ * | AWS DSQL | ✅ | Full EXCLUDED.* support |
7831
+ * | MySQL | ⚠️ | Uses ON DUPLICATE KEY UPDATE (different syntax) |
7832
+ * | SQLite | ✅ | Uses ON CONFLICT DO UPDATE |
7762
7833
  *
7763
7834
  * @param columns - Column(s) for conflict detection
7764
7835
  * @param values - Values to update (static or expression functions)
7765
- * @param where - Optional type-safe where clause builder (same as SELECT/UPDATE/DELETE)
7836
+ * @param where - Optional type-safe where clause for conditional updates
7766
7837
  *
7767
7838
  * @example
7768
7839
  * ```typescript
7769
7840
  * // Static values
7770
7841
  * .doUpdate('email', { status: 'updated' })
7771
7842
  *
7772
- * // Expression functions
7843
+ * // Expression functions - access EXCLUDED and current row values
7773
7844
  * .doUpdate('email', {
7774
7845
  * balance: (excluded, sql) => sql.add(excluded.balance, 100),
7775
7846
  * count: (excluded, sql, row) => sql.increment(1),
@@ -7780,23 +7851,10 @@ declare class ConnectedInsertBuilder<TTable = any> {
7780
7851
  * // Multiple conflict columns
7781
7852
  * .doUpdate(['email', 'tenantId'], { status: 'updated' })
7782
7853
  *
7783
- * // With type-safe where clause (FULL TypedConditionBuilder support!)
7854
+ * // With type-safe where clause
7784
7855
  * .doUpdate('email', { balance: 100 }, q =>
7785
7856
  * q.notEqual('role', 'admin')
7786
7857
  * .in('status', ['active', 'pending'])
7787
- * .or(q => q.isNull('deletedAt').greaterThan('score', 100))
7788
- * )
7789
- *
7790
- * // JSONB conditions in conflict where
7791
- * .doUpdate('email', { updatedAt: new Date() }, q =>
7792
- * q.jsonb.hasKey('metadata', 'verified')
7793
- * .array.contains('tags', ['premium'])
7794
- * )
7795
- *
7796
- * // Regex, fulltext, and more
7797
- * .doUpdate('email', { status: 'matched' }, q =>
7798
- * q.regex('code', '^[A-Z]{3}[0-9]+$')
7799
- * .fulltext.search('description', 'important')
7800
7858
  * )
7801
7859
  * ```
7802
7860
  */
@@ -7812,9 +7870,36 @@ declare class ConnectedInsertBuilder<TTable = any> {
7812
7870
  run(): Promise<number>;
7813
7871
  run(withMetadata: true): Promise<RelqRun>;
7814
7872
  /**
7815
- * Set RETURNING clause - returns executor with typed run()
7816
- * Use '*' to return all columns, or specify column names
7817
- * Pass null to skip RETURNING clause (useful for conditional returning)
7873
+ * Set RETURNING clause - returns executor with typed results.
7874
+ * Use '*' to return all columns, or specify column names.
7875
+ * Pass null to skip RETURNING clause (useful for conditional returning).
7876
+ *
7877
+ * **Dialect Support:**
7878
+ * | Dialect | Supported | Notes |
7879
+ * |-------------|-----------|-------|
7880
+ * | PostgreSQL | ✅ | Native RETURNING support |
7881
+ * | CockroachDB | ✅ | Native RETURNING support |
7882
+ * | Nile | ✅ | Native RETURNING support |
7883
+ * | AWS DSQL | ✅ | Native RETURNING support |
7884
+ * | MySQL | ❌ | Use LAST_INSERT_ID() instead |
7885
+ * | SQLite | ✅ | Native RETURNING support (3.35+) |
7886
+ *
7887
+ * @param columns - '*' for all, column name(s), or null to skip
7888
+ * @throws {RelqConfigError} If dialect doesn't support RETURNING
7889
+ *
7890
+ * @example
7891
+ * ```typescript
7892
+ * // Return all columns
7893
+ * const user = await db.table.users.insert({ name: 'John' })
7894
+ * .returning('*')
7895
+ * .run()
7896
+ * // user has all columns from users table
7897
+ *
7898
+ * // Return specific columns
7899
+ * const { id, createdAt } = await db.table.users.insert({ name: 'John' })
7900
+ * .returning(['id', 'createdAt'])
7901
+ * .run()
7902
+ * ```
7818
7903
  */
7819
7904
  returning(columns: null): this;
7820
7905
  returning(columns: "*"): ReturningExecutor<TTable extends {
@@ -7855,9 +7940,31 @@ declare class ConnectedUpdateBuilder<TTable = any> {
7855
7940
  run(): Promise<number>;
7856
7941
  run(withMetadata: true): Promise<RelqRun>;
7857
7942
  /**
7858
- * Set RETURNING clause - returns executor with typed run()
7859
- * Use '*' to return all columns, or specify column names
7860
- * Pass null to skip RETURNING clause (useful for conditional returning)
7943
+ * Set RETURNING clause - returns executor with typed results.
7944
+ * Use '*' to return all columns, or specify column names.
7945
+ * Pass null to skip RETURNING clause (useful for conditional returning).
7946
+ *
7947
+ * **Dialect Support:**
7948
+ * | Dialect | Supported | Notes |
7949
+ * |-------------|-----------|-------|
7950
+ * | PostgreSQL | ✅ | Native RETURNING support |
7951
+ * | CockroachDB | ✅ | Native RETURNING support |
7952
+ * | Nile | ✅ | Native RETURNING support |
7953
+ * | AWS DSQL | ✅ | Native RETURNING support |
7954
+ * | MySQL | ❌ | No RETURNING support |
7955
+ * | SQLite | ✅ | Native RETURNING support (3.35+) |
7956
+ *
7957
+ * @param columns - '*' for all, column name(s), or null to skip
7958
+ * @throws {RelqConfigError} If dialect doesn't support RETURNING
7959
+ *
7960
+ * @example
7961
+ * ```typescript
7962
+ * // Return updated row
7963
+ * const user = await db.table.users.update({ score: 100 })
7964
+ * .where(q => q.equal('id', userId))
7965
+ * .returning('*')
7966
+ * .run()
7967
+ * ```
7861
7968
  */
7862
7969
  returning(columns: null): this;
7863
7970
  returning(columns: "*"): ReturningExecutor<TTable extends {
@@ -8287,6 +8394,30 @@ declare class ConnectedSelectBuilder<TSchema = any, TTable = any, TColumns exten
8287
8394
  private setupColumnResolver;
8288
8395
  where(callback: (q: TypedConditionBuilder<TTable>) => TypedConditionBuilder<TTable>): this;
8289
8396
  orderBy(column: ColumnName<TTable>, direction?: "ASC" | "DESC"): this;
8397
+ /**
8398
+ * Order by with explicit NULLS FIRST or NULLS LAST placement.
8399
+ * PostgreSQL-specific ordering control for NULL values.
8400
+ *
8401
+ * @param column - Column to order by (type-safe)
8402
+ * @param direction - 'ASC' or 'DESC'
8403
+ * @param nulls - 'FIRST' puts NULLs at the beginning, 'LAST' puts them at the end
8404
+ *
8405
+ * @example
8406
+ * ```typescript
8407
+ * // NULLs at end for ascending order
8408
+ * db.table.users.select()
8409
+ * .orderByNulls('deletedAt', 'ASC', 'LAST')
8410
+ * .all()
8411
+ * // SQL: ORDER BY "deleted_at" ASC NULLS LAST
8412
+ *
8413
+ * // NULLs at start for descending order
8414
+ * db.table.posts.select()
8415
+ * .orderByNulls('publishedAt', 'DESC', 'FIRST')
8416
+ * .all()
8417
+ * // SQL: ORDER BY "published_at" DESC NULLS FIRST
8418
+ * ```
8419
+ */
8420
+ orderByNulls(column: ColumnName<TTable>, direction: "ASC" | "DESC", nulls: "FIRST" | "LAST"): this;
8290
8421
  limit(count: number): this;
8291
8422
  offset(count: number): this;
8292
8423
  groupBy(...columns: ColumnName<TTable>[]): this;
@@ -8444,6 +8575,20 @@ declare class ConnectedSelectBuilder<TSchema = any, TTable = any, TColumns exten
8444
8575
  * Uses PostgreSQL LATERAL to efficiently fetch related rows with
8445
8576
  * ordering and limiting per parent row.
8446
8577
  *
8578
+ * **Dialect Support:**
8579
+ * | Dialect | Supported | Notes |
8580
+ * |-------------|-----------|-------|
8581
+ * | PostgreSQL | ✅ | Native LATERAL support |
8582
+ * | CockroachDB | ✅ | Native LATERAL support |
8583
+ * | Nile | ✅ | Native LATERAL support |
8584
+ * | AWS DSQL | ❌ | Use separate queries |
8585
+ * | MySQL | ❌ | Use separate queries |
8586
+ * | SQLite | ❌ | Use separate queries |
8587
+ *
8588
+ * @param tableOrAlias - Table name or [tableName, alias] tuple
8589
+ * @param callback - Join condition callback: `(on, left, right) => on.equal(...)`
8590
+ * @throws {RelqConfigError} If dialect doesn't support LATERAL joins
8591
+ *
8447
8592
  * @example
8448
8593
  * ```typescript
8449
8594
  * // Get top 5 orders per user
@@ -8451,7 +8596,7 @@ declare class ConnectedSelectBuilder<TSchema = any, TTable = any, TColumns exten
8451
8596
  * .joinMany('orders', (on, users, orders) =>
8452
8597
  * on.equal(users.id, orders.userId)
8453
8598
  * .where(q => q.equal('status', 'completed'))
8454
- * .orderBy(orders.createdAt, 'DESC')
8599
+ * .orderBy('createdAt', 'DESC')
8455
8600
  * .limit(5)
8456
8601
  * )
8457
8602
  * .all()
@@ -8461,7 +8606,7 @@ declare class ConnectedSelectBuilder<TSchema = any, TTable = any, TColumns exten
8461
8606
  * db.table.users.select()
8462
8607
  * .joinMany(['orders', 'recentOrders'], (on, users, orders) =>
8463
8608
  * on.equal(users.id, orders.userId)
8464
- * .orderBy(orders.createdAt, 'DESC')
8609
+ * .orderBy('createdAt', 'DESC')
8465
8610
  * .limit(3)
8466
8611
  * )
8467
8612
  * // Result: Array<{ ...User, recentOrders: Order[] }>
@@ -8476,6 +8621,20 @@ declare class ConnectedSelectBuilder<TSchema = any, TTable = any, TColumns exten
8476
8621
  * Type-safe one-to-many LEFT JOIN using LATERAL subquery.
8477
8622
  * Returns an array of related rows (empty array if no matches).
8478
8623
  *
8624
+ * **Dialect Support:**
8625
+ * | Dialect | Supported | Notes |
8626
+ * |-------------|-----------|-------|
8627
+ * | PostgreSQL | ✅ | Native LATERAL support |
8628
+ * | CockroachDB | ✅ | Native LATERAL support |
8629
+ * | Nile | ✅ | Native LATERAL support |
8630
+ * | AWS DSQL | ❌ | Use separate queries |
8631
+ * | MySQL | ❌ | Use separate queries |
8632
+ * | SQLite | ❌ | Use separate queries |
8633
+ *
8634
+ * @param tableOrAlias - Table name or [tableName, alias] tuple
8635
+ * @param callback - Join condition callback: `(on, left, right) => on.equal(...)`
8636
+ * @throws {RelqConfigError} If dialect doesn't support LATERAL joins
8637
+ *
8479
8638
  * @example
8480
8639
  * ```typescript
8481
8640
  * // Get all users with their orders (empty array if no orders)
@@ -8496,16 +8655,31 @@ declare class ConnectedSelectBuilder<TSchema = any, TTable = any, TColumns exten
8496
8655
  leftJoinMany<TRightKey extends keyof TSchema & string, TResult>(table: TRightKey, callback: JoinManyCallback<TSchema, TTable, TSchema[TRightKey], TRightKey, TResult>): ConnectedSelectBuilder<TSchema, TTable, TColumns, TJoined & NestedJoinMany<TRightKey, TResult>>;
8497
8656
  distinct(): this;
8498
8657
  /**
8499
- * DISTINCT ON - select unique rows by specified columns
8500
- * PostgreSQL-specific feature
8658
+ * DISTINCT ON - select unique rows by specified columns.
8659
+ * Returns only the first row for each distinct combination of columns.
8660
+ *
8661
+ * **Dialect Support:**
8662
+ * | Dialect | Supported | Notes |
8663
+ * |-------------|-----------|-------|
8664
+ * | PostgreSQL | ✅ | Native support |
8665
+ * | CockroachDB | ✅ | Native support |
8666
+ * | Nile | ✅ | Native support |
8667
+ * | AWS DSQL | ❌ | Use GROUP BY instead |
8668
+ * | MySQL | ❌ | Use GROUP BY instead |
8669
+ * | SQLite | ❌ | Use GROUP BY instead |
8670
+ *
8671
+ * @param columns - Columns to select distinct on (must be first in ORDER BY)
8672
+ * @throws {RelqConfigError} If dialect doesn't support DISTINCT ON
8501
8673
  *
8502
8674
  * @example
8503
8675
  * ```typescript
8676
+ * // Get latest log per user
8504
8677
  * db.table.logs.select()
8505
8678
  * .distinctOn('userId')
8506
8679
  * .orderBy('userId')
8507
8680
  * .orderBy('createdAt', 'DESC')
8508
8681
  * .all()
8682
+ * // SQL: SELECT DISTINCT ON ("user_id") * FROM "logs" ORDER BY "user_id", "created_at" DESC
8509
8683
  * ```
8510
8684
  */
8511
8685
  distinctOn(...columns: string[]): this;
@@ -8538,7 +8712,31 @@ declare class ConnectedSelectBuilder<TSchema = any, TTable = any, TColumns exten
8538
8712
  */
8539
8713
  forUpdate(): this;
8540
8714
  /**
8541
- * FOR UPDATE SKIP LOCKED - skip locked rows (job queue pattern)
8715
+ * FOR UPDATE SKIP LOCKED - skip locked rows (job queue pattern).
8716
+ * Essential for implementing efficient work queues.
8717
+ *
8718
+ * **Dialect Support:**
8719
+ * | Dialect | Supported | Notes |
8720
+ * |-------------|-----------|-------|
8721
+ * | PostgreSQL | ✅ | Native support |
8722
+ * | CockroachDB | ✅ | Native support |
8723
+ * | Nile | ✅ | Native support |
8724
+ * | AWS DSQL | ❌ | Use FOR UPDATE only |
8725
+ * | MySQL | ✅ | Native support (8.0+) |
8726
+ * | SQLite | ❌ | No row locking |
8727
+ *
8728
+ * @throws {RelqConfigError} If dialect doesn't support SKIP LOCKED
8729
+ *
8730
+ * @example
8731
+ * ```typescript
8732
+ * // Job queue pattern - claim next available job
8733
+ * const job = await db.table.jobs.select()
8734
+ * .where(q => q.equal('status', 'pending'))
8735
+ * .orderBy('createdAt')
8736
+ * .limit(1)
8737
+ * .forUpdateSkipLocked()
8738
+ * .get()
8739
+ * ```
8542
8740
  */
8543
8741
  forUpdateSkipLocked(): this;
8544
8742
  /**
@@ -8583,26 +8781,39 @@ declare class ConnectedSelectBuilder<TSchema = any, TTable = any, TColumns exten
8583
8781
  */
8584
8782
  value<T = any>(column: string): Promise<T | null>;
8585
8783
  /**
8586
- * Iterate through query results row-by-row using PostgreSQL cursors
8587
- * Efficient for large datasets as it doesn't load all data into memory
8784
+ * Iterate through query results row-by-row using PostgreSQL cursors.
8785
+ * Efficient for large datasets as it doesn't load all data into memory.
8786
+ *
8787
+ * **Dialect Support:**
8788
+ * | Dialect | Supported | Notes |
8789
+ * |-------------|-----------|-------|
8790
+ * | PostgreSQL | ✅ | Native cursor support |
8791
+ * | CockroachDB | ✅ | Native cursor support |
8792
+ * | Nile | ✅ | Native cursor support |
8793
+ * | AWS DSQL | ❌ | Use pagination() instead |
8794
+ * | MySQL | ❌ | Use pagination() instead |
8795
+ * | SQLite | ❌ | Use pagination() instead |
8588
8796
  *
8589
- * @param callback - Async function called for each row
8797
+ * @param callback - Async function called for each row. Return `false` to stop iteration.
8590
8798
  * @param options - Optional configuration
8591
8799
  * @param options.batchSize - Number of rows to fetch per batch (default: 100)
8800
+ * @throws {RelqConfigError} If dialect doesn't support cursors
8592
8801
  *
8593
8802
  * @example
8594
8803
  * ```typescript
8595
- * await db.table('results')
8596
- * .select(['email', 'userId'])
8804
+ * // Process all unverified users
8805
+ * await db.table.users.select('email', 'userId')
8597
8806
  * .where(q => q.equal('verified', false))
8598
8807
  * .each(async (row) => {
8599
- * console.log(row.email);
8808
+ * await sendVerificationEmail(row.email);
8600
8809
  * });
8601
8810
  *
8602
- * // With custom batch size
8603
- * await db.table('results')
8604
- * .select(['email'])
8605
- * .each(async (row) => { ... }, { batchSize: 50 });
8811
+ * // Stop early with false return
8812
+ * await db.table.logs.select()
8813
+ * .each(async (row, index) => {
8814
+ * if (index >= 1000) return false; // Stop after 1000
8815
+ * processLog(row);
8816
+ * }, { batchSize: 50 });
8606
8817
  * ```
8607
8818
  */
8608
8819
  each<T = InferResultType<TSchema, TTable, TColumns> & TJoined>(callback: (row: T, index: number) => void | false | Promise<void | false>, options?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relq",
3
- "version": "1.0.69",
3
+ "version": "1.0.71",
4
4
  "description": "The Fully-Typed PostgreSQL ORM for TypeScript",
5
5
  "author": "Olajide Mathew O. <olajide.mathew@yuniq.solutions>",
6
6
  "license": "MIT",
@@ -67,7 +67,9 @@
67
67
  },
68
68
  "dependencies": {
69
69
  "@aws-sdk/dsql-signer": "^3.971.0",
70
+ "@clack/prompts": "^1.0.0",
70
71
  "@pgsql/types": "^17.6.2",
72
+ "citty": "^0.2.0",
71
73
  "cli-spinners": "^3.4.0",
72
74
  "jiti": "^2.6.1",
73
75
  "ora": "^9.0.0",