turbine-orm 0.65.0 → 0.66.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 (142) hide show
  1. package/README.md +34 -32
  2. package/dist/adapters/cockroachdb.js +21 -3
  3. package/dist/adapters/index.d.ts +15 -0
  4. package/dist/adapters/yugabytedb.js +20 -3
  5. package/dist/cjs/adapters/cockroachdb.js +21 -3
  6. package/dist/cjs/adapters/index.d.ts +15 -0
  7. package/dist/cjs/adapters/yugabytedb.js +20 -3
  8. package/dist/cjs/cli/destructive.d.ts +18 -4
  9. package/dist/cjs/cli/destructive.js +230 -122
  10. package/dist/cjs/cli/index.d.ts +21 -4
  11. package/dist/cjs/cli/index.js +119 -22
  12. package/dist/cjs/cli/mcp.d.ts +28 -8
  13. package/dist/cjs/cli/mcp.js +170 -127
  14. package/dist/cjs/cli/migrate.d.ts +134 -13
  15. package/dist/cjs/cli/migrate.js +349 -241
  16. package/dist/cjs/cli/pii-predicate-guard.d.ts +112 -0
  17. package/dist/cjs/cli/pii-predicate-guard.js +390 -0
  18. package/dist/cjs/cli/prisma-resolve.js +75 -4
  19. package/dist/cjs/cli/prisma-schema.d.ts +17 -1
  20. package/dist/cjs/cli/prisma-schema.js +83 -17
  21. package/dist/cjs/cli/sql-statements.d.ts +125 -0
  22. package/dist/cjs/cli/sql-statements.js +378 -0
  23. package/dist/cjs/cli/studio.js +49 -118
  24. package/dist/cjs/cli/ui.d.ts +1 -1
  25. package/dist/cjs/client.d.ts +43 -0
  26. package/dist/cjs/client.js +125 -6
  27. package/dist/cjs/dialect.d.ts +123 -0
  28. package/dist/cjs/dialect.js +33 -0
  29. package/dist/cjs/errors.d.ts +74 -1
  30. package/dist/cjs/errors.js +239 -25
  31. package/dist/cjs/index-advisor.d.ts +33 -1
  32. package/dist/cjs/index-advisor.js +32 -1
  33. package/dist/cjs/introspect.d.ts +48 -0
  34. package/dist/cjs/introspect.js +222 -91
  35. package/dist/cjs/mssql.js +43 -1
  36. package/dist/cjs/mysql.d.ts +5 -2
  37. package/dist/cjs/mysql.js +202 -17
  38. package/dist/cjs/nested-write.js +6 -1
  39. package/dist/cjs/pipeline-submittable.js +17 -3
  40. package/dist/cjs/pipeline.js +75 -9
  41. package/dist/cjs/powdb.d.ts +23 -0
  42. package/dist/cjs/powdb.js +33 -1
  43. package/dist/cjs/powql.d.ts +61 -9
  44. package/dist/cjs/powql.js +186 -49
  45. package/dist/cjs/prisma-compat.js +160 -41
  46. package/dist/cjs/query/aggregates.d.ts +1 -1
  47. package/dist/cjs/query/aggregates.js +80 -18
  48. package/dist/cjs/query/batched-loader.d.ts +10 -0
  49. package/dist/cjs/query/batched-loader.js +268 -7
  50. package/dist/cjs/query/builder.d.ts +73 -0
  51. package/dist/cjs/query/builder.js +225 -28
  52. package/dist/cjs/query/filters.d.ts +162 -0
  53. package/dist/cjs/query/filters.js +250 -1
  54. package/dist/cjs/query/relations.d.ts +10 -10
  55. package/dist/cjs/query/relations.js +93 -12
  56. package/dist/cjs/query/types.d.ts +14 -1
  57. package/dist/cjs/query/utils.d.ts +146 -2
  58. package/dist/cjs/query/utils.js +210 -4
  59. package/dist/cjs/query/warn-registry.d.ts +10 -0
  60. package/dist/cjs/query/warn-registry.js +10 -0
  61. package/dist/cjs/query/where-compile.d.ts +30 -0
  62. package/dist/cjs/query/where-compile.js +41 -0
  63. package/dist/cjs/query/where.d.ts +128 -13
  64. package/dist/cjs/query/where.js +215 -77
  65. package/dist/cjs/query/writes.d.ts +1 -1
  66. package/dist/cjs/query/writes.js +39 -15
  67. package/dist/cjs/schema-builder.d.ts +2 -1
  68. package/dist/cjs/schema-sql.d.ts +94 -4
  69. package/dist/cjs/schema-sql.js +506 -30
  70. package/dist/cjs/schema.d.ts +3 -1
  71. package/dist/cjs/sqlite.d.ts +6 -0
  72. package/dist/cjs/sqlite.js +151 -10
  73. package/dist/cjs/typed-sql.d.ts +29 -1
  74. package/dist/cjs/typed-sql.js +30 -12
  75. package/dist/cli/destructive.d.ts +18 -4
  76. package/dist/cli/destructive.js +229 -121
  77. package/dist/cli/index.d.ts +21 -4
  78. package/dist/cli/index.js +120 -24
  79. package/dist/cli/mcp.d.ts +28 -8
  80. package/dist/cli/mcp.js +172 -129
  81. package/dist/cli/migrate.d.ts +134 -13
  82. package/dist/cli/migrate.js +347 -238
  83. package/dist/cli/pii-predicate-guard.d.ts +112 -0
  84. package/dist/cli/pii-predicate-guard.js +386 -0
  85. package/dist/cli/prisma-resolve.js +75 -4
  86. package/dist/cli/prisma-schema.d.ts +17 -1
  87. package/dist/cli/prisma-schema.js +83 -17
  88. package/dist/cli/sql-statements.d.ts +125 -0
  89. package/dist/cli/sql-statements.js +373 -0
  90. package/dist/cli/studio.js +49 -118
  91. package/dist/cli/ui.d.ts +1 -1
  92. package/dist/client.d.ts +43 -0
  93. package/dist/client.js +126 -7
  94. package/dist/dialect.d.ts +123 -0
  95. package/dist/dialect.js +33 -0
  96. package/dist/errors.d.ts +74 -1
  97. package/dist/errors.js +228 -19
  98. package/dist/index-advisor.d.ts +33 -1
  99. package/dist/index-advisor.js +31 -1
  100. package/dist/introspect.d.ts +48 -0
  101. package/dist/introspect.js +221 -91
  102. package/dist/mssql.js +44 -2
  103. package/dist/mysql.d.ts +5 -2
  104. package/dist/mysql.js +203 -18
  105. package/dist/nested-write.js +7 -2
  106. package/dist/pipeline-submittable.js +18 -4
  107. package/dist/pipeline.js +76 -10
  108. package/dist/powdb.d.ts +23 -0
  109. package/dist/powdb.js +33 -2
  110. package/dist/powql.d.ts +61 -9
  111. package/dist/powql.js +187 -50
  112. package/dist/prisma-compat.js +160 -41
  113. package/dist/query/aggregates.d.ts +1 -1
  114. package/dist/query/aggregates.js +82 -20
  115. package/dist/query/batched-loader.d.ts +10 -0
  116. package/dist/query/batched-loader.js +270 -9
  117. package/dist/query/builder.d.ts +73 -0
  118. package/dist/query/builder.js +226 -30
  119. package/dist/query/filters.d.ts +162 -0
  120. package/dist/query/filters.js +246 -1
  121. package/dist/query/relations.d.ts +10 -10
  122. package/dist/query/relations.js +94 -14
  123. package/dist/query/types.d.ts +14 -1
  124. package/dist/query/utils.d.ts +146 -2
  125. package/dist/query/utils.js +204 -3
  126. package/dist/query/warn-registry.d.ts +10 -0
  127. package/dist/query/warn-registry.js +10 -0
  128. package/dist/query/where-compile.d.ts +30 -0
  129. package/dist/query/where-compile.js +40 -1
  130. package/dist/query/where.d.ts +128 -13
  131. package/dist/query/where.js +216 -80
  132. package/dist/query/writes.d.ts +1 -1
  133. package/dist/query/writes.js +40 -16
  134. package/dist/schema-builder.d.ts +2 -1
  135. package/dist/schema-sql.d.ts +94 -4
  136. package/dist/schema-sql.js +505 -30
  137. package/dist/schema.d.ts +3 -1
  138. package/dist/sqlite.d.ts +6 -0
  139. package/dist/sqlite.js +151 -10
  140. package/dist/typed-sql.d.ts +29 -1
  141. package/dist/typed-sql.js +30 -12
  142. package/package.json +6 -4
