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/cjs/powql.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  /**
3
- * PowqlInterface — Turbine's PowQL query generator (the PowDB analogue of
3
+ * PowqlInterface, Turbine's PowQL query generator (the PowDB analogue of
4
4
  * {@link QueryInterface}). It exposes the same public method surface as the SQL
5
- * `QueryInterface` (`findMany`, `create`, `update`, …) but emits **PowQL** — a
6
- * pipeline language, not SQL — executed through {@link PowdbPool}.
5
+ * `QueryInterface` (`findMany`, `create`, `update`, …) but emits **PowQL**, a
6
+ * pipeline language, not SQL, executed through {@link PowdbPool}.
7
7
  *
8
8
  * It is a *parallel* implementation rather than a `Dialect` of the SQL builder:
9
9
  * PowQL's grammar (`T filter <e> order <k> { .col }`) shares no surface with
@@ -15,14 +15,14 @@
15
15
  * server):
16
16
  * - `create`/`createMany`/`update`/`delete` use PowDB 0.7.0's trailing
17
17
  * `returning` keyword (`RETURNING *`, all columns) to surface affected rows
18
- * in one round-trip. `upsert` is the lone exception — its statement does not
18
+ * in one round-trip. `upsert` is the lone exception, its statement does not
19
19
  * accept `returning`, so it reselects the row by PK (a composite-PK upsert
20
20
  * reselects-or-writes inside one flat transaction).
21
21
  * - The PK is server-assigned when the column is `isGenerated` (PowDB's `auto`
22
- * int — read back via `returning`); otherwise a defaulted **string** PK is
22
+ * int, read back via `returning`); otherwise a defaulted **string** PK is
23
23
  * generated client-side (UUID).
24
- * - `with` (nested relations) uses **batched N+1 loaders** — D round-trips for
25
- * depth D, not one query — including manyToMany (junction → targets).
24
+ * - `with` (nested relations) uses **batched N+1 loaders**, D round-trips for
25
+ * depth D, not one query, including manyToMany (junction → targets).
26
26
  * - **Relation filters** (`some`/`none`/`every`, all cardinalities incl. m2m)
27
27
  * are resolved client-side to a literal `in (…)` list, never an IN-subquery:
28
28
  * PowDB's executor caches a subquery's result by plan shape and would return
@@ -31,7 +31,7 @@
31
31
  * shared nested-write engine as one flat top-level transaction (PowDB is
32
32
  * single-writer, no savepoints).
33
33
  * - pgvector / JSON / array filters and cursor streaming throw
34
- * {@link UnsupportedFeatureError} (E017) — they have no PowDB equivalent.
34
+ * {@link UnsupportedFeatureError} (E017), they have no PowDB equivalent.
35
35
  *
36
36
  * @module
37
37
  */
@@ -78,6 +78,7 @@ const aggregates_js_1 = require("./query/aggregates.js");
78
78
  const compound_unique_js_1 = require("./query/compound-unique.js");
79
79
  const filters_js_1 = require("./query/filters.js");
80
80
  const utils_js_1 = require("./query/utils.js");
81
+ const where_js_1 = require("./query/where.js");
81
82
  const schema_js_1 = require("./schema.js");
82
83
  /**
83
84
  * Max parent keys per relation-loader `in (…)` query. A `with` over a large
@@ -124,7 +125,7 @@ const POWQL_WRITE_ACTIONS = new Set([
124
125
  * relation before compiling to it. Keyed on the pool object identity so every
125
126
  * table interface over the same connection shares one snapshot; a WeakMap lets a
126
127
  * discarded pool's snapshot be collected. A fetch failure caches `[]` (a missing
127
- * listing means "no verifiable links" — a silent fallback to loaders, never an
128
+ * listing means "no verifiable links", a silent fallback to loaders, never an
128
129
  * error).
129
130
  */
130
131
  const LINK_SNAPSHOT_CACHE = new WeakMap();
@@ -263,8 +264,8 @@ class PowqlInterface {
263
264
  return `$${params.length}`;
264
265
  }
