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
@@ -220,6 +220,51 @@ export interface LimitOffsetInput {
220
220
  /** Whether the outer SELECT already carries an ORDER BY (so none must be injected). */
221
221
  hasOrderBy: boolean;
222
222
  }
223
+ /**
224
+ * Inputs for {@link Dialect.buildPartitionLimit}, the per-correlation-key row
225
+ * bound the batched relation loader pushes into its follow-up query.
226
+ *
227
+ * The follow-up is one flat `WHERE fk = ANY($1)` over EVERY parent's children,
228
+ * so a trailing `LIMIT n` would cap the TOTAL rather than the per-parent count.
229
+ * A window function is the portable-in-shape way to say "at most n rows per
230
+ * key": rank inside the partition, then keep the ranks at or below the bound.
231
+ *
232
+ * The loader hands over the compiled child SELECT and the pieces it cannot
233
+ * spell engine-independently; the dialect owns the SQL text (and its aliases),
234
+ * exactly like {@link buildLimitOffset} and {@link buildRelationSubquery}.
235
+ */
236
+ export interface PartitionLimitInput {
237
+ /** The compiled child SELECT, to be wrapped as a derived table. */
238
+ innerSql: string;
239
+ /** RAW (unquoted) correlation column to partition by. */
240
+ partitionColumn: string;
241
+ /**
242
+ * The window's (and the outer result's) ordering, as RAW column names.
243
+ *
244
+ * NEVER EMPTY, and never a partial order: the loader only asks for this
245
+ * rewrite when the relation's `orderBy` covers a NOT NULL unique key of the
246
+ * target table, so no two rows of a partition can tie. That precondition is
247
+ * the whole licence for the rewrite. The join plan takes its `limit` with a
248
+ * per-parent `ORDER BY … LIMIT n` and this takes it with a rank over one flat
249
+ * result, and where ties exist the two are each free to keep different rows,
250
+ * which was measured happening (see `partitionOrderBy` in
251
+ * query/batched-loader.ts). Anything short of a total order therefore keeps
252
+ * the client-side slice instead of arriving here.
253
+ */
254
+ orderBy: readonly {
255
+ column: string;
256
+ direction: 'ASC' | 'DESC';
257
+ nulls?: 'FIRST' | 'LAST';
258
+ }[];
259
+ /** SQL-ready placeholder for the bound (`$3`, `?`, …). */
260
+ limitPlaceholder: string;
261
+ /**
262
+ * RAW name of the rank column the wrapper adds. It is part of the projection
263
+ * (`SELECT <outer>.*`), so the CALLER must remove it from each raw row before
264
+ * parsing, or it would surface as an extra field on every child entity.
265
+ */
266
+ rankColumn: string;
267
+ }
223
268
  /**
224
269
  * Everything an engine needs to OVERRIDE nested-relation subquery generation, for
225
270
  * dialects whose JSON-aggregation shape is fundamentally different from PostgreSQL's
@@ -345,6 +390,37 @@ export interface Dialect {
345
390
  readonly supportsUpsertUpdateWhere?: boolean;
346
391
  /** Whether this dialect/engine supports pgvector distance ops (KNN / distance WHERE). */
347
392
  readonly supportsVector: boolean;
393
+ /**
394
+ * Whether this dialect/engine can actually answer JSON CONTAINMENT, i.e. the
395
+ * pathless `{ contains }` and `{ equals }` JSON filters that compile through
396
+ * {@link buildJsonContains}.
397
+ *
398
+ * A capability flag rather than a per-operand hook, because the answer turned
399
+ * out not to vary by operand. SQLite emulates containment as
400
+ * `EXISTS (SELECT 1 FROM json_each(col) WHERE value = $1)` while the param is
401
+ * bound as JSON TEXT, and `json_each.value` yields the DECODED SQL value, so
402
+ * the comparison is between two different encodings and never holds. Measured
403
+ * against a real in-process SQLite, every operand type returned zero rows:
404
+ *
405
+ * stored filter PG SQLite
406
+ * ["gold"] contains: 'gold' match none ('gold' = '"gold"')
407
+ * [1] contains: 1 match none (INTEGER 1 = TEXT '1')
408
+ * {"a":1,"t":"gold"} contains: { a: 1 } match none (no structural walk)
409
+ *
410
+ * So the feature has never worked on that engine at all, and refusing it
411
+ * removes nothing that functioned. Fewer-rows-with-no-error is the exact
412
+ * degrade the capability contract exists to convert into a refusal.
413
+ *
414
+ * Repairing it rather than refusing it is possible and deliberately not done
415
+ * here: `json_each` exposes a `type` column ('text' / 'integer' / 'real' /
416
+ * 'true' / 'false' / 'null'), so a faithful scalar test is
417
+ * `type = $1 AND value IS $2`. That is a param-COUNT change on a path whose
418
+ * build and collect sides must stay in lockstep, which is a different change
419
+ * from this one. Until then the flag is honest and the error names the
420
+ * alternative that is exact today (`{ path: [...], equals }`, which compiles
421
+ * to `json_extract`).
422
+ */
423
+ readonly supportsJsonContains: boolean;
348
424
  /**
349
425
  * Whether this dialect/engine supports the PostgreSQL full-text `search`
350
426
  * filter (`to_tsvector(...) @@ to_tsquery(...)`). Optional: absent is treated
@@ -454,6 +530,28 @@ export interface Dialect {
454
530
  readonly supportsILike: boolean;
455
531
  /** Build a case-insensitive LIKE equivalent. */
