turbine-orm 0.50.0 → 0.51.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.
Files changed (186) hide show
  1. package/README.md +66 -66
  2. package/dist/adapters/cockroachdb.d.ts +5 -5
  3. package/dist/adapters/cockroachdb.js +10 -10
  4. package/dist/adapters/index.d.ts +5 -5
  5. package/dist/adapters/index.js +7 -7
  6. package/dist/adapters/yugabytedb.d.ts +7 -7
  7. package/dist/adapters/yugabytedb.js +10 -10
  8. package/dist/cjs/adapters/cockroachdb.d.ts +5 -5
  9. package/dist/cjs/adapters/cockroachdb.js +10 -10
  10. package/dist/cjs/adapters/index.d.ts +5 -5
  11. package/dist/cjs/adapters/index.js +7 -7
  12. package/dist/cjs/adapters/yugabytedb.d.ts +7 -7
  13. package/dist/cjs/adapters/yugabytedb.js +10 -10
  14. package/dist/cjs/cli/config.d.ts +13 -2
  15. package/dist/cjs/cli/config.js +3 -2
  16. package/dist/cjs/cli/destructive.d.ts +1 -1
  17. package/dist/cjs/cli/destructive.js +1 -1
  18. package/dist/cjs/cli/index.d.ts +10 -10
  19. package/dist/cjs/cli/index.js +49 -45
  20. package/dist/cjs/cli/loader.d.ts +7 -7
  21. package/dist/cjs/cli/loader.js +9 -9
  22. package/dist/cjs/cli/mcp.js +4 -4
  23. package/dist/cjs/cli/migrate.d.ts +5 -5
  24. package/dist/cjs/cli/migrate.js +11 -11
  25. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  26. package/dist/cjs/cli/ui.d.ts +2 -2
  27. package/dist/cjs/cli/ui.js +2 -2
  28. package/dist/cjs/client.d.ts +49 -38
  29. package/dist/cjs/client.js +57 -56
  30. package/dist/cjs/dialect.d.ts +62 -18
  31. package/dist/cjs/dialect.js +40 -2
  32. package/dist/cjs/errors.d.ts +5 -5
  33. package/dist/cjs/errors.js +11 -11
  34. package/dist/cjs/generate.d.ts +6 -6
  35. package/dist/cjs/generate.js +31 -29
  36. package/dist/cjs/index-advisor.d.ts +5 -5
  37. package/dist/cjs/index-advisor.js +0 -0
  38. package/dist/cjs/index.d.ts +1 -1
  39. package/dist/cjs/index.js +7 -7
  40. package/dist/cjs/introspect.d.ts +35 -9
  41. package/dist/cjs/introspect.js +83 -32
  42. package/dist/cjs/mssql.d.ts +11 -11
  43. package/dist/cjs/mssql.js +64 -29
  44. package/dist/cjs/mysql.d.ts +8 -8
  45. package/dist/cjs/mysql.js +61 -23
  46. package/dist/cjs/nested-write.d.ts +21 -2
  47. package/dist/cjs/nested-write.js +51 -14
  48. package/dist/cjs/optional-peer-import.cjs +7 -7
  49. package/dist/cjs/optional-peer-import.d.cts +7 -7
  50. package/dist/cjs/pipeline-submittable.d.ts +2 -2
  51. package/dist/cjs/pipeline-submittable.js +6 -6
  52. package/dist/cjs/pipeline.d.ts +1 -1
  53. package/dist/cjs/pipeline.js +4 -4
  54. package/dist/cjs/powdb-introspect.d.ts +1 -1
  55. package/dist/cjs/powdb-introspect.js +1 -1
  56. package/dist/cjs/powdb.d.ts +28 -28
  57. package/dist/cjs/powdb.js +66 -66
  58. package/dist/cjs/powql.d.ts +27 -27
  59. package/dist/cjs/powql.js +73 -52
  60. package/dist/cjs/query/aggregates.d.ts +1 -1
  61. package/dist/cjs/query/aggregates.js +5 -5
  62. package/dist/cjs/query/batched-loader.d.ts +11 -11
  63. package/dist/cjs/query/batched-loader.js +24 -24
  64. package/dist/cjs/query/builder.d.ts +39 -21
  65. package/dist/cjs/query/builder.js +99 -57
  66. package/dist/cjs/query/compound-unique.d.ts +1 -1
  67. package/dist/cjs/query/compound-unique.js +0 -0
  68. package/dist/cjs/query/deferred.d.ts +12 -6
  69. package/dist/cjs/query/deferred.js +1 -1
  70. package/dist/cjs/query/filters.d.ts +31 -11
  71. package/dist/cjs/query/filters.js +67 -14
  72. package/dist/cjs/query/index.d.ts +1 -1
  73. package/dist/cjs/query/index.js +1 -1
  74. package/dist/cjs/query/relations.d.ts +9 -9
  75. package/dist/cjs/query/relations.js +164 -57
  76. package/dist/cjs/query/types.d.ts +86 -35
  77. package/dist/cjs/query/types.js +1 -1
  78. package/dist/cjs/query/utils.d.ts +27 -10
  79. package/dist/cjs/query/utils.js +86 -14
  80. package/dist/cjs/query/where.d.ts +47 -28
  81. package/dist/cjs/query/where.js +130 -31
  82. package/dist/cjs/query/writes.d.ts +24 -5
  83. package/dist/cjs/query/writes.js +102 -13
  84. package/dist/cjs/realtime.d.ts +7 -7
  85. package/dist/cjs/realtime.js +9 -9
  86. package/dist/cjs/schema-builder.d.ts +18 -7
  87. package/dist/cjs/schema-builder.js +17 -10
  88. package/dist/cjs/schema-metadata.d.ts +3 -3
  89. package/dist/cjs/schema-metadata.js +9 -9
  90. package/dist/cjs/schema-sql.d.ts +9 -9
  91. package/dist/cjs/schema-sql.js +20 -20
  92. package/dist/cjs/schema.d.ts +19 -9
  93. package/dist/cjs/schema.js +6 -6
  94. package/dist/cjs/serverless.d.ts +15 -15
  95. package/dist/cjs/serverless.js +16 -16
  96. package/dist/cjs/sqlite.d.ts +8 -8
  97. package/dist/cjs/sqlite.js +53 -22
  98. package/dist/cjs/typed-sql.d.ts +4 -4
  99. package/dist/cjs/typed-sql.js +5 -5
  100. package/dist/cli/config.d.ts +13 -2
  101. package/dist/cli/config.js +3 -2
  102. package/dist/cli/destructive.d.ts +1 -1
  103. package/dist/cli/destructive.js +1 -1
  104. package/dist/cli/index.d.ts +10 -10
  105. package/dist/cli/index.js +49 -45
  106. package/dist/cli/loader.d.ts +7 -7
  107. package/dist/cli/loader.js +9 -9
  108. package/dist/cli/mcp.js +4 -4
  109. package/dist/cli/migrate.d.ts +5 -5
  110. package/dist/cli/migrate.js +11 -11
  111. package/dist/cli/studio-ui.generated.js +1 -1
  112. package/dist/cli/ui.d.ts +2 -2
  113. package/dist/cli/ui.js +2 -2
  114. package/dist/client.d.ts +49 -38
  115. package/dist/client.js +57 -56
  116. package/dist/dialect.d.ts +62 -18
  117. package/dist/dialect.js +40 -2
  118. package/dist/errors.d.ts +5 -5
  119. package/dist/errors.js +11 -11
  120. package/dist/generate.d.ts +6 -6
  121. package/dist/generate.js +31 -29
  122. package/dist/index-advisor.d.ts +5 -5
  123. package/dist/index-advisor.js +0 -0
  124. package/dist/index.d.ts +1 -1
  125. package/dist/index.js +7 -7
  126. package/dist/introspect.d.ts +35 -9
  127. package/dist/introspect.js +82 -32
  128. package/dist/mssql.d.ts +11 -11
  129. package/dist/mssql.js +64 -29
  130. package/dist/mysql.d.ts +8 -8
  131. package/dist/mysql.js +61 -23
  132. package/dist/nested-write.d.ts +21 -2
  133. package/dist/nested-write.js +51 -14
  134. package/dist/optional-peer-import.cjs +7 -7
  135. package/dist/optional-peer-import.d.cts +7 -7
  136. package/dist/pipeline-submittable.d.ts +2 -2
  137. package/dist/pipeline-submittable.js +6 -6
  138. package/dist/pipeline.d.ts +1 -1
  139. package/dist/pipeline.js +4 -4
  140. package/dist/powdb-introspect.d.ts +1 -1
  141. package/dist/powdb-introspect.js +1 -1
  142. package/dist/powdb.d.ts +28 -28
  143. package/dist/powdb.js +66 -66
  144. package/dist/powql.d.ts +27 -27
  145. package/dist/powql.js +73 -52
  146. package/dist/query/aggregates.d.ts +1 -1
  147. package/dist/query/aggregates.js +5 -5
  148. package/dist/query/batched-loader.d.ts +11 -11
  149. package/dist/query/batched-loader.js +24 -24
  150. package/dist/query/builder.d.ts +39 -21
  151. package/dist/query/builder.js +100 -58
  152. package/dist/query/compound-unique.d.ts +1 -1
  153. package/dist/query/compound-unique.js +0 -0
  154. package/dist/query/deferred.d.ts +12 -6
  155. package/dist/query/deferred.js +1 -1
  156. package/dist/query/filters.d.ts +31 -11
  157. package/dist/query/filters.js +66 -13
  158. package/dist/query/index.d.ts +1 -1
  159. package/dist/query/index.js +1 -1
  160. package/dist/query/relations.d.ts +9 -9
  161. package/dist/query/relations.js +165 -58
  162. package/dist/query/types.d.ts +86 -35
  163. package/dist/query/types.js +1 -1
  164. package/dist/query/utils.d.ts +27 -10
  165. package/dist/query/utils.js +84 -14
  166. package/dist/query/where.d.ts +47 -28
  167. package/dist/query/where.js +129 -32
  168. package/dist/query/writes.d.ts +24 -5
  169. package/dist/query/writes.js +101 -13
  170. package/dist/realtime.d.ts +7 -7
  171. package/dist/realtime.js +9 -9
  172. package/dist/schema-builder.d.ts +18 -7
  173. package/dist/schema-builder.js +17 -10
  174. package/dist/schema-metadata.d.ts +3 -3
  175. package/dist/schema-metadata.js +9 -9
  176. package/dist/schema-sql.d.ts +9 -9
  177. package/dist/schema-sql.js +20 -20
  178. package/dist/schema.d.ts +19 -9
  179. package/dist/schema.js +6 -6
  180. package/dist/serverless.d.ts +15 -15
  181. package/dist/serverless.js +16 -16
  182. package/dist/sqlite.d.ts +8 -8
  183. package/dist/sqlite.js +53 -22
  184. package/dist/typed-sql.d.ts +4 -4
  185. package/dist/typed-sql.js +5 -5
  186. package/package.json +2 -2