265
266
  /**
266
- * Render a value for a write *assignment* (`col := …`). Every value — float
267
- * columns included — is sent as a positional `$N` param. PowDB ≥ 0.7.0 fixed
267
+ * Render a value for a write *assignment* (`col := …`). Every value, float
268
+ * columns included, is sent as a positional `$N` param. PowDB ≥ 0.7.0 fixed
268
269
  * the int→float UPDATE coercion bug (`score := $n` with an integer param now
269
270
  * reads back the integer value, not the raw i64 bits), so the float-literal
270
271
  * inlining workaround Turbine carried for ≤ 0.6.2 is gone. Marks the column as
@@ -295,7 +296,7 @@ class PowqlInterface {
295
296
  get capabilities() {
296
297
  return this.pool.capabilities ?? powdb_js_1.ALL_POWDB_CAPABILITIES;
297
298
  }
298
- /** A predicate that is always false — the empty-`in` / contradiction sentinel. */
299
+ /** A predicate that is always false, the empty-`in` / contradiction sentinel. */
299
300
  alwaysFalse() {
300
301
  const pk = this.meta.primaryKey[0] ?? this.meta.columns[0]?.name;
301
302
  return `(.${pk} is null and .${pk} is not null)`;
@@ -338,7 +339,7 @@ class PowqlInterface {
338
339
  }
339
340
  else if (this.meta.relations[key]) {
340
341
  // Relation filters are pre-resolved to scalar in/notIn by
341
- // resolveRelationFilters() before buildWhere runs — reaching here means
342
+ // resolveRelationFilters() before buildWhere runs, reaching here means
342
343
  // a caller skipped that step (an internal bug, not user error).
343
344
  throw new errors_js_1.ValidationError(`[turbine] internal: relation filter "${key}" reached buildWhere unresolved (missing resolveRelationFilters()).`);
344
345
  }
@@ -372,7 +373,7 @@ class PowqlInterface {
372
373
  }
373
374
  rejectUnsupportedFilter(op, field);
374
375
  if (!Object.keys(op).some((k) => OPERATOR_KEYS.has(k))) {
375
- // A bare object that is not an operator set — equality by value.
376
+ // A bare object that is not an operator set, equality by value.
376
377
  return `${ref} = ${this.param(value, params)}`;
377
378
  }
378
379
  const insensitive = op.mode === 'insensitive';
@@ -470,6 +471,10 @@ class PowqlInterface {
470
471
  * by the empty-where guard.
471
472
  */
472
473
  buildJsonPathCondition(col, filter, params, alias) {
474
+ // Same strict-key check as the SQL path. Without it an unrecognized
475
+ // operator compiled to zero conditions and the predicate silently
476
+ // disappeared, returning every row (see assertJsonFilterKeys).
477
+ (0, where_js_1.assertJsonFilterKeys)(filter, col.name);
473
478
  const conds = [];
474
479
  // Bind the path segments at most once and reuse the expression string across
475
480
  // equals + range comparisons (they share the same `path`).
@@ -515,8 +520,20 @@ class PowqlInterface {
515
520
  }
516
521
  conds.push(`${pathP()} ${powOp} ${this.param(v, params)}`);
517
522
  }
518
- if (!conds.length)
519
- return '';
523
+ // Substring comparisons against the text at `path`, in the same fixed
524
+ // order as the SQL path. PowQL has `like`, so these compile natively.
525
+ for (const { pattern, value } of (0, where_js_1.jsonStringEntries)(filter, col.name)) {
526
+ const insensitive = filter.mode === 'insensitive';
527
+ const lhs = insensitive ? `lower(${pathP()})` : pathP();
528
+ conds.push(`${lhs} like ${this.bindLike(pattern((0, utils_js_1.escapeLike)(value)), params, insensitive)}`);
529
+ }
530
+ // Every reachable shape now emits a condition or throws: assertJsonFilterKeys
531
+ // rejects a filter that compares nothing, so an empty list here would be a
532
+ // generator bug rather than a user error, and must not silently widen the
533
+ // query the way it used to.
534
+ if (!conds.length) {
535
+ throw new errors_js_1.ValidationError(`[turbine] internal: JSON filter on "${col.name}" compiled to no condition. This is a bug in turbine-orm.`);
536
+ }
520
537
  return conds.length > 1 ? `(${conds.join(' and ')})` : conds[0];
521
538
  }
522
539
  /** Bind a value, lowercasing for case-insensitive comparisons. */
@@ -529,7 +546,7 @@ class PowqlInterface {
529
546
  const ph = this.param(pattern, params);
530
547
  return insensitive ? `lower(${ph})` : ph;
531
548
  }
532
- /** `lhs [not] in ($1, $2, …)` — empty list collapses to a constant. */
549
+ /** `lhs [not] in ($1, $2, …)`, empty list collapses to a constant. */
533
550
  buildInList(lhs, values, params, insensitive, negate) {
534
551
  if (!Array.isArray(values) || values.length === 0) {
535
552
  // `in []` matches nothing; `not in []` matches everything (SQL parity requires a
@@ -556,7 +573,7 @@ class PowqlInterface {
556
573
  * so a second subquery of the same shape with a different value returns the
557
574
  * first one's stale rows (reproduced live on the embedded engine; the
558
575
  * single-statement literal `in (list)` form is always correct). Resolving
559
- * client-side trades extra round-trips for correctness, and recurses — nested
576
+ * client-side trades extra round-trips for correctness, and recurses, nested
560
577
  * relation filters in the inner predicate resolve when the target query runs.
561
578
  */
562
579
  async resolveRelationFilters(where, timeout) {
@@ -598,7 +615,7 @@ class PowqlInterface {
598
615
  const fk = (0, schema_js_1.normalizeKeyColumns)(rel.foreignKey);
599
616
  const rk = (0, schema_js_1.normalizeKeyColumns)(rel.referenceKey);
600
617
  if (fk.length > 1 || rk.length > 1) {
601
- throw new errors_js_1.UnsupportedFeatureError('composite-key relation filters', 'PowDB', `relation "${rel.name}" uses a composite key — PowQL has no tuple-\`in\` to express it`);
618
+ throw new errors_js_1.UnsupportedFeatureError('composite-key relation filters', 'PowDB', `relation "${rel.name}" uses a composite key, PowQL has no tuple-\`in\` to express it`);
602
619
  }
603
620
  const targetMeta = this.schema.tables[rel.to];
604
621
  if (!targetMeta)
@@ -639,7 +656,7 @@ class PowqlInterface {
639
656
  if (!targetMeta)
640
657
  throw new errors_js_1.ValidationError(`[turbine] Relation "${rel.name}" targets unknown table "${rel.to}".`);
641
658
  if (sourceJ.length > 1 || targetJ.length > 1 || sourceRef.length > 1 || targetMeta.primaryKey.length > 1) {
642
- throw new errors_js_1.UnsupportedFeatureError('composite-key manyToMany filters', 'PowDB', `relation "${rel.name}" — PowQL has no tuple-\`in\` for composite junction/target keys`);
659
+ throw new errors_js_1.UnsupportedFeatureError('composite-key manyToMany filters', 'PowDB', `relation "${rel.name}", PowQL has no tuple-\`in\` for composite junction/target keys`);
643
660
  }
644
661
  const sourceJCol = sourceJ[0];
645
662
  const targetJCol = targetJ[0];
@@ -657,7 +674,7 @@ class PowqlInterface {
657
674
  });
658
675
  return [...new Set(rows.map((r) => r[targetPkField]).filter((v) => v != null))];
659
676
  };
660
- // Junction source keys linking any of `targetPks` (literal IN-list — never a subquery).
677
+ // Junction source keys linking any of `targetPks` (literal IN-list, never a subquery).
661
678
  const sourcesForTargets = async (targetPks) => {
662
679
  if (!targetPks.length)
663
680
  return [];
@@ -805,7 +822,7 @@ class PowqlInterface {
805
822
  }
806
823
  return `${this.ref(field, alias)} ${spec.sort === 'desc' ? 'desc' : 'asc'}`;
807
824
  }
808
- // Name the actual feature in the refusal — a pick-row ordering
825
+ // Name the actual feature in the refusal, a pick-row ordering
809
826
  // reported as "vector / distance ordering" sends users hunting for
810
827
  // pgvector docs. Everything else stays E017 on PowDB.
811
828
  const feature = (0, filters_js_1.isRelationPickOrderBy)(dir)
@@ -987,8 +1004,8 @@ class PowqlInterface {
987
1004
  *
988
1005
  * When the engine supports nested projections (>= 0.18) and the strategy
989
1006
  * does not opt out, eligible `with` relations compile INTO this statement as
990
- * nested-projection blocks (`nestedPlans`) — one round-trip for the whole
991
- * shape — and only the ineligible remainder (`residualWith`) goes to the
1007
+ * nested-projection blocks (`nestedPlans`), one round-trip for the whole
1008
+ * shape, and only the ineligible remainder (`residualWith`) goes to the
992
1009
  * post-execution loaders. Without nesting the emitted PowQL is byte-identical
993
1010
  * to the pre-0.18 output (no alias, `.col` refs).
994
1011
  */
@@ -1146,7 +1163,7 @@ class PowqlInterface {
1146
1163
  return row;
1147
1164
  }
1148
1165
  // -------------------------------------------------------------------------
1149
- // Nested relations — batched N+1 loaders (hasMany / hasOne / belongsTo)
1166
+ // Nested relations, batched N+1 loaders (hasMany / hasOne / belongsTo)
1150
1167
  // -------------------------------------------------------------------------
1151
1168
  /**
1152
1169
  * Load each requested relation for `parents` and attach it onto each row.
@@ -1278,7 +1295,7 @@ class PowqlInterface {
1278
1295
  }
1279
1296
  }
1280
1297
  /**
1281
- * manyToMany nested read — a three-hop batched loader (no `json_agg`/join
1298
+ * manyToMany nested read, a three-hop batched loader (no `json_agg`/join
1282
1299
  * pushdown): (1) read the junction rows for all parents in `sourceKey in (…)`
1283
1300
  * chunks, (2) read the target rows for the collected `targetKey`s, (3) stitch
1284
1301
  * each parent → its junction rows → its targets in memory. Mirrors the
@@ -1296,7 +1313,7 @@ class PowqlInterface {
1296
1313
  if (!targetMeta)
1297
1314
  throw new errors_js_1.ValidationError(`[turbine] Relation "${relName}" targets unknown table "${rel.to}".`);
1298
1315
  if (sourceJ.length > 1 || targetJ.length > 1 || sourceRef.length > 1 || targetMeta.primaryKey.length > 1) {
1299
- throw new errors_js_1.UnsupportedFeatureError('composite-key manyToMany', 'PowDB', `relation "${relName}" — PowQL has no tuple-\`in\`, so composite junction/target keys can't be loaded`);
1316
+ throw new errors_js_1.UnsupportedFeatureError('composite-key manyToMany', 'PowDB', `relation "${relName}", PowQL has no tuple-\`in\`, so composite junction/target keys can't be loaded`);
1300
1317
  }
1301
1318
  const sourceJCol = sourceJ[0];
1302
1319
  const targetJCol = targetJ[0];
@@ -1742,7 +1759,7 @@ class PowqlInterface {
1742
1759
  /**
1743
1760
  * Shape the nested JSON children back into typed entities on every parent
1744
1761
  * row. The nested field arrives as a decoded JSON array on the native wire
1745
- * (or JSON text on the legacy wire — parsed here); its values are real JSON
1762
+ * (or JSON text on the legacy wire, parsed here); its values are real JSON
1746
1763
  * types, so each child object goes through the NATIVE coercion policy
1747
1764
  * (`rowToEntity(…, true)`: a date column's micros number becomes a `Date`, a
1748
1765
  * json column's document passes through, a str `"null"` stays a string).
@@ -1839,7 +1856,7 @@ class PowqlInterface {
1839
1856
  * must stay on the loaders (ALWAYS a silent fallback with identical output).
1840
1857
  *
1841
1858
  * SCOPED TIGHT: this fires ONLY for a to-one relation whose child projection
1842
- * includes a bigint/bytes column — exactly the case a JSON nested block cannot
1859
+ * includes a bigint/bytes column, exactly the case a JSON nested block cannot
1843
1860
  * carry, so nested projections have already fallen back to a per-relation loader
1844
1861
  * (`planNestedRelation` returned `null` for the same shape). Cases nested
1845
1862
  * projections DO serve keep nested projections: link-bearing statements are
@@ -1847,9 +1864,9 @@ class PowqlInterface {
1847
1864
  * link path would regress a hot path for no gain. Requires: single-column
1848
1865
  * belongsTo; no relation `with` / `where` / `distinct` / `orderBy` /
1849
1866
  * `limit` / `offset` (a scalar path has no per-hop filter/order and cannot
1850
- * reproduce those — such inputs stay on the loader for exact parity); a link
1867
+ * reproduce those, such inputs stay on the loader for exact parity); a link
1851
1868
  * name and all projected columns that are bare identifiers (a quoted segment in
1852
- * a dotted link path is outside the verified spelling — fall back); and a
1869
+ * a dotted link path is outside the verified spelling, fall back); and a
1853
1870
  * DECLARED link that verifiably matches (`findMatchingLink`).
1854
1871
  */
1855
1872
  async planLinkPathRelation(relName, rel, opt, includePii, parentCols, index) {
@@ -1880,7 +1897,7 @@ class PowqlInterface {
1880
1897
  });
1881
1898
  if (!hasCarrierBlockedCol)
1882
1899
  return null;
1883
- // All cheap checks passed: NOW fetch (and cache) the link snapshot — never
1900
+ // All cheap checks passed: NOW fetch (and cache) the link snapshot, never
1884
1901
  // before, so a query with no link-path candidate issues no `schema links`.
1885
1902
  const link = this.findMatchingLink(await this.linksSnapshot(), rel);
1886
1903
  if (!link)
@@ -1911,7 +1928,7 @@ class PowqlInterface {
1911
1928
  }
1912
1929
  /**
1913
1930
  * Reconstruct each link-path relation's child entity from its flat hop fields
1914
- * and attach it under the relation name — output indistinguishable from the
1931
+ * and attach it under the relation name, output indistinguishable from the
1915
1932
  * loader (same keys, same coercions). Presence: the target PK cell arriving
1916
1933
  * Empty (a null/dangling FK at the hop) means no linked row → `null`, matching
1917
1934
  * the loader's `matches[0] ?? null`. Otherwise the gathered snake cells go
@@ -1941,7 +1958,7 @@ class PowqlInterface {
1941
1958
  }
1942
1959
  }
1943
1960
  // -------------------------------------------------------------------------
1944
- // Writes (reselect — PowDB has no RETURNING)
1961
+ // Writes (reselect, PowDB has no RETURNING)
1945
1962
  // -------------------------------------------------------------------------
1946
1963
  /** Split `data` into scalar assignments; reject relation (nested-write) keys. */
1947
1964
  scalarData(data) {
@@ -1950,7 +1967,7 @@ class PowqlInterface {
1950
1967
  if (value === undefined)
1951
1968
  continue;
1952
1969
  if (this.meta.relations[field]) {
1953
- throw new errors_js_1.UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}" — nested writes need create()/update(), not createMany()/upsert()`);
1970
+ throw new errors_js_1.UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}", nested writes need create()/update(), not createMany()/upsert()`);
1954
1971
  }
1955
1972
  out.push({ col: this.column(field), value });
1956
1973
  }
@@ -1959,8 +1976,8 @@ class PowqlInterface {
1959
1976
  /**
1960
1977
  * Fill in a client-generated UUID for a defaulted **string** PK that wasn't
1961
1978
  * supplied. A server-generated PK ({@link ColumnMetadata.isGenerated}, e.g. an
1962
- * `int` column with PowDB's `auto` modifier) is left untouched — PowDB assigns
1963
- * it and the trailing `returning` reads it back — as is any non-string PK.
1979
+ * `int` column with PowDB's `auto` modifier) is left untouched, PowDB assigns
1980
+ * it and the trailing `returning` reads it back, as is any non-string PK.
1964
1981
  */
1965
1982
  applyPkDefault(data) {
1966
1983
  const out = { ...data };
@@ -1977,7 +1994,7 @@ class PowqlInterface {
1977
1994
  return out;
1978
1995
  }
1979
1996
  /**
1980
- * The table name as a PowQL type reference — backtick-quoted when it is a
1997
+ * The table name as a PowQL type reference, backtick-quoted when it is a
1981
1998
  * reserved word (e.g. a table named `order`). Used in every emitted
1982
1999
  * statement; plain `this.table` stays in error messages.
1983
2000
  */
@@ -2060,7 +2077,7 @@ class PowqlInterface {
2060
2077
  if (value === undefined)
2061
2078
  continue;
2062
2079
  if (this.meta.relations[field]) {
2063
- throw new errors_js_1.UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}" — nested writes need create()/update(), not updateMany()/upsert()`);
2080
+ throw new errors_js_1.UnsupportedFeatureError('nested writes', 'PowDB', `relation "${field}", nested writes need create()/update(), not updateMany()/upsert()`);
2064
2081
  }
2065
2082
  const colMeta = this.column(field);
2066
2083
  const ref = this.ref(field);
@@ -2089,11 +2106,11 @@ class PowqlInterface {
2089
2106
  return parts.join(', ');
2090
2107
  }
2091
2108
  // -------------------------------------------------------------------------
2092
- // Nested writes — create/update whose `data` carries relation ops (create,
2109
+ // Nested writes, create/update whose `data` carries relation ops (create,
2093
2110
  // connect, connectOrCreate, disconnect, set, delete, update, upsert). Reuses
2094
2111
  // the engine-agnostic nested-write engine (it only needs ctx.schema + the
2095
2112
  // table accessors PowqlInterface already provides). Runs as ONE flat top-level
2096
- // PowDB transaction — single global write lock, no savepoints, so the whole
2113
+ // PowDB transaction, single global write lock, no savepoints, so the whole
2097
2114
  // tree commits or rolls back together (mirrors the SQL path's coverage:
2098
2115
  // hasMany / hasOne / belongsTo; manyToMany nested writes are not handled by
2099
2116
  // the shared engine on any backend).
@@ -2136,9 +2153,13 @@ class PowqlInterface {
2136
2153
  // Pass the PowDB pool so its read-only guard + capabilities carry into
2137
2154
  // the transaction-scoped proxy pool (see createTxPool).
2138
2155
  this.pool);
2139
- const ctx = { schema: this.schema, tx: tx };
2156
+ const ctx = {
2157
+ schema: this.schema,
2158
+ tx: tx,
2159
+ scopedConnect: this.options?.scopedConnect === true,
2160
+ };
2140
2161
  // Plant the single-writer re-entrancy marker for the implicit tx's
2141
- // subtree (same seam TurbineClient.$transaction uses) — user code that
2162
+ // subtree (same seam TurbineClient.$transaction uses), user code that
2142
2163
  // fires db.$transaction from inside (e.g. $use middleware around a
2143
2164
  // nested-write child op) must fast-fail E017, not queue into deadlock.
2144
2165
  const wrap = client
@@ -2148,7 +2169,7 @@ class PowqlInterface {
2148
2169
  return result;
2149
2170
  }
2150
2171
  catch (err) {
2151
- // Only roll back a transaction we actually opened — a failed BEGIN
2172
+ // Only roll back a transaction we actually opened, a failed BEGIN
2152
2173
  // (queue timeout, re-entrancy E017) must not emit a stray ROLLBACK that
2153
2174
  // could land inside another transaction on a shared engine handle.
2154
2175
  if (began) {
@@ -2156,7 +2177,7 @@ class PowqlInterface {
2156
2177
  await client.query(d?.rollbackStatement?.() ?? 'rollback');
2157
2178
  }
2158
2179
  catch {
2159
- /* best-effort — the connection may be gone */
2180
+ /* best-effort, the connection may be gone */
2160
2181
  }
2161
2182
  }
2162
2183
  throw err;
@@ -2179,7 +2200,7 @@ class PowqlInterface {
2179
2200
  const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
2180
2201
  const where = this.buildWhere(resolvedWhere, params);
2181
2202
  this.assertCompiledWhere(where, false, 'delete');
2182
- // `returning` hands back the deleted row(s) — no separate pre-image reselect needed.
2203
+ // `returning` hands back the deleted row(s), no separate pre-image reselect needed.
2183
2204
  const { rows, native } = await this.exec(`${this.qt} filter ${where} delete returning`, params, args.timeout, 'delete');
2184
2205
  const row = rows.length ? this.stripWritePii(this.shape(rows, native)[0]) : null;
2185
2206
  if (!row)
@@ -2267,7 +2288,7 @@ class PowqlInterface {
2267
2288
  }
2268
2289
  async aggregate(args) {
2269
2290
  return this.withMiddleware('aggregate', args, async () => {
2270
- // One scalar query per aggregate — PowDB's bare-projection aggregate is broken.
2291
+ // One scalar query per aggregate, PowDB's bare-projection aggregate is broken.
2271
2292
  const result = {};
2272
2293
  const filterParams = [];
2273
2294
  const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
@@ -2636,12 +2657,12 @@ class PowqlInterface {
2636
2657
  // -------------------------------------------------------------------------
2637
2658
  // Streaming / unsupported
2638
2659
  // -------------------------------------------------------------------------
2639
- // biome-ignore lint/correctness/useYield: intentionally throws before yielding — PowDB has no server cursor.
2660
+ // biome-ignore lint/correctness/useYield: intentionally throws before yielding, PowDB has no server cursor.
2640
2661
  async *findManyStream() {
2641
2662
  throw new errors_js_1.UnsupportedFeatureError('cursor streaming (findManyStream)', 'PowDB', 'PowDB has no server-side cursor; page with findMany({ limit, offset }) instead');
2642
2663
  }
2643
2664
  // -------------------------------------------------------------------------
2644
- // Reselect helper (upsert only — PowDB's upsert has no `returning`)
2665
+ // Reselect helper (upsert only, PowDB's upsert has no `returning`)
2645
2666
  // -------------------------------------------------------------------------
2646
2667
  /** Reselect a single row by its single-column primary key value. */
2647
2668
  async reselectByPk(pkValue, timeout) {
@@ -2654,12 +2675,12 @@ class PowqlInterface {
2654
2675
  return rows.length ? this.shape(rows, native)[0] : null;
2655
2676
  }
2656
2677
  /**
2657
- * Empty-where guard — blocks accidental whole-table writes. Mirrors the SQL
2678
+ * Empty-where guard, blocks accidental whole-table writes. Mirrors the SQL
2658
2679
  * path's `assertMutationHasPredicate` (query/builder.ts): it gates on the
2659
2680
  * *compiled* PowQL filter fragment, NOT the shape of the `where` object. A
2660
- * `where` whose conditions all evaporate during compilation — `{}`,
2681
+ * `where` whose conditions all evaporate during compilation, `{}`,
2661
2682
  * `{ id: undefined }`, `{ OR: [] }`, `{ AND: [] }`, `{ NOT: {} }`,
2662
- * `{ OR: [{ f: undefined }] }` — compiles to the empty string and is refused,
2683
+ * `{ OR: [{ f: undefined }] }`, compiles to the empty string and is refused,
2663
2684
  * because emitting a filter-less write would hit every row.
2664
2685
  */
2665
2686
  assertCompiledWhere(compiledWhere, allow, action) {
@@ -73,7 +73,7 @@ export declare function buildDistinctOnSource<T extends object>(qi: BuilderCtx,
73
73
  * throws {@link ValidationError} for unknown fields and `qi.q()` quotes via
74
74
  * the dialect, so no unvalidated identifier ever reaches the SQL string. Every
75
75
  * comparison value is pushed onto the shared `params` array and referenced by
76
- * a `$N` placeholder via {@link buildHavingNumericClauses} — there is no string
76
+ * a `$N` placeholder via {@link buildHavingNumericClauses}, there is no string
77
77
  * interpolation of user values.
78
78
  *
79
79
  * `jsonAggExprs` (from {@link buildGroupBy}) maps `alias:aggKey` to the
@@ -107,7 +107,7 @@ function buildGroupBy(qi, args) {
107
107
  ? buildDistinctOnSource(qi, args.distinctOn, whereSql, params)
108
108
  : `${qi.q(qi.table)}${whereSql}`;
109
109
  // Group keys: plain columns and/or JSON-path keys. Output-name collisions
110
- // are rejected up front — and the check runs over the EMITTED SQL output
110
+ // are rejected up front, and the check runs over the EMITTED SQL output
111
111
  // column names (snake_case column / JSON alias / `_agg_key` aggregate
112
112
  // alias), not just the given arg keys: the driver keeps only the LAST
113
113
  // duplicate field per row object, so a JSON alias equal to another key's
@@ -254,7 +254,7 @@ function buildGroupBy(qi, args) {
254
254
  buildAggregates('_min', 'MIN', args._min);
255
255
  buildAggregates('_max', 'MAX', args._max);
256
256
  let sql = `SELECT ${selectExprs.join(', ')} FROM ${fromSql} GROUP BY ${groupExprs.join(', ')}`;
257
- // HAVING — filter whole groups by their aggregate values.
257
+ // HAVING, filter whole groups by their aggregate values.
258
258
  // Appends to the same `params` array, so placeholders continue from the
259
259
  // WHERE clause's parameter positions (qi.p(params.length) below).
260
260
  if (args.having) {
@@ -510,7 +510,7 @@ function buildDistinctOnSource(qi, distinctOn, whereSql, params) {
510
510
  * throws {@link ValidationError} for unknown fields and `qi.q()` quotes via
511
511
  * the dialect, so no unvalidated identifier ever reaches the SQL string. Every
512
512
  * comparison value is pushed onto the shared `params` array and referenced by
513
- * a `$N` placeholder via {@link buildHavingNumericClauses} — there is no string
513
+ * a `$N` placeholder via {@link buildHavingNumericClauses}, there is no string
514
514
  * interpolation of user values.
515
515
  *
516
516
  * `jsonAggExprs` (from {@link buildGroupBy}) maps `alias:aggKey` to the
@@ -522,7 +522,7 @@ function buildDistinctOnSource(qi, distinctOn, whereSql, params) {
522
522
  function buildHavingClauses(qi, having, params, jsonAggExprs) {
523
523
  const clauses = [];
524
524
  // Maps the per-field aggregate key to its SQL function name. The set of
525
- // allowed keys is fixed here — any other key on a field's filter object is
525
+ // allowed keys is fixed here, any other key on a field's filter object is
526
526
  // rejected by ValidationError below (never interpolated).
527
527
  const aggFnByKey = {
528
528
  _sum: 'SUM',
@@ -545,7 +545,7 @@ function buildHavingClauses(qi, having, params, jsonAggExprs) {
545
545
  `expected an aggregate object like { _sum: { gt: 100 } }.`);
546
546
  }
547
547
  // toColumn validates the field against schema metadata (throws
548
- // ValidationError on unknown columns) and q() quotes the identifier — no
548
+ // ValidationError on unknown columns) and q() quotes the identifier, no
549
549
  // unvalidated identifier ever reaches the SQL string. Resolution is lazy:
550
550
  // a JSON-path aggregate alias is not a column, so it must not hit
551
551
  // toColumn when every aggregate under it resolves via `jsonAggExprs`.
@@ -1,19 +1,19 @@
1
1
  /**
2
- * turbine-orm — Batched relation loader (the `relationLoadStrategy: 'batched'` path)
2
+ * turbine-orm, Batched relation loader (the `relationLoadStrategy: 'batched'` path)
3
3
  *
4
4
  * ## Why this exists
5
5
  *
6
6
  * Turbine's default `with`-clause strategy resolves nested relations in ONE SQL
7
- * statement using correlated `json_agg(json_build_object(...))` subqueries — one
7
+ * statement using correlated `json_agg(json_build_object(...))` subqueries, one
8
8
  * probe per parent row (see `buildRelationSubquery` in builder.ts). That is the
9
9
  * right default: a single round-trip, and when the child FK columns are indexed
10
10
  * each probe is an index seek. But it degrades in two situations:
11
11
  *
12
- * 1. **Missing FK index** — a correlated probe per parent row becomes
12
+ * 1. **Missing FK index**, a correlated probe per parent row becomes
13
13
  * N-parents × full-table-scan. A batched-loader ORM pays that missing index
14
14
  * only ONCE (a single `WHERE fk = ANY($1)` seq-scan), which is why schemas
15
15
  * migrated from those ORMs often lack the index the json_agg path needs.
16
- * 2. **Huge unpaginated result sets** — the JSON wire format
16
+ * 2. **Huge unpaginated result sets**, the JSON wire format
17
17
  * (`json_build_object` per row, re-serialized inside `json_agg`) is heavy to
18
18
  * encode/decode compared with flat rows.
19
19
  *
@@ -29,19 +29,19 @@
29
29
  * - **Same executor / connection path.** Every follow-up query runs through the
30
30
  * caller's own executor ({@link RelationLoadContext.exec}) and child query
31
31
  * interfaces built on the caller's pool. Inside a `$transaction` that pool is
32
- * the pinned-connection `txPool`, so batched loads join the transaction — no
32
+ * the pinned-connection `txPool`, so batched loads join the transaction, no
33
33
  * separate pool checkout per query.
34
34
  * - **Identical output shape.** The stitched result is byte-for-byte the same
35
35
  * shape the join strategy produces: relation arrays for hasMany/manyToMany
36
36
  * (`[]` when empty), single-or-null for hasOne/belongsTo, with the same
37
- * camelCase keys and Date coercion — because the child rows are parsed by the
37
+ * camelCase keys and Date coercion, because the child rows are parsed by the
38
38
  * very same `parseRow`/`buildFindMany` machinery via a child QueryInterface.
39
39
  * - **Stitch keys never leak.** To stitch, the follow-up query must select the
40
40
  * FK/PK it joins on even when the caller's `select`/`omit` excluded it; the
41
41
  * loader adds those columns for the query and strips them from the returned
42
42
  * entities afterwards ({@link includeKeysForBatching}).
43
43
  *
44
- * PowDB (powql.ts) has its own batched loaders for the same reasons — this is the
44
+ * PowDB (powql.ts) has its own batched loaders for the same reasons, this is the
45
45
  * clean Postgres/SQL implementation, deliberately NOT shared with PowQL.
46
46
  *
47
47
  * @module
@@ -132,7 +132,7 @@ export declare function defaultProjectionFields(meta: TableMetadata, includePii:
132
132
  *
133
133
  * Used both for the base query (parent keys) and each follow-up query (child
134
134
  * keys) so a caller's `select: { title: true }` on a relation still stitches even
135
- * though the FK was not requested — and the FK never appears in the output.
135
+ * though the FK was not requested, and the FK never appears in the output.
136
136
  */
137
137
  export declare function includeKeysForBatching(select: Record<string, boolean> | undefined, omit: Record<string, boolean> | undefined, fields: string[],
138
138
  /**
@@ -173,13 +173,13 @@ export declare function neededParentKeyFields(parentMeta: TableMetadata, withCla
173
173
  */
174
174
  export declare function resolveCountRelations(parentMeta: TableMetadata, countSpec: WithCount): RelationDef[];
175
175
  /**
176
- * Reject pick-row relation ordering anywhere inside a `with` tree's orderBy —
176
+ * Reject pick-row relation ordering anywhere inside a `with` tree's orderBy -
177
177
  * strategy parity with the join path, which throws this exact E003 at SQL
178
178
  * build time (`pickOrderNestedError` in builder.ts). Without this guard the
179
179
  * loaders would forward `options.orderBy` as the child reader's TOP-LEVEL
180
- * findMany orderBy, where the pick shape compiles fine — so the same query
180
+ * findMany orderBy, where the pick shape compiles fine, so the same query
181
181
  * would execute on 'batched' but throw on 'join'. Walks the whole tree up
182
- * front so acceptance never depends on which levels have rows — the batched
182
+ * front so acceptance never depends on which levels have rows, the batched
183
183
  * runners in builder.ts call this BEFORE the base query (a zero-row base
184
184
  * result must still reject, exactly like the join strategy's build-time throw).
185
185
  */