456
532
  buildInsensitiveLike(column: string, paramRef: string): string;
533
+ /**
534
+ * Escape the LIKE metacharacters of this engine in a bound `contains` /
535
+ * `startsWith` / `endsWith` / `stringContains` operand. Optional: when a
536
+ * dialect omits it the shared {@link escapeLike} is used, which escapes the
537
+ * SQL-standard set (`\`, `%`, `_`) and pairs with the `ESCAPE '\'` clause the
538
+ * builders always emit.
539
+ *
540
+ * It exists because the standard set is not the whole set everywhere:
541
+ * T-SQL's `LIKE` also treats `[` as opening a character CLASS, so
542
+ * `{ contains: '[draft]' }` becomes "contains any one of d, r, a, f, t" on
543
+ * SQL Server, a silently over-broad predicate on an engine where every other
544
+ * dialect agrees the value is a literal. That is not injection (the operand
545
+ * is still bound), but it is a wrong answer, and the difference is a property
546
+ * of the ENGINE's pattern grammar, not of the SQL text, which is why it is a
547
+ * dialect hook rather than a branch in the where builder.
548
+ *
549
+ * Whatever a dialect returns MUST be escaped for the same `ESCAPE '\'`
550
+ * clause, i.e. a backslash prefix, and MUST be identical on the SQL-build and
551
+ * the cache-hit param-collect paths (both call this one hook, so they cannot
552
+ * drift).
553
+ */
554
+ escapeLikePattern?(value: string): string;
457
555
  /** JSON operator support level for this dialect. */
458
556
  readonly jsonPathSupport: 'native' | 'function' | 'limited';
459
557
  /** Build a JSON containment check. */
@@ -601,6 +699,31 @@ export interface Dialect {
601
699
  * param-push-ordering contract the override must honor.
602
700
  */
603
701
  buildRelationSubquery?(ctx: RelationSubqueryContext): string;
702
+ /**
703
+ * Bound a batched relation follow-up to at most N rows per correlation key,
704
+ * by wrapping the compiled child SELECT in a `ROW_NUMBER() OVER (PARTITION
705
+ * BY …)` filter. See {@link PartitionLimitInput} for why a trailing `LIMIT`
706
+ * cannot express this.
707
+ *
708
+ * OPTIONAL, and absent means "no pushdown": the batched loader then fetches
709
+ * every matching child and applies the per-relation `limit` client-side, the
710
+ * behaviour every engine had before this hook existed. That fallback is
711
+ * correct but unbounded in bytes over the wire (200 parents x ~505 children
712
+ * with `limit: 3` measured 101,000 rows fetched to keep 600, +52.9 MB peak
713
+ * heap against +0.5 MB for the join plan), which is why PostgreSQL
714
+ * implements it. Only PostgreSQL does in this release; the other engines keep
715
+ * the client-side slice, so their emitted SQL is byte-identical to before.
716
+ *
717
+ * The client-side slice is NOT removed when a dialect implements this: it
718
+ * stays as the belt-and-braces bound, and is a no-op once the engine has
719
+ * already limited each partition. It is also still the whole mechanism for
720
+ * any relation whose `orderBy` does not TOTALLY order the target table, on
721
+ * every engine including this one: a rewrite that reaches here has to return
722
+ * the same rows the join plan's per-parent `ORDER BY … LIMIT n` returns, and
723
+ * with ties present neither plan's choice is forced. See
724
+ * {@link PartitionLimitInput.orderBy}.
725
+ */
726
+ buildPartitionLimit?(input: PartitionLimitInput): string;
604
727
  }