@@ -115,7 +115,9 @@ export interface ColumnMetadata {
115
115
  * Tagged in `defineSchema` (`pii: true`) and carried through generated
116
116
  * metadata. A PII column is EXCLUDED from default projections: it comes back
117
117
  * only when explicitly named in `select` or when the query passes
118
- * `includePii: true` (full opt-in). Studio redacts PII cells by default.
118
+ * `includePii: UNSAFE` (full opt-in, the imported sentinel symbol, since a
119
+ * literal `true` is what a stray JSON body deserializes to). Studio redacts
120
+ * PII cells by default.
119
121
  * Optional / defaults to `false`; untagged schemas behave exactly as before.
120
122
  * Introspection never auto-tags PII (it is a code-first declaration).
121
123
  */
@@ -67,6 +67,12 @@ export declare class SqlitePool implements PgCompatPool {
67
67
  /** The underlying `node:sqlite` handle, exposed as an escape hatch (seed/DDL). */
68
68
  readonly db: DatabaseSync;
69
69
  private closed;
70
+ /**
71
+ * `sql -> declared-boolean result columns` (or `null` for none). Bounded like
72
+ * the query builder's SQL-template cache, since a caller can generate
73
+ * unbounded distinct statements (raw SQL, an IN-list that inlines).
74
+ */
75
+ private readonly booleanCache;
70
76
  constructor(db: DatabaseSync);
71
77
  query(text: QueryArg, values?: unknown[]): Promise<any>;
72
78
  connect(): Promise<PgCompatPoolClient>;
@@ -57,6 +57,7 @@ const client_js_1 = require("./client.js");
57
57
  const dialect_js_1 = require("./dialect.js");
58
58
  const errors_js_1 = require("./errors.js");
59
59
  const introspect_js_1 = require("./introspect.js");
60
+ const utils_js_1 = require("./query/utils.js");
60
61
  const schema_js_1 = require("./schema.js");
61
62
  let cachedDatabaseSync;
62
63
  /**
@@ -130,14 +131,101 @@ function normalizeValue(value) {
130
131
  }
131
132
  return value;
132
133
  }
133
- /** Convert a `node:sqlite` null-prototype row into a normalized plain object. */
134
- function normalizeRow(row) {
134
+ /**
135
+ * Convert a `node:sqlite` null-prototype row into a normalized plain object.
136
+ *
137
+ * `booleanKeys`, when present, names the result columns whose ORIGIN column is
138
+ * declared with a boolean affinity; their 1/0 storage is turned back into
139
+ * `true`/`false` (see {@link booleanResultColumns}). Anything else in such a
140
+ * column, and every column not listed, passes through untouched.
141
+ */
142
+ function normalizeRow(row, booleanKeys) {
135
143
  const out = {};
136
144
  for (const key of Object.keys(row)) {
137
- out[key] = normalizeValue(row[key]);
145
+ const value = normalizeValue(row[key]);
146
+ out[key] = booleanKeys?.has(key) && (value === 1 || value === 0) ? value === 1 : value;
138
147
  }
139
148
  return out;
140
149
  }
150
+ /**
151
+ * The result columns of a prepared statement whose ORIGIN column is declared
152
+ * boolean, or `null` when there are none (the overwhelmingly common case).
153
+ *
154
+ * WHY THIS EXISTS. SQLite has no boolean storage class: a `BOOLEAN` column
155
+ * holds 1/0 integers. Turbine still generates `ok: boolean` for it
156
+ * ({@link sqliteTypeToTs} maps `/bool/` to `boolean`), so `row.ok === true` was
157
+ * false, `JSON.stringify(row)` emitted `1`, and a value the ORM itself wrote as
158
+ * `true` came back as a number. The declared type is the ONLY thing that says
159
+ * "this integer is a boolean", and it lives in the schema, not in the value.
160
+ *
161
+ * The decltype comes from the driver rather than from Turbine's metadata
162
+ * because the driver knows which TABLE each result column came from.
163
+ * `StatementSync.columns()` reports `type` as `sqlite3_column_decltype`, which
164
+ * is non-null only for a direct table-column reference and null for any
165
+ * expression, so a computed column can never be coerced by accident.
166
+ *
167
+ * Node gained `columns()` in 22.13 / 23.4 while this engine's floor is 22.5, so
168
+ * it is feature-detected: on the narrow band without it, booleans keep reading
169
+ * back as 1/0 exactly as before rather than the engine refusing to run.
170
+ */
171
+ function booleanResultColumns(stmt) {
172
+ const columnsFn = stmt.columns;
173
+ if (typeof columnsFn !== 'function')
174
+ return null;
175
+ let described;
176
+ try {
177
+ described = columnsFn.call(stmt);
178
+ }
179
+ catch {
180
+ // A statement the driver cannot describe (some PRAGMA forms) is not worth
181
+ // failing a query over; skip the coercion for it.
182
+ return null;
183
+ }
184
+ let out = null;
185
+ for (const c of described) {
186
+ if (typeof c.name !== 'string' || typeof c.type !== 'string')
187
+ continue;
188
+ if (!/bool/i.test(c.type))
189
+ continue;
190
+ if (out === null)
191
+ out = new Set();
192
+ out.add(c.name);
193
+ }
194
+ return out;
195
+ }
196
+ /**
197
+ * The `node:sqlite` namespace, looked up once, or `null` when this Node build
198
+ * has no such builtin. Deliberately separate from {@link loadDatabaseSync},
199
+ * which THROWS: this is a capability question asked from the pure dialect,
200
+ * which must stay importable on Node 20.
201
+ */
202
+ let cachedSqliteNamespace;
203
+ /**
204
+ * Can the driver report a result column's DECLARED type?
205
+ *
206
+ * This is the same capability {@link booleanResultColumns} feature-detects, and
207
+ * the two have to be asked the SAME question or they split: that helper is what
208
+ * turns 1/0 into `true`/`false` on every direct read, and the boolean
209
+ * `jsonWireRule` is what does it on the join path. On the Node band with
210
+ * `node:sqlite` but without `StatementSync.columns()` (>= 22.5, < 22.13 / 23.4)
211
+ * the helper returns `null` and every direct read keeps 1/0, so a join that
212
+ * still converted would produce exactly the strategy-dependent type flip the
213
+ * rule exists to prevent, on the one runtime where nothing else can catch it.
214
+ *
215
+ * The prototype property is re-read on each call rather than memoized as a
216
+ * boolean, so the answer cannot outlive the capability.
217
+ */
218
+ function driverReportsDeclaredTypes() {
219
+ if (cachedSqliteNamespace === undefined) {
220
+ try {
221
+ cachedSqliteNamespace = (0, node_module_1.createRequire)(process.cwd())('node:sqlite');
222
+ }
223
+ catch {
224
+ cachedSqliteNamespace = null;
225
+ }
226
+ }
227
+ return typeof cachedSqliteNamespace?.StatementSync?.prototype?.columns === 'function';
228
+ }
141
229
  // ---------------------------------------------------------------------------
142
230
  // Statement classification + error translation
143
231
  // ---------------------------------------------------------------------------
@@ -241,7 +329,7 @@ function toNamedBinding(values) {
241
329
  }
242
330
  return named;
243
331
  }
244
- function runStatement(db, sql, values) {
332
+ function runStatement(db, sql, values, booleanCache) {
245
333
  const binding = toNamedBinding(values);
246
334
  let stmt;
247
335
  try {
@@ -253,7 +341,15 @@ function runStatement(db, sql, values) {
253
341
  stmt.setReadBigInts(true);
254
342
  try {
255
343
  if (statementReturnsRows(sql)) {
256
- const rows = (binding ? stmt.all(binding) : stmt.all()).map(normalizeRow);
344
+ // Describe each distinct statement ONCE. `null` (no boolean column) is a
345
+ // cached answer too, so the ordinary query pays a single Map hit per call
346
+ // and nothing per row.
347
+ let booleanKeys = booleanCache?.get(sql);
348
+ if (booleanKeys === undefined) {
349
+ booleanKeys = booleanResultColumns(stmt);
350
+ booleanCache?.set(sql, booleanKeys);
351
+ }
352
+ const rows = (binding ? stmt.all(binding) : stmt.all()).map((r) => normalizeRow(r, booleanKeys ?? undefined));
257
353
  return { rows, rowCount: rows.length };
258
354
  }
259
355
  const info = binding ? stmt.run(binding) : stmt.run();
@@ -276,21 +372,28 @@ class SqlitePool {
276
372
  /** The underlying `node:sqlite` handle, exposed as an escape hatch (seed/DDL). */
277
373
  db;
278
374
  closed = false;
375
+ /**
376
+ * `sql -> declared-boolean result columns` (or `null` for none). Bounded like
377
+ * the query builder's SQL-template cache, since a caller can generate
378
+ * unbounded distinct statements (raw SQL, an IN-list that inlines).
379
+ */
380
+ booleanCache = new utils_js_1.LRUCache(1000);
279
381
  constructor(db) {
280
382
  this.db = db;
281
383
  }
282
384
  // biome-ignore lint/suspicious/noExplicitAny: pg-compat query is generic over the row shape; runStatement returns plain objects.
283
385
  async query(text, values) {
284
386
  const { text: sql, params } = normalizeQueryArgs(text, values);
285
- return runStatement(this.db, sql, params);
387
+ return runStatement(this.db, sql, params, this.booleanCache);
286
388
  }
287
389
  async connect() {
288
390
  const db = this.db;
391
+ const booleanCache = this.booleanCache;
289
392
  return {
290
393
  // biome-ignore lint/suspicious/noExplicitAny: see query() above.
291
394
  query: async (text, values) => {
292
395
  const { text: sql, params } = normalizeQueryArgs(text, values);
293
- return runStatement(db, sql, params);
396
+ return runStatement(db, sql, params, booleanCache);
294
397
  },
295
398
  release: () => {
296
399
  // Single shared connection, nothing to return to a pool.
@@ -375,6 +478,7 @@ exports.sqliteDialect = {
375
478
  supportsReturning: true,
376
479
  supportsILike: false,
377
480
  supportsVector: false,
481
+ supportsJsonContains: false,
378
482
  // FTS5 is a virtual-table feature with its own MATCH syntax, not the
379
483
  // `to_tsvector @@ to_tsquery` shape Turbine's `search` filter emits.
380
484
  supportsFullTextSearch: false,
@@ -447,6 +551,33 @@ exports.sqliteDialect = {
447
551
  },
448
552
  };
449
553
  }
554
+ // A declared-boolean column is 1/0 in storage, and the driver shim turns
555
+ // that back into true/false on every direct read (see
556
+ // booleanResultColumns). `json_object` sees the raw integer and has no
557
+ // decltype to consult, so the join strategy alone would have kept handing
558
+ // back 1/0 while the top-level read, the batched loader and the flatten
559
+ // plan returned booleans, which is the same strategy-dependent type flip
560
+ // the bigint rule above exists to prevent. No cast is needed, only the
561
+ // decode: the JSON number IS the stored value, it just has to be read the
562
+ // same way.
563
+ //
564
+ // Gated on the SAME capability the direct-read half feature-detects (see
565
+ // driverReportsDeclaredTypes). Without it the shim cannot name a boolean
566
+ // result column, so 1/0 is the answer everywhere; converting here alone
567
+ // would put the flip back, pointing the other way.
568
+ if (t.includes('BOOL') && driverReportsDeclaredTypes()) {
569
+ return {
570
+ // Carried as TEXT because that is how the decode half is reached at
571
+ // all: the parser only runs `decode` on a string cell, so a rule that
572
+ // left the JSON number alone would never fire. The CASE narrows the
573
+ // carrier to EXACTLY the two values that mean true/false, so anything
574
+ // else a declared-boolean column happens to hold (SQLite does not
575
+ // enforce the type) stays in its own JSON form and reads back as the
576
+ // driver would have returned it, rather than being stringified.
577
+ sql: (ref) => `CASE WHEN ${ref} IN (0, 1) THEN CAST(${ref} AS TEXT) ELSE ${ref} END`,
578
+ decode: (value) => (value === '1' ? true : value === '0' ? false : value),
579
+ };
580
+ }
450
581
  // A BLOB cannot go into JSON at all: SQLite raises "JSON cannot hold BLOB
451
582
  // values" and the whole query fails with a raw SQL logic error, where the
452
583
  // batched loader returns the row fine. Carry hex and rebuild the bytes.
@@ -531,9 +662,14 @@ exports.sqliteDialect = {
531
662
  // COLLATE NOCASE is ASCII-only (no Unicode case folding), documented limit.
532
663
  return `${column} LIKE ${paramRef} COLLATE NOCASE`;
533
664
  },
665
+ // UNREACHABLE while `supportsJsonContains` is false, and kept deliberately:
666
+ // it is the emulation this dialect would use if the encoding mismatch were
667
+ // repaired, and deleting it would delete the record of what was tried. The
668
+ // param is bound as JSON text (`'"gold"'`) while `json_each.value` yields the
669
+ // decoded SQL value (`gold`), so this predicate never holds for ANY operand.
670
+ // See `supportsJsonContains` in dialect.ts for the measurements and the shape
671
+ // a faithful version needs (`json_each.type` alongside the value).
534
672
  buildJsonContains(column, paramRef) {
535
- // Emulated containment: true when any top-level JSON value equals the param.
536
- // Limited vs Postgres `@>` (no deep/object containment), jsonPathSupport='function'.
537
673
  return `EXISTS (SELECT 1 FROM json_each(${column}) WHERE json_each.value = ${paramRef})`;
538
674
  },
539
675
  buildJsonPathExtract(column, pathParamRef) {
@@ -616,7 +752,12 @@ exports.sqliteDialect = {
616
752
  function pragma(db, sql) {
617
753
  // PRAGMA / SELECT against sqlite_master, read-only, identifiers are SQLite
618
754
  // catalog names (never user input here), values normalized for safe ints.
619
- return db.prepare(sql).all().map(normalizeRow);
755
+ // No boolean-key set: PRAGMA output columns are catalog metadata (`notnull`,
756
+ // `pk`, `unique`), which are 0/1 integers the introspector reads AS integers.
757
+ return db
758
+ .prepare(sql)
759
+ .all()
760
+ .map((r) => normalizeRow(r));
620
761
  }
621
762
  /**
622
763
  * Read a live SQLite database (an open `DatabaseSync` handle) into the same
@@ -79,7 +79,35 @@ export declare class TypedSqlQuery<T extends Record<string, unknown>> implements
79
79
  private readonly sql;
80
80
  private readonly params;
81
81
  private readonly logging;
82
- constructor(pool: PgCompatPool, sql: string, params: unknown[], logging: boolean);
82
+ /**
83
+ * Runs the execution under the owning client's `errorMessages` mode. Passed
84
+ * in rather than read here because this module must not import the client.
85
+ *
86
+ * It has to reach the EXECUTION, not the construction, and that is the whole
87
+ * reason it is a constructor parameter instead of a wrapper around
88
+ * `client.sql\`...\``: this builder is lazy, so a scope established around
89
+ * the tagged-template call is torn down before a single row is fetched.
90
+ * Wrapping the template call compiled, ran, and protected nothing.
91
+ *
92
+ * Defaults to calling through, so a `TypedSqlQuery` built outside a client
93
+ * behaves exactly as before.
94
+ */
95
+ private readonly runScoped;
96
+ constructor(pool: PgCompatPool, sql: string, params: unknown[], logging: boolean,
97
+ /**
98
+ * Runs the execution under the owning client's `errorMessages` mode. Passed
99
+ * in rather than read here because this module must not import the client.
100
+ *
101
+ * It has to reach the EXECUTION, not the construction, and that is the whole
102
+ * reason it is a constructor parameter instead of a wrapper around
103
+ * `client.sql\`...\``: this builder is lazy, so a scope established around
104
+ * the tagged-template call is torn down before a single row is fetched.
105
+ * Wrapping the template call compiled, ran, and protected nothing.
106
+ *
107
+ * Defaults to calling through, so a `TypedSqlQuery` built outside a client
108
+ * behaves exactly as before.
109
+ */
110
+ runScoped?: <R>(fn: () => R) => R);
83
111
  /** Execute and return all rows. Internal; powers `then`, `one`, and `scalar`. */
84
112
  private run;
85
113
  /**
@@ -96,24 +96,42 @@ class TypedSqlQuery {
96
96
  sql;
97
97
  params;
98
98
  logging;
99
- constructor(pool, sql, params, logging) {
99
+ runScoped;
100
+ constructor(pool, sql, params, logging,
101
+ /**
102
+ * Runs the execution under the owning client's `errorMessages` mode. Passed
103
+ * in rather than read here because this module must not import the client.
104
+ *
105
+ * It has to reach the EXECUTION, not the construction, and that is the whole
106
+ * reason it is a constructor parameter instead of a wrapper around
107
+ * `client.sql\`...\``: this builder is lazy, so a scope established around
108
+ * the tagged-template call is torn down before a single row is fetched.
109
+ * Wrapping the template call compiled, ran, and protected nothing.
110
+ *
111
+ * Defaults to calling through, so a `TypedSqlQuery` built outside a client
112
+ * behaves exactly as before.
113
+ */
114
+ runScoped = (fn) => fn()) {
100
115
  this.pool = pool;
101
116
  this.sql = sql;
102
117
  this.params = params;
103
118
  this.logging = logging;
119
+ this.runScoped = runScoped;
104
120
  }
105
121
  /** Execute and return all rows. Internal; powers `then`, `one`, and `scalar`. */
106
- async run() {
107
- if (this.logging) {
108
- console.log(`[turbine] Typed SQL: ${this.sql.trim().substring(0, 120)}...`);
109
- }
110
- try {
111
- const result = await this.pool.query(this.sql, this.params);
112
- return result.rows;
113
- }
114
- catch (err) {
115
- throw (0, errors_js_1.wrapPgError)(err);
116
- }
122
+ run() {
123
+ return this.runScoped(async () => {
124
+ if (this.logging) {
125
+ console.log(`[turbine] Typed SQL: ${this.sql.trim().substring(0, 120)}...`);
126
+ }
127
+ try {
128
+ const result = await this.pool.query(this.sql, this.params);
129
+ return result.rows;
130
+ }
131
+ catch (err) {
132
+ throw (0, errors_js_1.wrapPgError)(err);
133
+ }
134
+ });
117
135
  }
118
136
  /**
119
137
  * PromiseLike implementation: `await db.sql<T>\`...\`` resolves to `T[]`.
@@ -18,15 +18,27 @@
18
18
  * Row removal hides in more than a leading `DELETE`, so the scan also covers:
19
19
  * the optional-`COLUMN` shorthand (`ALTER TABLE t DROP email`), data-modifying
20
20
  * CTEs (`WITH d AS (DELETE ...) SELECT ...`), `MERGE ... THEN DELETE`, dynamic
21
- * SQL inside a `DO`/function body, and an `UPDATE` whose only WHERE sits inside
22
- * a subquery (which restricts nothing).
21
+ * SQL inside a `DO`/function body, an `UPDATE` whose only WHERE sits inside a
22
+ * subquery (which restricts nothing), a `DROP ... CASCADE` of a non-table
23
+ * object (which takes dependent COLUMNS with it), a detached partition, and an
24
+ * `EXPLAIN ANALYZE` of any of the above (which really executes it).
25
+ *
26
+ * The lexing, "where does a statement end" and "which characters are code",
27
+ * is NOT done here: it lives in `sql-statements.ts` and is shared with the
28
+ * migration runner. Two lexers is how this guard came to disagree with the
29
+ * executor about what a file contained (see that module's header).
23
30
  */
24
- export type DestructiveKind = 'drop-table' | 'drop-schema' | 'drop-database' | 'drop-owned' | 'drop-matview' | 'drop-column' | 'truncate' | 'delete' | 'update-without-where' | 'alter-column-type' | 'merge-delete';
31
+ export type DestructiveKind = 'drop-table' | 'drop-schema' | 'drop-database' | 'drop-owned' | 'drop-matview' | 'drop-column' | 'drop-cascade' | 'detach-partition' | 'truncate' | 'delete' | 'update-without-where' | 'alter-column-type' | 'merge-delete' | 'rename' | 'dynamic-destructive';
25
32
  export interface DestructiveStatement {
26
33
  /** The offending SQL statement (trimmed, possibly long, display truncated) */
27
34
  statement: string;
28
35
  kind: DestructiveKind;
29
- /** Best-effort extracted object name (table, schema, or table.column) */
36
+ /**
37
+ * Best-effort extracted object name (table, schema, or table.column). For
38
+ * `drop-cascade` it is prefixed with the object kind (`TYPE order_status`),
39
+ * because the same rule covers eight different kinds of object and the bare
40
+ * name would not tell the operator what they are about to lose.
41
+ */
30
42
  target: string;
31
43
  }
32
44
  /** Human explanation per kind, used in CLI output. */
@@ -36,3 +48,5 @@ export declare const DESTRUCTIVE_KIND_LABEL: Record<DestructiveKind, string>;
36
48
  * return every statement that can destroy data.
37
49
  */
38
50
  export declare function scanDestructiveSql(sql: string): DestructiveStatement[];
51
+ /** Shown in place of an object name that does not exist until the block runs. */
52
+ export declare const DYNAMIC_TARGET = "<name assembled at run time>";