package/dist/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * turbine-orm — TurbineClient
2
+ * turbine-orm, TurbineClient
3
3
  *
4
4
  * The main entry point for the Turbine TypeScript SDK.
5
5
  * Manages connection pooling and provides typed table accessors.
@@ -87,12 +87,12 @@ const READ_OPERATIONS = new Set([
87
87
  /**
88
88
  * Internal marker on the config object that tells the `TurbineClient`
89
89
  * constructor to build a lightweight "primary-only" view sharing an existing
90
- * client's primary pool, dialect, query options, and middleware — instead of
90
+ * client's primary pool, dialect, query options, and middleware, instead of
91
91
  * creating a fresh pool. Produced solely by `$primary()`; never public.
92
92
  */
93
93
  const PRIMARY_VIEW = Symbol('turbine.primaryView');
94
94
  // ---------------------------------------------------------------------------
95
- // TransactionClient — provides typed table accessors within a transaction
95
+ // TransactionClient, provides typed table accessors within a transaction
96
96
  // ---------------------------------------------------------------------------
97
97
  /**
98
98
  * A transaction-scoped client that provides the same table accessor API as TurbineClient.
@@ -107,7 +107,7 @@ export class TransactionClient {
107
107
  sourcePool;
108
108
  tableCache = new Map();
109
109
  savepointCounter = 0;
110
- /** Active SQL dialect — owns savepoint keywords and raw-SQL placeholders. */
110
+ /** Active SQL dialect, owns savepoint keywords and raw-SQL placeholders. */
111
111
  dialect;
112
112
  constructor(client, schema, middlewares, queryOptions,
113
113
  /**
@@ -235,14 +235,14 @@ export class TransactionClient {
235
235
  // TurbineClient
236
236
  // ---------------------------------------------------------------------------
237
237
  export class TurbineClient {
238
- /** The underlying pg.Pool — exposed for escape hatches */
238
+ /** The underlying pg.Pool, exposed for escape hatches */
239
239
  pool;
240
240
  /** The schema metadata this client was built from */
241
241
  schema;
242
242
  static int8ParserRegistered = false;
243
243
  static utcTimestampParserRegistered = false;
244
244
  logging;
245
- /** Active SQL dialect — owns transaction keywords, set_config, raw-SQL placeholders, capability flags. */
245
+ /** Active SQL dialect, owns transaction keywords, set_config, raw-SQL placeholders, capability flags. */
246
246
  dialect;
247
247
  tableCache = new Map();
248
248
  middlewares = [];
@@ -251,7 +251,7 @@ export class TurbineClient {
251
251
  errorMessagesSafe;
252
252
  /** True when Turbine created the pool and is responsible for tearing it down */
253
253
  ownsPool = true;
254
- /** Active LISTEN subscriptions — torn down on disconnect() so it never hangs */
254
+ /** Active LISTEN subscriptions, torn down on disconnect() so it never hangs */
255
255
  activeSubscriptions = new Set();
256
256
  /**
257
257
  * Read-replica pools in round-robin order. Empty when no replicas are
@@ -347,7 +347,7 @@ export class TurbineClient {
347
347
  */
348
348
  // Only register the int8 parser when the PRIMARY pool is Turbine-owned.
349
349
  // External pools (Neon HTTP, Vercel Postgres) may ship their own pg-types
350
- // fork and rely on their own parser configuration — registration is
350
+ // fork and rely on their own parser configuration, registration is
351
351
  // process-global, so flipping it because a string replica exists alongside
352
352
  // an external primary would silently change the external primary's parsing
353
353
  // too. String replicas configured next to an external primary therefore
@@ -364,8 +364,8 @@ export class TurbineClient {
364
364
  // Parse `timestamp` (OID 1114) as UTC instead of server-local time. The
365
365
  // pg driver's default hands back a Date built in the process's local zone,
366
366
  // so the same row yields a different instant per deployment region. The
367
- // ORM convention (Prisma, Rails, Django) — and the only interpretation
368
- // that round-trips what Postgres stores — is UTC. Same ownership rule as
367
+ // ORM convention (Prisma, Rails, Django), and the only interpretation
368
+ // that round-trips what Postgres stores, is UTC. Same ownership rule as
369
369
  // the int8 parser: never mutate parser state on external pools.
370
370
  if (ownsAnyPool && config.utcTimestamps !== false && !TurbineClient.utcTimestampParserRegistered) {
371
371
  pg.types.setTypeParser(1114, (val) => new Date(`${val.replace(' ', 'T')}Z`));
@@ -381,6 +381,7 @@ export class TurbineClient {
381
381
  defaultLimit: config.defaultLimit,
382
382
  warnOnUnlimited: config.warnOnUnlimited,
383
383
  utcTimestamps: config.utcTimestamps,
384
+ scopedConnect: config.scopedConnect,
384
385
  relationLoadStrategy: config.relationLoadStrategy,
385
386
  stableRelationOrder: config.stableRelationOrder,
386
387
  implicitPkOrdering: config.implicitPkOrdering,
@@ -412,17 +413,17 @@ export class TurbineClient {
412
413
  }
413
414
  },
414
415
  };
415
- // Apply NotFoundError message redaction mode (default: safe — values are
416
+ // Apply NotFoundError message redaction mode (default: safe, values are
416
417
  // stripped from messages to avoid leaking PII into error logs).
417
418
  if (config.errorMessages) {
418
419
  setErrorMessageMode(config.errorMessages);
419
420
  }
420
421
  if (config.pool) {
421
- // External pool — use directly. Turbine doesn't manage its lifecycle.
422
+ // External pool, use directly. Turbine doesn't manage its lifecycle.
422
423
  this.pool = config.pool;
423
424
  this.ownsPool = false;
424
425
  if (this.logging) {
425
- console.log(`[turbine] Using external pool — ${Object.keys(schema.tables).length} tables`);
426
+ console.log(`[turbine] Using external pool, ${Object.keys(schema.tables).length} tables`);
426
427
  }
427
428
  }
428
429
  else {
@@ -466,7 +467,7 @@ export class TurbineClient {
466
467
  console.error('[turbine] Unexpected pool error:', err.message);
467
468
  });
468
469
  if (this.logging) {
469
- console.log(`[turbine] Pool created — max ${poolConfig.max} connections, ${Object.keys(schema.tables).length} tables`);
470
+ console.log(`[turbine] Pool created, max ${poolConfig.max} connections, ${Object.keys(schema.tables).length} tables`);
470
471
  }
471
472
  }
472
473
  // Build read-replica pools (if any). String entries become owned pg.Pools
@@ -514,14 +515,14 @@ export class TurbineClient {
514
515
  }
515
516
  }
516
517
  // -------------------------------------------------------------------------
517
- // Middleware — intercept all queries
518
+ // Middleware, intercept all queries
518
519
  // -------------------------------------------------------------------------
519
520
  /**
520
521
  * Register a middleware function that runs around every query.
521
522
  *
522
523
  * Middleware can inspect and log query parameters, measure timing, and
523
524
  * transform the result returned by `next()`. Note: query SQL is generated
524
- * BEFORE middleware runs — `params.args` is a read-only snapshot, and
525
+ * BEFORE middleware runs, `params.args` is a read-only snapshot, and
525
526
  * mutating it does NOT change the executed SQL. Cross-cutting filters
526
527
  * (e.g. soft deletes) belong in the query itself: pass an explicit
527
528
  * `where: { deletedAt: null }` or wrap the table accessor in a small helper.
@@ -536,7 +537,7 @@ export class TurbineClient {
536
537
  * return result;
537
538
  * });
538
539
  *
539
- * // Result transformation middleware — redact a field on the way out
540
+ * // Result transformation middleware, redact a field on the way out
540
541
  * db.$use(async (params, next) => {
541
542
  * const result = await next(params);
542
543
  * if (params.model === 'users' && Array.isArray(result)) {
@@ -558,7 +559,7 @@ export class TurbineClient {
558
559
  cache.clear();
559
560
  }
560
561
  // -------------------------------------------------------------------------
561
- // Event emitter — subscribe to query lifecycle events
562
+ // Event emitter, subscribe to query lifecycle events
562
563
  // -------------------------------------------------------------------------
563
564
  $on(_event, listener) {
564
565
  this.queryListeners.add(listener);
@@ -567,7 +568,7 @@ export class TurbineClient {
567
568
  this.queryListeners.delete(listener);
568
569
  }
569
570
  // -------------------------------------------------------------------------
570
- // Observability — automatic metrics collection
571
+ // Observability, automatic metrics collection
571
572
  // -------------------------------------------------------------------------
572
573
  observeEngine;
573
574
  async $observe(config) {
@@ -589,16 +590,16 @@ export class TurbineClient {
589
590
  };
590
591
  }
591
592
  // -------------------------------------------------------------------------
592
- // Table accessor — creates QueryInterface for any table
593
+ // Table accessor, creates QueryInterface for any table
593
594
  // -------------------------------------------------------------------------
594
595
  /**
595
596
  * Get a QueryInterface for a table.
596
- * Results are cached — calling `table('users')` twice returns the same instance.
597
+ * Results are cached, calling `table('users')` twice returns the same instance.
597
598
  *
598
599
  * When read replicas are configured, this returns a thin routing proxy: the
599
600
  * read-only operations in {@link READ_OPERATIONS} are dispatched to a
600
- * round-robin replica-bound QueryInterface (so an entire read — base rows and
601
- * any batched sub-queries — runs against a single consistent replica), while
601
+ * round-robin replica-bound QueryInterface (so an entire read, base rows and
602
+ * any batched sub-queries, runs against a single consistent replica), while
602
603
  * writes and every other member fall through to the primary-bound instance.
603
604
  * With no replicas the original single-pool instance is returned directly.
604
605
  */
@@ -673,7 +674,7 @@ export class TurbineClient {
673
674
  });
674
675
  }
675
676
  /**
676
- * Return a view of this client that pins EVERY operation — reads included —
677
+ * Return a view of this client that pins EVERY operation, reads included -
677
678
  * to the primary pool, bypassing replica routing. Use it to read your own
678
679
  * write without replication lag, or for any read that must see the latest
679
680
  * committed data.
@@ -681,7 +682,7 @@ export class TurbineClient {
681
682
  * The view shares the primary pool, schema, dialect, query options, and
682
683
  * middleware; it owns nothing, so its `disconnect()` is a no-op. When no
683
684
  * replicas are configured this simply returns the client itself (already
684
- * primary-only). The view is cached — repeated calls return the same instance.
685
+ * primary-only). The view is cached, repeated calls return the same instance.
685
686
  *
686
687
  * @example
687
688
  * ```ts
@@ -699,14 +700,14 @@ export class TurbineClient {
699
700
  return this.primaryView;
700
701
  }
701
702
  // -------------------------------------------------------------------------
702
- // Pipeline — batch multiple queries into one round-trip
703
+ // Pipeline, batch multiple queries into one round-trip
703
704
  // -------------------------------------------------------------------------
704
705
  /**
705
706
  * Execute multiple queries in a single database round-trip.
706
707
  *
707
708
  * Two call styles:
708
- * - `db.pipeline(q1, q2, q3)` — rest params (backward-compatible)
709
- * - `db.pipeline([q1, q2, q3], { transactional: false })` — array + options
709
+ * - `db.pipeline(q1, q2, q3)`, rest params (backward-compatible)
710
+ * - `db.pipeline([q1, q2, q3], { transactional: false })`, array + options
710
711
  *
711
712
  * On pg.Pool-backed connections with TCP, this uses the real Postgres
712
713
  * extended-query pipeline protocol (one TCP flush, one round-trip).
@@ -728,7 +729,7 @@ export class TurbineClient {
728
729
  queries = args;
729
730
  }
730
731
  if (this.logging) {
731
- console.log(`[turbine] Pipeline: ${queries.length} queries — ${queries.map((q) => q.tag).join(', ')}`);
732
+ console.log(`[turbine] Pipeline: ${queries.length} queries, ${queries.map((q) => q.tag).join(', ')}`);
732
733
  }
733
734
  return executePipeline(this.pool, queries, options);
734
735
  }
@@ -741,7 +742,7 @@ export class TurbineClient {
741
742
  return pipelineSupported(this.pool);
742
743
  }
743
744
  // -------------------------------------------------------------------------
744
- // Raw SQL — tagged template literal escape hatch
745
+ // Raw SQL, tagged template literal escape hatch
745
746
  // -------------------------------------------------------------------------
746
747
  /**
747
748
  * Execute a raw SQL query with parameter interpolation via tagged templates.
@@ -775,7 +776,7 @@ export class TurbineClient {
775
776
  }
776
777
  }
777
778
  /**
778
- * Execute a **typed** raw SQL query — Turbine's answer to Prisma's TypedSQL.
779
+ * Execute a **typed** raw SQL query, Turbine's answer to Prisma's TypedSQL.
779
780
  *
780
781
  * Like {@link raw}, every interpolated `${value}` becomes a `$N` parameter
781
782
  * (never string-concatenated), so it is injection-safe by construction. The
@@ -783,7 +784,7 @@ export class TurbineClient {
783
784
  * returned {@link TypedSqlQuery} can be `await`ed directly for `T[]`, or
784
785
  * refined with `.one()` (→ `T | null`) or `.scalar<V>()` (→ `V | null`).
785
786
  *
786
- * Rows are returned as-is — no snake→camel mapping (matching `raw()`). Alias
787
+ * Rows are returned as-is, no snake→camel mapping (matching `raw()`). Alias
787
788
  * columns in SQL if you want camelCase keys.
788
789
  *
789
790
  * @example
@@ -809,7 +810,7 @@ export class TurbineClient {
809
810
  return new TypedSqlQuery(this.pool, sql, params, this.logging);
810
811
  }
811
812
  // -------------------------------------------------------------------------
812
- // Transaction support (raw — legacy)
813
+ // Transaction support (raw, legacy)
813
814
  // -------------------------------------------------------------------------
814
815
  /**
815
816
  * Execute a function within a database transaction (raw pg.PoolClient).
@@ -828,7 +829,7 @@ export class TurbineClient {
828
829
  * Only true once BEGIN has actually succeeded. If BEGIN itself throws
829
830
  * (e.g. a single-writer engine's transaction gate times out or rejects a
830
831
  * re-entrant begin), issuing a "best-effort" ROLLBACK would be a stray
831
- * statement from a context that never opened a transaction — on a driver
832
+ * statement from a context that never opened a transaction, on a driver
832
833
  * with one shared engine handle (PowDB embedded) it would roll back a
833
834
  * DIFFERENT caller's open transaction.
834
835
  */
@@ -851,7 +852,7 @@ export class TurbineClient {
851
852
  await client.query(this.dialect.rollbackStatement());
852
853
  }
853
854
  catch {
854
- // Best-effort rollback — the connection may have died mid-query.
855
+ // Best-effort rollback, the connection may have died mid-query.
855
856
  }
856
857
  }