605
728
  export interface DialectIntrospector {
606
729
  introspect(options: IntrospectOptions): Promise<SchemaMetadata>;
@@ -56,6 +56,7 @@ exports.postgresDialect = {
56
56
  nullJsonLiteral: 'NULL',
57
57
  aggSupportsInlineOrderBy: true,
58
58
  supportsVector: true,
59
+ supportsJsonContains: true,
59
60
  supportsFullTextSearch: true,
60
61
  supportsArrayColumns: true,
61
62
  supportsListenNotify: true,
@@ -294,6 +295,38 @@ exports.postgresDialect = {
294
295
  params: [name, value],
295
296
  };
296
297
  },
298
+ buildPartitionLimit(input) {
299
+ // Two wrapper levels, and both are needed: `ROW_NUMBER()` is a window
300
+ // function, so it cannot appear in the same WHERE that filters on it.
301
+ // Aliases are deliberately not in the `t<n>` family the relation builder
302
+ // allocates, so a future caller that wraps a query carrying those aliases
303
+ // cannot shadow one.
304
+ const inner = this.quoteIdentifier('turbine_pl_src');
305
+ const outer = this.quoteIdentifier('turbine_pl_rank');
306
+ const rank = this.quoteIdentifier(input.rankColumn);
307
+ const order = (ref) => input.orderBy
308
+ .map((o) => `${ref}.${this.quoteIdentifier(o.column)} ${o.direction}${o.nulls ? ` NULLS ${o.nulls}` : ''}`)
309
+ .join(', ');
310
+ // The empty-list branches are defensive only: the loader never asks for
311
+ // this rewrite without a total order (see the note on
312
+ // `PartitionLimitInput.orderBy`). An unordered window numbers each
313
+ // partition arbitrarily, and so does the join plan's ORDER-BY-less
314
+ // per-parent `LIMIT`, but they are different plans over different row sets
315
+ // and were measured choosing differently, so that shape keeps the loader's
316
+ // client-side slice rather than arriving here.
317
+ const windowOrder = input.orderBy.length > 0 ? ` ORDER BY ${order(inner)}` : '';
318
+ // The OUTER ordering is not cosmetic: the loader buckets children in result
319
+ // order, so without re-applying it here the relation array would come back
320
+ // in whatever order the rank filter happened to emit. It agrees with the
321
+ // window's own ordering by construction, since both are this one list and
322
+ // that list admits no ties, so the outer sort cannot pick a different order
323
+ // among the ranked rows than the rank did.
324
+ const outerOrder = input.orderBy.length > 0 ? ` ORDER BY ${order(outer)}` : '';
325
+ return (`SELECT ${outer}.* FROM (SELECT ${inner}.*, ROW_NUMBER() OVER (PARTITION BY ` +
326
+ `${inner}.${this.quoteIdentifier(input.partitionColumn)}${windowOrder}) AS ${rank} ` +
327
+ `FROM (${input.innerSql}) AS ${inner}) AS ${outer} ` +
328
+ `WHERE ${outer}.${rank} <= ${input.limitPlaceholder}${outerOrder}`);
329
+ },
297
330
  async *openStream(connection, sql, params, batchSize, opts) {
298
331
  // Cursors require a single connection inside a transaction: BEGIN → DECLARE
299
332
  // … NO SCROLL CURSOR FOR → FETCH n (loop) → CLOSE → COMMIT; ROLLBACK on
@@ -47,6 +47,40 @@ export declare class TurbineError extends Error {
47
47
  * with `{ errorMessages: 'verbose' }`.
48
48
  */
49
49
  export type ErrorMessageMode = 'safe' | 'verbose';
50
+ /**
51
+ * Register a client's configured mode and report whether per-operation scoping
52
+ * is now required.
53
+ *
54
+ * The gate is DIVERGENCE, not "has a client been built": while every client in
55
+ * the process agrees (the overwhelmingly common case, including every
56
+ * single-client app), the module default is already the right answer for all of
57
+ * them and no scope has to be established, so the query path pays exactly
58
+ * nothing. The moment two clients disagree, every client starts scoping, which
59
+ * is why this is read per call rather than latched per client.
60
+ *
61
+ * @internal Used by TurbineClient; not part of the public surface.
62
+ */
63
+ export declare function registerClientErrorMessageMode(mode: ErrorMessageMode): void;
64
+ /**
65
+ * Whether any two clients in this process have registered different modes, so
66
+ * the module default can no longer speak for all of them.
67
+ *
68
+ * @internal
69
+ */
70
+ export declare function errorMessageModesDiverged(): boolean;
71
+ /**
72
+ * Run `fn` with `mode` in force for everything it does, awaits included.
73
+ *
74
+ * Establishing the scope around the call (rather than threading a mode
75
+ * parameter into every error constructor) is what makes this reachable at all:
76
+ * the mode-sensitive errors are built deep inside the query executor, the write
77
+ * builders and the nested-write engine, none of which are handed the client.
78
+ * The async context is captured when the operation's promise chain is created
79
+ * INSIDE this call, so every continuation of it resolves the same mode.
80
+ *
81
+ * @internal
82
+ */
83
+ export declare function runWithErrorMessageMode<R>(mode: ErrorMessageMode, fn: () => R): R;
50
84
  /**
51
85
  * Set the global NotFoundError message mode. Called from the TurbineClient
52
86
  * constructor when `TurbineConfig.errorMessages` is provided.
@@ -70,7 +104,14 @@ export type ErrorMessageMode = 'safe' | 'verbose';
70
104
  * leak, not as a log-scrubbing boundary.
71
105
  */
72
106
  export declare function setErrorMessageMode(mode: ErrorMessageMode): void;
73
- /** Returns the current NotFoundError message mode. Exported for tests. */
107
+ /**
108
+ * Returns the NotFoundError message mode in effect right now.
109
+ *
110
+ * Called inside an operation issued through a client whose mode differs from
111
+ * another client's in the same process, that is THAT client's mode; anywhere
112
+ * else it is the process default {@link setErrorMessageMode} last set, which is
113
+ * what it has always returned. Exported for tests.
114
+ */
74
115
  export declare function getErrorMessageMode(): ErrorMessageMode;
75
116
  /**
76
117
  * The marker left where a driver `detail` string was removed in 'safe' mode.
@@ -78,6 +119,17 @@ export declare function getErrorMessageMode(): ErrorMessageMode;
78
119
  * assertions can match on it without hardcoding the wording.
79
120
  */
80
121
  export declare const REDACTED_DETAIL = "[redacted by turbine errorMessages:\"safe\"]";
122
+ /**
123
+ * Mark a driver error as carrying row values in its `message` (see
124
+ * {@link VALUE_BEARING_MESSAGE}). Called by the engine error augmenters for the
125
+ * exact driver codes whose message grammar embeds a value; every other code is
126
+ * left alone so nothing is withheld that did not need to be.
127
+ *
128
+ * Non-enumerable so the flag itself never shows up in a serialized error, and
129
+ * best-effort so a frozen or exotic driver error cannot turn a constraint
130
+ * violation into a TypeError.
131
+ */
132
+ export declare function markValueBearingMessage<T>(err: T): T;
81
133
  /**
82
134
  * Render a user-supplied `where` / `connect` target for a "no row found" error
83
135
  * message, honoring the global {@link ErrorMessageMode}. In 'safe' mode (the
@@ -168,6 +220,27 @@ export declare class ConnectionError extends TurbineError {
168
220
  sqlstate?: string;
169
221
  });
170
222
  }
223
+ /**
224
+ * The message for "this engine's connection string could not be parsed".
225
+ *
226
+ * The one rule it exists to enforce: NEVER echo the value. A DSN carries a
227
+ * password, and the trigger for this error is a MALFORMED DSN, which is exactly
228
+ * when someone pastes the failure into a bug report, a CI log, or an error
229
+ * tracker. No redaction written against the URL grammar can be trusted on a
230
+ * string that just failed to parse as a URL, so the only safe amount of it to
231
+ * include is none: not the password, not the host, not a prefix.
232
+ *
233
+ * This mirrors the Postgres path (`assertUsableConnectionString` in client.ts),
234
+ * which reached the same conclusion first and stated the same reason; the
235
+ * engines used to interpolate the raw string instead, so SECURITY.md's claim of
236
+ * redaction "in all CLI error output" was true for one engine out of four.
237
+ * Shared rather than copied so the three engines cannot drift back apart.
238
+ *
239
+ * @param engine the human engine label ("MySQL", "SQL Server", "PowDB").
240
+ * @param example a well-formed connection string for that engine, with a
241
+ * placeholder password. Never derived from the caller's value.
242
+ */
243
+ export declare function malformedConnectionStringMessage(engine: string, example: string): string;
171
244
  /** Thrown when a relation reference is invalid */
172
245
  export declare class RelationError extends TurbineError {
173
246
  constructor(message: string);