857
858
  throw err;
@@ -883,21 +884,21 @@ export class TurbineClient {
883
884
  client.release(err);
884
885
  }
885
886
  catch {
886
- // pg may throw if the client is already released — swallow.
887
+ // pg may throw if the client is already released, swallow.
887
888
  }
888
889
  };
889
890
  let timedOut = false;
890
891
  /**
891
- * Only true once BEGIN has actually succeeded. If BEGIN itself throws —
892
+ * Only true once BEGIN has actually succeeded. If BEGIN itself throws -
892
893
  * e.g. a single-writer engine's transaction gate times out in its FIFO
893
- * queue or rejects a re-entrant begin (PowDB, E002/E017) — this context
894
+ * queue or rejects a re-entrant begin (PowDB, E002/E017), this context
894
895
  * never opened a transaction, so the catch below must NOT issue its
895
896
  * best-effort ROLLBACK: on a driver with one shared engine handle that
896
897
  * stray ROLLBACK would tear down a DIFFERENT caller's open transaction.
897
898
  */
898
899
  let began = false;
899
900
  try {
900
- // BEGIN with optional isolation level — the dialect owns the keyword and
901
+ // BEGIN with optional isolation level, the dialect owns the keyword and
901
902
  // BEGIN+isolation composition (Postgres appends ` ISOLATION LEVEL …`).
902
903
  const isolationSql = options?.isolationLevel ? ISOLATION_LEVELS[options.isolationLevel] : undefined;
903
904
  await client.query(this.dialect.beginStatement(isolationSql));
@@ -906,15 +907,15 @@ export class TurbineClient {
906
907
  // Order matters: BEGIN -> isolation level (above) -> set_config loop ->
907
908
  // user fn. Any error here propagates to the catch below and rolls back
908
909
  // like any other transaction failure. We use set_config(name, value,
909
- // is_local=true) — the parameterizable, transaction-scoped equivalent of
910
- // SET LOCAL — so both name and value are BOUND params, never interpolated.
910
+ // is_local=true), the parameterizable, transaction-scoped equivalent of
911
+ // SET LOCAL, so both name and value are BOUND params, never interpolated.
911
912
  if (options?.sessionContext) {
912
913
  if (!this.dialect.supportsRLS) {
913
914
  throw new UnsupportedFeatureError('sessionContext (RLS session GUCs)', this.dialect.name, 'set_config-based row-level-security context requires PostgreSQL.');
914
915
  }
915
916
  for (const [name, value] of Object.entries(options.sessionContext)) {
916
917
  if (!GUC_NAME_REGEX.test(name)) {
917
- throw new ValidationError(`[turbine] Invalid session-context GUC name "${name}" — must match ` +
918
+ throw new ValidationError(`[turbine] Invalid session-context GUC name "${name}", must match ` +
918
919
  '/^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)?$/ (optionally namespaced, e.g. "app.current_tenant")');
919
920
  }
920
921
  const cfg = this.dialect.buildSetSessionConfig(name, String(value));
@@ -944,7 +945,7 @@ export class TurbineClient {
944
945
  const runCallback = () => (wrap ? wrap.call(client, () => fn(tx)) : fn(tx));
945
946
  if (timeout) {
946
947
  // Race between the function and a timeout. If the timeout fires we
947
- // need to actually abort the in-flight query — otherwise the backend
948
+ // need to actually abort the in-flight query, otherwise the backend
948
949
  // keeps running until pg's own timeout, holding a pool slot the whole
949
950
  // time. The simplest reliable cancellation is to destroy the
950
951
  // connection: passing a truthy argument to client.release() tells the
@@ -958,7 +959,7 @@ export class TurbineClient {
958
959
  // Destroy the connection to abort the in-flight backend query.
959
960
  // We do this BEFORE rejecting so the socket is gone by the time
960
961
  // the caller's catch block runs.
961
- releaseOnce(new Error('[turbine] Transaction timeout — connection destroyed'));
962
+ releaseOnce(new Error('[turbine] Transaction timeout, connection destroyed'));
962
963
  reject(new TimeoutError(timeout, 'Transaction'));
963
964
  }, timeout);
964
965
  });
@@ -979,11 +980,11 @@ export class TurbineClient {
979
980
  return result;
980
981
  }
981
982
  catch (err) {
982
- // If the timeout fired we already destroyed the connection — issuing a
983
+ // If the timeout fired we already destroyed the connection, issuing a
983
984
  // ROLLBACK on a released client would throw "Client has already been
984
985
  // released". Skip the rollback in that case (the backend rolled back
985
986
  // when its socket was closed). Likewise skip it when BEGIN never
986
- // succeeded (`began` false) — there is no transaction to roll back and
987
+ // succeeded (`began` false), there is no transaction to roll back and
987
988
  // the stray statement could hit another caller's transaction on a
988
989
  // shared-handle engine.
989
990
  if (began && !timedOut && !released) {
@@ -991,7 +992,7 @@ export class TurbineClient {
991
992
  await client.query(this.dialect.rollbackStatement());
992
993
  }
993
994
  catch {
994
- // Best-effort rollback — the connection may have died mid-query.
995
+ // Best-effort rollback, the connection may have died mid-query.
995
996
  }
996
997
  }
997
998
  if (this.logging) {
@@ -1017,7 +1018,7 @@ export class TurbineClient {
1017
1018
  * {@link PgCompatPoolClient.supportsPipelining} (its `query()` accepts
1018
1019
  * concurrent calls and completes them in FIFO submission order), all
1019
1020
  * statements are dispatched in one write burst and the replies are
1020
- * collected in order — ~1 round trip plus server time. Only taken when
1021
+ * collected in order, ~1 round trip plus server time. Only taken when
1021
1022
  * the dialect's writes surface rows directly (`resultStrategy` !==
1022
1023
  * 'reselect'): a reselect plan is itself a sequential write+read pair.
1023
1024
  *
@@ -1087,7 +1088,7 @@ export class TurbineClient {
1087
1088
  return this.$transaction(fn, { sessionContext: context });
1088
1089
  }
1089
1090
  // -------------------------------------------------------------------------
1090
- // LISTEN / NOTIFY — Postgres realtime pub/sub
1091
+ // LISTEN / NOTIFY, Postgres realtime pub/sub
1091
1092
  // -------------------------------------------------------------------------
1092
1093
  /**
1093
1094
  * Subscribe to a Postgres NOTIFY channel. The handler fires with each
@@ -1102,12 +1103,12 @@ export class TurbineClient {
1102
1103
  *
1103
1104
  * The channel name CANNOT be a bound parameter (`LISTEN $1` is a syntax
1104
1105
  * error), so it is validated against a strict identifier regex AND quoted via
1105
- * `quoteIdent` before interpolation — it is the only identifier this method
1106
+ * `quoteIdent` before interpolation, it is the only identifier this method
1106
1107
  * places into SQL text.
1107
1108
  *
1108
1109
  * **Serverless caveat:** LISTEN needs a persistent connection that can push
1109
1110
  * async notifications. Stateless HTTP drivers (Neon HTTP, Vercel Postgres)
1110
- * cannot do this — `$listen` throws a `ConnectionError` rather than hang.
1111
+ * cannot do this, `$listen` throws a `ConnectionError` rather than hang.
1111
1112
  * `$notify` works on every driver.
1112
1113
  *
1113
1114
  * @example
@@ -1138,7 +1139,7 @@ export class TurbineClient {
1138
1139
  /**
1139
1140
  * Send a Postgres NOTIFY on `channel` with an optional payload string.
1140
1141
  *
1141
- * Issued as `SELECT pg_notify($1, $2)` — both the channel and payload are
1142
+ * Issued as `SELECT pg_notify($1, $2)`, both the channel and payload are
1142
1143
  * BOUND parameters (no quoting/injection concern). The channel is still
1143
1144
  * validated against the identifier regex for parity with `$listen` and to
1144
1145
  * catch typos loudly. Works on every driver, including serverless HTTP pools.
@@ -1164,7 +1165,7 @@ export class TurbineClient {
1164
1165
  }
1165
1166
  }
1166
1167
  // -------------------------------------------------------------------------
1167
- // Retry — automatic retry for retryable errors (deadlock, serialization)
1168
+ // Retry, automatic retry for retryable errors (deadlock, serialization)
1168
1169
  // -------------------------------------------------------------------------
1169
1170
  /**
1170
1171
  * Execute an async function with automatic retry on retryable errors.
@@ -1207,7 +1208,7 @@ export class TurbineClient {
1207
1208
  * Gracefully shut down the connection pool.
1208
1209
  *
1209
1210
  * If Turbine was given an external pool via `TurbineConfig.pool`, this
1210
- * method is a no-op — the caller is responsible for the pool's lifecycle.
1211
+ * method is a no-op, the caller is responsible for the pool's lifecycle.
1211
1212
  */
1212
1213
  async disconnect() {
1213
1214
  // Tear down any live LISTEN subscriptions first. Each holds a dedicated
@@ -1226,7 +1227,7 @@ export class TurbineClient {
1226
1227
  this.activeSubscriptions.clear();
1227
1228
  }
1228
1229
  // Close owned (string-configured) replica pools regardless of whether the
1229
- // primary is owned — external replica pools are left untouched (caller owns
1230
+ // primary is owned, external replica pools are left untouched (caller owns
1230
1231
  // their lifecycle), same contract as an external primary.
1231
1232
  for (const replicaPool of this.ownedReplicaPools) {
1232
1233
  try {
@@ -1240,7 +1241,7 @@ export class TurbineClient {
1240
1241
  }
1241
1242
  if (!this.ownsPool) {
1242
1243
  if (this.logging) {
1243
- console.log('[turbine] disconnect() skipped — external primary pool is not owned by Turbine');
1244
+ console.log('[turbine] disconnect() skipped, external primary pool is not owned by Turbine');
1244
1245
  }
1245
1246
  return;
1246
1247
  }
package/dist/dialect.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * turbine-orm — SQL dialect contract
2
+ * turbine-orm, SQL dialect contract
3
3
  *
4
4
  * Phase-1 seam for future database packages. The current package remains
5
5
  * PostgreSQL-native by default, but query generation now depends on this
@@ -40,6 +40,28 @@ export interface BulkInsertStatementInput {
40
40
  skipDuplicates?: boolean;
41
41
  /** Optional SQL-ready RETURNING selection. */
42
42
  returning?: ReturningSelection;
43
+ /**
44
+ * Force the row-major `VALUES (…), (…)` form even on a dialect that would
45
+ * otherwise batch column-major.
46
+ *
47
+ * Set when any target column is ARRAY-typed: PostgreSQL's `UNNEST` transpose
48
+ * flattens a nested array, so an array-valued column cannot survive it. The
49
+ * flag costs one placeholder per cell rather than per column, which is why
50
+ * it is opt-in rather than the default. Dialects that already emit `VALUES`
51
+ * ignore it.
52
+ */
53
+ requireRowValues?: boolean;
54
+ }
55
+ /**
56
+ * The cast/decode pair for one JSON-divergent column type. See
57
+ * {@link Dialect.jsonWireRule}. The two halves MUST agree: a cast with no
58
+ * matching decode silently returns text where a value is expected.
59
+ */
60
+ export interface JsonWireRule {
61
+ /** The SQL expression to place in the JSON builder, given the column reference. */
62
+ sql(columnRef: string): string;
63
+ /** Turn the JSON-carried value back into the driver's representation. */
64
+ decode(value: unknown): unknown;
43
65
  }
44
66
  export interface BuiltStatement {
45
67
  sql: string;
@@ -58,7 +80,7 @@ export interface UpsertStatementInput {
58
80
  updateSetClauses: string[];
59
81
  /**
60
82
  * Optional SQL-ready predicate (no `WHERE` keyword) restricting the
61
- * conflict-UPDATE to matching rows — used by global filters (soft-delete /
83
+ * conflict-UPDATE to matching rows, used by global filters (soft-delete /
62
84
  * multi-tenancy) so an upsert never resurrects/steals a row outside the
63
85
  * filter. Only honored by dialects that set
64
86
  * {@link Dialect.supportsUpsertUpdateWhere}; others must never receive it.
@@ -107,13 +129,13 @@ export interface CreateIndexStatementInput {
107
129
  /**
108
130
  * How a dialect surfaces the row(s) produced by an INSERT/UPDATE/DELETE/upsert.
109
131
  *
110
- * - `'returning'` — a trailing `RETURNING *` clause returns the affected rows
132
+ * - `'returning'`, a trailing `RETURNING *` clause returns the affected rows
111
133
  * in the same statement (PostgreSQL, SQLite ≥ 3.35). The executor reads them
112
134
  * directly from the statement result.
113
- * - `'output'` — the statement itself emits the rows in a non-RETURNING shape
135
+ * - `'output'`, the statement itself emits the rows in a non-RETURNING shape
114
136
  * (SQL Server `OUTPUT INSERTED.*`). Executed exactly like `'returning'`: the
115
137
  * rows come back on the statement result.
116
- * - `'reselect'` — the engine cannot return rows from a write (MySQL). The
138
+ * - `'reselect'`, the engine cannot return rows from a write (MySQL). The
117
139
  * executor runs the write, then issues a follow-up `SELECT` (by primary
118
140
  * key / unique / where predicate) to fetch the affected row(s). The build
119
141
  * method supplies the {@link DeferredQuery} `reselect` plan that owns the
@@ -144,7 +166,7 @@ export interface OpenStreamOptions {
144
166
  ambientTransaction?: boolean;
145
167
  }
146
168
  /**
147
- * Inputs for {@link Dialect.buildUpdateStatement} — full UPDATE assembly. Used by
169
+ * Inputs for {@link Dialect.buildUpdateStatement}, full UPDATE assembly. Used by
148
170
  * engines whose returning shape is injected MID-statement rather than as a trailing
149
171
  * clause (SQL Server `OUTPUT INSERTED.*` lands between `SET …` and `WHERE …`).
150
172
  */
@@ -159,7 +181,7 @@ export interface UpdateStatementInput {
159
181
  returning?: ReturningSelection;
160
182
  }
161
183
  /**
162
- * Inputs for {@link Dialect.buildDeleteStatement} — full DELETE assembly. SQL Server
184
+ * Inputs for {@link Dialect.buildDeleteStatement}, full DELETE assembly. SQL Server
163
185
  * injects `OUTPUT DELETED.*` between `DELETE FROM <t>` and `WHERE …`.
164
186
  */
165
187
  export interface DeleteStatementInput {
@@ -171,10 +193,10 @@ export interface DeleteStatementInput {
171
193
  returning?: ReturningSelection;
172
194
  }
173
195
  /**
174
- * Inputs for {@link Dialect.buildLimitOffset} — the trailing pagination clause of an
196
+ * Inputs for {@link Dialect.buildLimitOffset}, the trailing pagination clause of an
175
197
  * outer SELECT. PostgreSQL/MySQL/SQLite use `LIMIT x [OFFSET y]`; SQL Server has no
176
198
  * `LIMIT` and uses `[ORDER BY …] OFFSET y ROWS [FETCH NEXT x ROWS ONLY]` (which
177
- * requires an ORDER BY — a stable default is injected when {@link hasOrderBy} is false).
199
+ * requires an ORDER BY, a stable default is injected when {@link hasOrderBy} is false).
178
200
  */
179
201
  export interface LimitOffsetInput {
180
202
  /** SQL-ready placeholder/literal for the row LIMIT, or undefined for no limit. */
@@ -190,13 +212,13 @@ export interface LimitOffsetInput {
190
212
  * `json_agg(json_build_object(...))` (SQL Server's `FOR JSON PATH` expresses the
191
213
  * object shape through the child SELECT's column ALIASES rather than an explicit
192
214
  * `JSON_OBJECT`, so it cannot be assembled from {@link Dialect.buildJsonObject} /
193
- * {@link Dialect.buildJsonArrayAgg} primitives — see {@link Dialect.buildRelationSubquery}).
215
+ * {@link Dialect.buildJsonArrayAgg} primitives, see {@link Dialect.buildRelationSubquery}).
194
216
  *
195
217
  * The query builder pre-resolves the parts that are engine-independent (alias,
196
218
  * select/omit-resolved columns, recursion + param threading) and hands them to the
197
219
  * dialect. **Param-push ordering contract:** the override MUST push values to
198
220
  * {@link params} in the same order the builder's collect path expects so the SQL
199
- * cache and pipeline batching stay in sync —
221
+ * cache and pipeline batching stay in sync -
200
222
  * - to-many with `limit`/`orderBy` (the "wrap" path) and manyToMany:
201
223
  * `buildWhere(...)` → push `limit` → `recurse(...)` for each nested relation;
202
224
  * - everything else (to-one, unordered/unlimited to-many): `recurse(...)` for each
@@ -207,7 +229,7 @@ export interface RelationSubqueryContext {
207
229
  relDef: RelationDef;
208
230
  /** The `with` spec for this relation: `true`, or a {@link WithOptions} object. */
209
231
  spec: true | WithOptions;
210
- /** Shared parameter array — push BOUND values here in the order described above. */
232
+ /** Shared parameter array, push BOUND values here in the order described above. */
211
233
  params: unknown[];
212
234
  /** Parent alias or table name (RAW identifier, not quoted) to correlate against. */
213
235
  parentRef: string;
@@ -258,12 +280,12 @@ export interface Dialect {
258
280
  /** Build a JSON object expression from output keys and SQL expressions. */
259
281
  buildJsonObject(pairs: [key: string, expr: string][]): string;
260
282
  /**
261
- * Build a positional JSON ARRAY expression from ordered SQL expressions —
283
+ * Build a positional JSON ARRAY expression from ordered SQL expressions -
262
284
  * the key-less counterpart to {@link buildJsonObject} used by the opt-in
263
285
  * `jsonEncoding: 'positional'` mode. Emitting `json_build_array(v1, v2, …)`
264
286
  * instead of `json_build_object('k1', v1, …)` drops every repeated key name
265
287
  * from every nested object of every row (the decode side maps positions back
266
- * to keys via a build-time shape descriptor). Postgres-only in v1 — other
288
+ * to keys via a build-time shape descriptor). Postgres-only in v1, other
267
289
  * engines never reach this because the builder gates positional encoding to
268
290
  * `dialect.name === 'postgresql'`, so the method is optional on the contract.
269
291
  */
@@ -303,7 +325,7 @@ export interface Dialect {
303
325
  * (a predicate on the conflict-UPDATE, e.g. Postgres `ON CONFLICT … DO UPDATE
304
326
  * SET … WHERE …`). Global filters only push a conflict-UPDATE predicate when
305
327
  * this is true, so engines whose upsert cannot express one (MySQL
306
- * `ON DUPLICATE KEY UPDATE`) never receive an orphaned parameter. Optional —
328
+ * `ON DUPLICATE KEY UPDATE`) never receive an orphaned parameter. Optional -
307
329
  * absent is treated as `false`.
308
330
  */
309
331
  readonly supportsUpsertUpdateWhere?: boolean;
@@ -374,6 +396,28 @@ export interface Dialect {
374
396
  buildJsonPathExtract(column: string, pathParamRef: string): string;
375
397
  /** Build a correlation clause across single or composite keys. */
376
398
  buildCorrelation(leftRef: string, leftColumns: string | string[], rightRef: string, rightColumns: string | string[]): string;
399
+ /**
400
+ * How a column whose JSON rendering DIVERGES from the driver's own value
401
+ * should be carried through a `with` subquery, or `undefined` when the
402
+ * column's JSON form already matches.
403
+ *
404
+ * A nested-relation subquery builds JSON (`json_build_object`,
405
+ * `json_group_array`, …) and the JSON number grammar is not the wire
406
+ * grammar: PostgreSQL renders `numeric` as a JSON number, SQLite renders a
407
+ * 64-bit INTEGER as one, and a JSON number is an IEEE double. So the same
408
+ * column read at top level and read through a relation came back as
409
+ * different values, LOSSILY past 2^53, and because `auto` picks between the
410
+ * join and batched strategies by row count, the same query could return
411
+ * either. The fix is to carry the value as text and decode it back with the
412
+ * engine's own rule, which is what this hook pairs up: `sql` must emit an
413
+ * expression the JSON builder can hold, and `decode` must turn that back
414
+ * into exactly what the driver would have produced.
415
+ *
416
+ * Omitting the hook keeps a dialect on the old behavior (no cast, no
417
+ * decode), which is correct for any engine whose JSON rendering agrees with
418
+ * its wire format.
419
+ */
420
+ jsonWireRule?(columnType: string): JsonWireRule | undefined;
377
421
  /** Optional type mapping hook for code generation/introspection. */
378
422
  typeToTypeScript?(dialectType: string, nullable: boolean): string;
379
423
  /**
@@ -469,7 +513,7 @@ export interface Dialect {
469
513
  buildLimitOffset?(input: LimitOffsetInput): string;
470
514
  /**
471
515
  * Assemble a full UPDATE statement. Optional: omitted by engines whose returning
472
- * shape is a trailing clause (PG/SQLite `RETURNING`, MySQL none) — the builder
516
+ * shape is a trailing clause (PG/SQLite `RETURNING`, MySQL none), the builder
473
517
  * falls back to `UPDATE <t> SET <set> <where><returningClause>`. SQL Server
474
518
  * implements this to inject `OUTPUT INSERTED.*` between `SET …` and `WHERE …`.
475
519
  */
@@ -483,7 +527,7 @@ export interface Dialect {
483
527
  * OVERRIDE nested-relation subquery generation entirely. Optional: when a dialect
484
528
  * omits it, the builder uses its native `json_agg(json_build_object(...))` path
485
529
  * (PG) routed through {@link buildJsonObject} / {@link buildJsonArrayAgg} /
486
- * {@link wrapJsonSubresult} — so MySQL and SQLite, which only swap those
530
+ * {@link wrapJsonSubresult}, so MySQL and SQLite, which only swap those
487
531
  * primitives, produce identical output and never define this hook. SQL Server
488
532
  * defines it to emit `FOR JSON PATH` correlated subqueries (its JSON aggregate
489
533
  * is expressed through the child SELECT's column aliases, which does not map onto
@@ -503,7 +547,7 @@ export interface IntrospectOptions {
503
547
  }
504
548
  /**
505
549
  * @deprecated Migration locking is owned by the {@link DatabaseAdapter} seam
506
- * (`src/adapters/index.ts` — `acquireLock`/`releaseLock`/`statementTimeout`),
550
+ * (`src/adapters/index.ts`, `acquireLock`/`releaseLock`/`statementTimeout`),
507
551
  * which is the single canonical locking seam. This interface was never
508
552
  * implemented or wired and is retained only for type back-compat; new engines
509
553
  * MUST provide a `DatabaseAdapter` instead. Will be removed in a future major.