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
@@ -94,7 +94,7 @@ interface ColumnRefContext {
94
94
  * The operator's own RAW (unquoted) column name. The `column` argument the
95
95
  * operator builders receive is already quoted on the build side and raw on
96
96
  * the collect side, so the temporal bind rewrite resolves the column's type
97
- * from here instead — the one value both sides pass identically.
97
+ * from here instead, the one value both sides pass identically.
98
98
  */
99
99
  rawColumn: string;
100
100
  }
@@ -105,7 +105,7 @@ interface ColumnRefContext {
105
105
  * `with`-clause `where` filters (against a per-subquery alias, `t0.col`) compile
106
106
  * an arbitrary target table's where against a column qualifier. They differ ONLY
107
107
  * in that qualifier, the correlation parent handed to `buildRelationFilter`, and
108
- * the unknown-column error wording — so a single scoped build/collect/fingerprint
108
+ * the unknown-column error wording, so a single scoped build/collect/fingerprint
109
109
  * trio, driven by the SAME canonical {@link walkWhere} the top level uses, serves
110
110
  * both. See `buildScopedWhere` / `collectScopedWhereParams` / `fingerprintScopedWhere`.
111
111
  */
@@ -114,7 +114,7 @@ interface WhereScope {
114
114
  meta: TableMetadata;
115
115
  /** The target table name (used for host binding + error messages). */
116
116
  table: string;
117
- /** SQL prefix before `q(col)` — `"target".` for EXISTS sub-wheres, `t0.` for aliases. */
117
+ /** SQL prefix before `q(col)`, `"target".` for EXISTS sub-wheres, `t0.` for aliases. */
118
118
  qualifier: string;
119
119
  /** The `parentTable` correlation argument for nested `buildRelationFilter` calls. */
120
120
  relationParent: string;
@@ -168,7 +168,7 @@ export declare function collectScalarParams(qi: BuilderCtx, key: string, value:
168
168
  * Param-collect mirror of {@link buildRelationFilter} for one relation-filter
169
169
  * object (`{ some/every/none/is/isNot }`, already normalized). Pushes, per
170
170
  * present branch and in the canonical order some→none→every→is→isNot, the
171
- * branch's sub-where params THEN the target table's global-filter params —
171
+ * branch's sub-where params THEN the target table's global-filter params -
172
172
  * exactly the order buildRelationFilter emits. When no global filter applies
173
173
  * the gf calls are no-ops, so this stays byte-identical to the pre-0.28 path.
174
174
  * Shared by every collect site that mirrors buildRelationFilter
@@ -226,7 +226,7 @@ export declare function targetGlobalFilterAlias(qi: BuilderCtx, targetTable: str
226
226
  export declare function collectTargetGlobalFilterAlias(qi: BuilderCtx, targetTable: string, params: unknown[]): void;
227
227
  /**
228
228
  * SQL clause for `targetTable`'s global filter rendered against the bare
229
- * (unaliased) table name — the form used inside relation-filter `EXISTS`
229
+ * (unaliased) table name, the form used inside relation-filter `EXISTS`
230
230
  * subqueries. Pushes its params; `''` when none. Mirror:
231
231
  * {@link collectTargetGlobalFilterExists}.
232
232
  */
@@ -247,7 +247,7 @@ export declare function globalFilterCacheSegment(qi: BuilderCtx): string;
247
247
  /**
248
248
  * True when the USER-supplied `where` compiles to no predicate (`{}`,
249
249
  * `{ id: undefined }`, `{ OR: [{ a: undefined }] }`, …). This is the exact
250
- * signal the empty-`where` guard needs — the compiled emptiness, NOT the
250
+ * signal the empty-`where` guard needs, the compiled emptiness, NOT the
251
251
  * fingerprint (which is non-empty for an all-undefined `OR`/`AND`). It ignores
252
252
  * any configured global filter, so a global filter never lets an unguarded
253
253
  * mass mutation through.
@@ -269,7 +269,7 @@ export declare function buildWhereClause(qi: BuilderCtx, where: Record<string, u
269
269
  */
270
270
  export declare function buildScalarClause(qi: BuilderCtx, key: string, value: unknown, params: unknown[], andClauses: string[]): void;
271
271
  /**
272
- * A {@link WhereHost} with no relations — used to fingerprint a sub-where
272
+ * A {@link WhereHost} with no relations, used to fingerprint a sub-where
273
273
  * whose target table is unknown (`schema.tables[t]` miss). `walkWhere` reads
274
274
  * only `tableMeta.relations`, so every key falls to the scalar path, matching
275
275
  * the pre-unification `meta?.relations` short-circuit.
@@ -283,7 +283,7 @@ export declare function aliasWhereScope(qi: BuilderCtx, targetTable: string, met
283
283
  /**
284
284
  * Compile a scoped sub-where to SQL. Serves BOTH the relation-filter EXISTS
285
285
  * body ({@link buildSubWhereForRelation}) and the relation `with`-clause
286
- * `where` ({@link buildAliasWhere}) — the emitted SQL is byte-identical to the
286
+ * `where` ({@link buildAliasWhere}), the emitted SQL is byte-identical to the
287
287
  * former hand-mirrored walkers, since it renders the same clauses in the same
288
288
  * ({@link walkWhere}-canonical) key order.
289
289
  */
@@ -292,8 +292,8 @@ export declare function buildScopedWhere(qi: BuilderCtx, scope: WhereScope, wher
292
292
  * Emit the SQL clause(s) for one scalar key of a scoped sub-where. Reproduces
293
293
  * the null / JSON / array / operator / equality fall-through both former
294
294
  * walkers shared (relation sub-wheres and alias wheres carry no vector or
295
- * text-search scalar surface, so — unlike the top-level {@link buildScalarClause}
296
- * — those shapes are not special-cased here and keep their historical
295
+ * text-search scalar surface, so, unlike the top-level {@link buildScalarClause}
296
+ * - those shapes are not special-cased here and keep their historical
297
297
  * equality-guard behavior).
298
298
  */
299
299
  export declare function buildScopedScalarClause(qi: BuilderCtx, scope: WhereScope, field: string, value: unknown, params: unknown[], clauses: string[]): void;
@@ -340,7 +340,7 @@ export declare function pgTypeForColumn(_qi: BuilderCtx, meta: TableMetadata, co
340
340
  * the UTC-component literal, so a predicate matches the value a write of the
341
341
  * same `Date` stored.
342
342
  *
343
- * This is a VALUE transform only — it never changes the emitted SQL — so the
343
+ * This is a VALUE transform only, it never changes the emitted SQL, so the
344
344
  * SQL-template cache is unaffected, and it is applied on the cache-hit
345
345
  * param-collect path as well as the build path.
346
346
  *
@@ -353,13 +353,13 @@ export declare function coerceWhereOperand(qi: BuilderCtx, meta: TableMetadata,
353
353
  * Introspection stores each column's `udt_name` in `pgTypes` and every
354
354
  * database enum in `schema.enums` (typname → labels); a column whose type
355
355
  * matches an enum key needs an explicit `::"EnumName"` cast on its write
356
- * binds — bulk-insert forms like `UNNEST($1::text[])` otherwise type the
356
+ * binds, bulk-insert forms like `UNNEST($1::text[])` otherwise type the
357
357
  * value as text and Postgres refuses the implicit text→enum coercion
358
358
  * ("column X is of type Y but expression is of type text").
359
359
  *
360
360
  * Postgres-only by construction: gated on the active dialect being
361
361
  * `postgresql` AND on `schema.enums` having entries (only PG introspection
362
- * produces them — `defineSchema` and the other engines leave it empty), so
362
+ * produces them, `defineSchema` and the other engines leave it empty), so
363
363
  * SQLite/MySQL/MSSQL/PowDB output is byte-identical.
364
364
  */
365
365
  export declare function enumTypeForColumn(qi: BuilderCtx, column: string): string | null;
@@ -382,7 +382,7 @@ export declare function assertBindableEqualityValue(qi: BuilderCtx, rawColumn: s
382
382
  /**
383
383
  * Build the user-supplied `where` filter of a relation `with` clause against
384
384
  * the relation's table alias. Supports the same scalar surface as the
385
- * top-level WHERE builder — equality, IS NULL, operator objects (incl.
385
+ * top-level WHERE builder, equality, IS NULL, operator objects (incl.
386
386
  * `mode: 'insensitive'`), and OR/AND/NOT combinators. Unknown operator
387
387
  * objects throw via {@link assertBindableEqualityValue}.
388
388
  *
@@ -395,7 +395,7 @@ export declare function collectAliasWhereParams(qi: BuilderCtx, targetTable: str
395
395
  /**
396
396
  * Value-invariant, shape-aware fingerprint for a relation `with` clause's
397
397
  * `where` filter. Must distinguish every SQL shape {@link buildAliasWhere}
398
- * can emit — equality vs null vs operator sets vs combinators — or two
398
+ * can emit, equality vs null vs operator sets vs combinators, or two
399
399
  * differently-shaped wheres would share one cached SQL string.
400
400
  */
401
401
  export declare function fingerprintAliasWhere(qi: BuilderCtx, where: Record<string, unknown>, targetTable?: string): string;
@@ -444,7 +444,7 @@ export declare function requireArrayColumns(qi: BuilderCtx): void;
444
444
  * Resolve a {@link VectorMetric} to its pgvector distance operator from a
445
445
  * fixed allow-list, validating the target column is actually a `vector`
446
446
  * column. Throws {@link ValidationError} for an unknown metric or a
447
- * non-vector column — a user-supplied string can never become a SQL operator.
447
+ * non-vector column, a user-supplied string can never become a SQL operator.
448
448
  */
449
449
  export declare function vectorOperator(qi: BuilderCtx, field: string, rawColumn: string, metric: string): string;
450
450
  /**
@@ -456,8 +456,8 @@ export declare function vectorOperator(qi: BuilderCtx, field: string, rawColumn:
456
456
  */
457
457
  export declare function pushVectorParam(qi: BuilderCtx, field: string, _rawColumn: string, to: unknown, params: unknown[]): string;
458
458
  /**
459
- * Prisma-compat: a plain object on a to-one relation key —
460
- * `where: { vendor: { name: { contains: 'x' } } }` — is an implicit `is`
459
+ * Prisma-compat: a plain object on a to-one relation key -
460
+ * `where: { vendor: { name: { contains: 'x' } } }`, is an implicit `is`
461
461
  * filter. Normalize it to `{ is: obj }` so all downstream handling (SQL,
462
462
  * params, fingerprint) sees one canonical shape. To-many relations still
463
463
  * require an explicit `some`/`every`/`none` (a bare object there is
@@ -468,7 +468,7 @@ export declare function normalizeRelationFilter(_qi: BuilderCtx, relDef: Relatio
468
468
  * Case-insensitive json/jsonb column-type check. Postgres reports lowercase
469
469
  * udt_names, but SQLite/MySQL introspection surfaces the DECLARED type
470
470
  * (e.g. `JSON`), so every JSON-feature gate compares through this predicate
471
- * — build and collect sides alike, keeping the SQL-cache lockstep.
471
+ * - build and collect sides alike, keeping the SQL-cache lockstep.
472
472
  */
473
473
  export declare function isJsonColumnType(_qi: BuilderCtx, colType: string): boolean;
474
474
  export declare function getColumnPgType(qi: BuilderCtx, column: string): string;
@@ -478,13 +478,32 @@ export declare function getColumnPgType(qi: BuilderCtx, column: string): string;
478
478
  */
479
479
  export declare function getArrayElementType(_qi: BuilderCtx, pgType: string): string;
480
480
  /**
481
- * Validate and enumerate the range comparisons (`gt`/`gte`/`lt`/`lte`) on a
482
- * JSON filter, in the fixed {@link JSON_RANGE_OPERATORS} order. Shared by
483
- * the SQL-build path ({@link buildJsonFilterClauses}) and the cache-hit
484
- * param-collect path ({@link collectJsonFilterParams}) so both always agree
485
- * on which params are pushed — and both throw identically for invalid
486
- * shapes, so a warmed cache can never skip validation.
481
+ * Refuse a {@link JsonFilter} carrying a key that is not a JSON operator, and
482
+ * refuse a filter that selects a `path` but never compares it.
483
+ *
484
+ * Both shapes used to compile to NOTHING. `buildJsonFilterClauses` only ever
485
+ * emitted a clause for a key it recognized, so `{ path: ['title'],
486
+ * string_contains: 'x' }`, the Prisma spelling, and an easy typo besides -
487
+ * produced an empty clause list, the predicate vanished, and the query
488
+ * returned EVERY row. Inside an `AND` it silently dropped that conjunct, so a
489
+ * tenant scope written this way widened to the whole table. The equivalent
490
+ * typo on a scalar column has always thrown; this closes the inconsistency.
491
+ *
492
+ * Called from both the SQL-build path and the cache-hit param-collect path, so
493
+ * a warmed SQL cache cannot skip the check.
487
494
  */
495
+ export declare function assertJsonFilterKeys(filter: JsonFilter, column: string): void;
496
+ /**
497
+ * Validate and enumerate the substring comparisons on a JSON filter, in the
498
+ * fixed {@link JSON_STRING_OPERATORS} order. Shared by the build and collect
499
+ * paths exactly like {@link jsonRangeEntries}, so both agree on the params
500
+ * pushed and both throw identically on an invalid shape.
501
+ */
502
+ export declare function jsonStringEntries(filter: JsonFilter, column: string): {
503
+ op: string;
504
+ pattern: (escaped: string) => string;
505
+ value: string;
506
+ }[];
488
507
  export declare function jsonRangeEntries(_qi: BuilderCtx, filter: JsonFilter, column: string): {
489
508
  sqlOp: string;
490
509
  value: number | string;
@@ -500,19 +519,19 @@ export declare function jsonRangeEntries(_qi: BuilderCtx, filter: JsonFilter, co
500
519
  export declare function buildJsonFilterClauses(qi: BuilderCtx, column: string, filter: JsonFilter, params: unknown[]): string[];
501
520
  /**
502
521
  * Bind value for a JSON path parameter, encoded per dialect. PostgreSQL's
503
- * `#>>` takes a `text[]` (the segments as strings — or `nativeForm` when the
522
+ * `#>>` takes a `text[]` (the segments as strings, or `nativeForm` when the
504
523
  * caller has a specific native binding, e.g. JsonFilter's raw path array).
505
524
  * Every other engine's JSON function (`json_extract` / `JSON_EXTRACT` /
506
525
  * `JSON_VALUE`) takes a `'$'`-rooted JSONPath STRING: binding the raw array
507
526
  * would arrive as `'["a"]'` (the driver shims JSON.stringify non-primitive
508
527
  * params) and fail at runtime with the engine's bad-JSON-path error. The
509
- * encoded path stays a bound parameter — never spliced into SQL text — so
528
+ * encoded path stays a bound parameter, never spliced into SQL text, so
510
529
  * the build/collect param mirrors stay in lockstep and injection-safe.
511
530
  */
512
531
  export declare function jsonPathParam(qi: BuilderCtx, path: readonly (string | number)[], nativeForm?: unknown): unknown;
513
532
  /**
514
533
  * Cast an extracted JSON path text value to a numeric type for range
515
- * comparison. PostgreSQL uses `(expr)::numeric` (exact — the right way to
534
+ * comparison. PostgreSQL uses `(expr)::numeric` (exact, the right way to
516
535
  * compare JSON numbers, and `::float` would lose precision on big ints);
517
536
  * other dialects route through {@link Dialect.castAggregate} (SQLite/MySQL/
518
537
  * SQL Server have no `::` operator) as a float cast.
@@ -65,6 +65,8 @@ exports.normalizeRelationFilter = normalizeRelationFilter;
65
65
  exports.isJsonColumnType = isJsonColumnType;
66
66
  exports.getColumnPgType = getColumnPgType;
67
67
  exports.getArrayElementType = getArrayElementType;
68
+ exports.assertJsonFilterKeys = assertJsonFilterKeys;
69
+ exports.jsonStringEntries = jsonStringEntries;
68
70
  exports.jsonRangeEntries = jsonRangeEntries;
69
71
  exports.buildJsonFilterClauses = buildJsonFilterClauses;
70
72
  exports.jsonPathParam = jsonPathParam;
@@ -237,7 +239,7 @@ function collectScalarParams(qi, key, value, params) {
237
239
  * Param-collect mirror of {@link buildRelationFilter} for one relation-filter
238
240
  * object (`{ some/every/none/is/isNot }`, already normalized). Pushes, per
239
241
  * present branch and in the canonical order some→none→every→is→isNot, the
240
- * branch's sub-where params THEN the target table's global-filter params —
242
+ * branch's sub-where params THEN the target table's global-filter params -
241
243
  * exactly the order buildRelationFilter emits. When no global filter applies
242
244
  * the gf calls are no-ops, so this stays byte-identical to the pre-0.28 path.
243
245
  * Shared by every collect site that mirrors buildRelationFilter
@@ -255,7 +257,7 @@ function collectRelationFilterParams(qi, relDef, filterObj, params) {
255
257
  }
256
258
  if (filterObj.every !== undefined && filterObj.every !== null) {
257
259
  // gf is only emitted (build) when the `every` sub-where compiles to a
258
- // filter — otherwise `every` is trivially true and no subquery is built.
260
+ // filter, otherwise `every` is trivially true and no subquery is built.
259
261
  if (buildSubWhereForRelation(qi, target, filterObj.every, []) !== null) {
260
262
  collectRelFilterParams(qi, target, filterObj.every, params);
261
263
  collectTargetGlobalFilterExists(qi, target, params);
@@ -326,6 +328,7 @@ function collectOperatorParams(qi, column, op, params, refCtx) {
326
328
  * comparison values in {@link JSON_RANGE_OPERATORS} order.
327
329
  */
328
330
  function collectJsonFilterParams(qi, filter, params, column) {
331
+ assertJsonFilterKeys(filter, column);
329
332
  let pathPushed = false;
330
333
  const pushPathOnce = () => {
331
334
  if (!pathPushed) {
@@ -351,6 +354,10 @@ function collectJsonFilterParams(qi, filter, params, column) {
351
354
  pushPathOnce();
352
355
  params.push(value);
353
356
  }
357
+ for (const { pattern, value } of jsonStringEntries(filter, column)) {
358
+ pushPathOnce();
359
+ params.push(pattern((0, utils_js_1.escapeLike)(value)));
360
+ }
354
361
  }
355
362
  /** Collect params from array filter. Mirrors buildArrayFilterClauses. */
356
363
  function collectArrayFilterParams(qi, filter, params) {
@@ -406,7 +413,7 @@ function resolveGlobalFilter(qi, table, skip = qi.currentSkip) {
406
413
  return null;
407
414
  const obj = resolved;
408
415
  // An all-undefined filter (e.g. `{ tenantId: undefined }`) contributes
409
- // nothing — treat it as absent so it never emits a dangling clause.
416
+ // nothing, treat it as absent so it never emits a dangling clause.
410
417
  if (Object.keys(obj).every((k) => obj[k] === undefined))
411
418
  return null;
412
419
  return obj;
@@ -451,7 +458,7 @@ function collectTargetGlobalFilterAlias(qi, targetTable, params) {
451
458
  }
452
459
  /**
453
460
  * SQL clause for `targetTable`'s global filter rendered against the bare
454
- * (unaliased) table name — the form used inside relation-filter `EXISTS`
461
+ * (unaliased) table name, the form used inside relation-filter `EXISTS`
455
462
  * subqueries. Pushes its params; `''` when none. Mirror:
456
463
  * {@link collectTargetGlobalFilterExists}.
457
464
  */
@@ -513,7 +520,7 @@ function globalFilterCacheSegment(qi) {
513
520
  /**
514
521
  * True when the USER-supplied `where` compiles to no predicate (`{}`,
515
522
  * `{ id: undefined }`, `{ OR: [{ a: undefined }] }`, …). This is the exact
516
- * signal the empty-`where` guard needs — the compiled emptiness, NOT the
523
+ * signal the empty-`where` guard needs, the compiled emptiness, NOT the
517
524
  * fingerprint (which is non-empty for an all-undefined `OR`/`AND`). It ignores
518
525
  * any configured global filter, so a global filter never lets an unguarded
519
526
  * mass mutation through.
@@ -635,7 +642,7 @@ function buildScalarClause(qi, key, value, params, andClauses) {
635
642
  }
636
643
  }
637
644
  /**
638
- * A {@link WhereHost} with no relations — used to fingerprint a sub-where
645
+ * A {@link WhereHost} with no relations, used to fingerprint a sub-where
639
646
  * whose target table is unknown (`schema.tables[t]` miss). `walkWhere` reads
640
647
  * only `tableMeta.relations`, so every key falls to the scalar path, matching
641
648
  * the pre-unification `meta?.relations` short-circuit.
@@ -687,7 +694,7 @@ function aliasWhereScope(qi, targetTable, meta, alias) {
687
694
  /**
688
695
  * Compile a scoped sub-where to SQL. Serves BOTH the relation-filter EXISTS
689
696
  * body ({@link buildSubWhereForRelation}) and the relation `with`-clause
690
- * `where` ({@link buildAliasWhere}) — the emitted SQL is byte-identical to the
697
+ * `where` ({@link buildAliasWhere}), the emitted SQL is byte-identical to the
691
698
  * former hand-mirrored walkers, since it renders the same clauses in the same
692
699
  * ({@link walkWhere}-canonical) key order.
693
700
  */
@@ -728,8 +735,8 @@ function buildScopedWhere(qi, scope, where, params) {
728
735
  * Emit the SQL clause(s) for one scalar key of a scoped sub-where. Reproduces
729
736
  * the null / JSON / array / operator / equality fall-through both former
730
737
  * walkers shared (relation sub-wheres and alias wheres carry no vector or
731
- * text-search scalar surface, so — unlike the top-level {@link buildScalarClause}
732
- * — those shapes are not special-cased here and keep their historical
738
+ * text-search scalar surface, so, unlike the top-level {@link buildScalarClause}
739
+ * - those shapes are not special-cased here and keep their historical
733
740
  * equality-guard behavior).
734
741
  */
735
742
  function buildScopedScalarClause(qi, scope, field, value, params, clauses) {
@@ -929,7 +936,7 @@ function buildRelationFilter(qi, _relName, relDef, filterObj, params, parentTabl
929
936
  // DOMAIN of correlated rows in EVERY branch: `some`/`none`/`is`/`isNot`
930
937
  // ignore filtered-out rows, and `every` quantifies over only the surviving
931
938
  // rows ("every NON-deleted related row matches P"). It is ANDed into the
932
- // correlation and its params pushed AFTER the per-branch filter — mirrored
939
+ // correlation and its params pushed AFTER the per-branch filter, mirrored
933
940
  // exactly in collectWhereParams' relation-filter branch. `qt` is the bare
934
941
  // target table, matching the `FROM ${qt}` here (see targetGlobalFilterExists).
935
942
  const gfAnd = () => {
@@ -964,10 +971,10 @@ function buildRelationFilter(qi, _relName, relDef, filterObj, params, parentTabl
964
971
  clauses.push(`NOT EXISTS (SELECT 1 FROM ${qt} WHERE ${correlation}${gf} AND NOT (${filterClause}))`);
965
972
  }
966
973
  else {
967
- // "every" with empty filter = true (all match trivially) — gf irrelevant.
974
+ // "every" with empty filter = true (all match trivially), gf irrelevant.
968
975
  }
969
976
  }
970
- // "is": EXISTS — for to-one relations (same SQL as "some").
977
+ // "is": EXISTS, for to-one relations (same SQL as "some").
971
978
  // `is: null` = "no related row" (Prisma semantics) → NOT EXISTS.
972
979
  if (filterObj.is !== undefined) {
973
980
  if (filterObj.is === null) {
@@ -980,7 +987,7 @@ function buildRelationFilter(qi, _relName, relDef, filterObj, params, parentTabl
980
987
  clauses.push(`EXISTS (SELECT 1 FROM ${qt} WHERE ${correlation}${filterAnd}${gfAnd()})`);
981
988
  }
982
989
  }
983
- // "isNot": NOT EXISTS — for to-one relations (same SQL as "none").
990
+ // "isNot": NOT EXISTS, for to-one relations (same SQL as "none").
984
991
  // `isNot: null` = "a related row exists" → EXISTS.
985
992
  if (filterObj.isNot !== undefined) {
986
993
  if (filterObj.isNot === null) {
@@ -1021,7 +1028,7 @@ function pgTypeForColumn(_qi, meta, column) {
1021
1028
  * the UTC-component literal, so a predicate matches the value a write of the
1022
1029
  * same `Date` stored.
1023
1030
  *
1024
- * This is a VALUE transform only — it never changes the emitted SQL — so the
1031
+ * This is a VALUE transform only, it never changes the emitted SQL, so the
1025
1032
  * SQL-template cache is unaffected, and it is applied on the cache-hit
1026
1033
  * param-collect path as well as the build path.
1027
1034
  *
@@ -1042,13 +1049,13 @@ function coerceWhereOperand(qi, meta, column, value) {
1042
1049
  * Introspection stores each column's `udt_name` in `pgTypes` and every
1043
1050
  * database enum in `schema.enums` (typname → labels); a column whose type
1044
1051
  * matches an enum key needs an explicit `::"EnumName"` cast on its write
1045
- * binds — bulk-insert forms like `UNNEST($1::text[])` otherwise type the
1052
+ * binds, bulk-insert forms like `UNNEST($1::text[])` otherwise type the
1046
1053
  * value as text and Postgres refuses the implicit text→enum coercion
1047
1054
  * ("column X is of type Y but expression is of type text").
1048
1055
  *
1049
1056
  * Postgres-only by construction: gated on the active dialect being
1050
1057
  * `postgresql` AND on `schema.enums` having entries (only PG introspection
1051
- * produces them — `defineSchema` and the other engines leave it empty), so
1058
+ * produces them, `defineSchema` and the other engines leave it empty), so
1052
1059
  * SQLite/MySQL/MSSQL/PowDB output is byte-identical.
1053
1060
  */
1054
1061
  function enumTypeForColumn(qi, column) {
@@ -1059,7 +1066,7 @@ function enumTypeForColumn(qi, column) {
1059
1066
  return null;
1060
1067
  // Cross-schema guard (N-5): introspection records pgTypeSchema ONLY when
1061
1068
  // the column's type lives OUTSIDE the introspected schema. A same-named
1062
- // enum in another schema must not get this schema's cast — search_path
1069
+ // enum in another schema must not get this schema's cast, search_path
1063
1070
  // would resolve `::"status"` to the wrong type. Skipping the cast restores
1064
1071
  // the pre-cast behavior for such columns. Columns without pgTypeSchema
1065
1072
  // (same-schema types, defineSchema/legacy metadata) keep the cast.
@@ -1104,7 +1111,7 @@ function assertBindableEqualityValue(qi, rawColumn, value, columnPgType, table)
1104
1111
  /**
1105
1112
  * Build the user-supplied `where` filter of a relation `with` clause against
1106
1113
  * the relation's table alias. Supports the same scalar surface as the
1107
- * top-level WHERE builder — equality, IS NULL, operator objects (incl.
1114
+ * top-level WHERE builder, equality, IS NULL, operator objects (incl.
1108
1115
  * `mode: 'insensitive'`), and OR/AND/NOT combinators. Unknown operator
1109
1116
  * objects throw via {@link assertBindableEqualityValue}.
1110
1117
  *
@@ -1123,7 +1130,7 @@ function collectAliasWhereParams(qi, targetTable, targetMeta, where, params) {
1123
1130
  /**
1124
1131
  * Value-invariant, shape-aware fingerprint for a relation `with` clause's
1125
1132
  * `where` filter. Must distinguish every SQL shape {@link buildAliasWhere}
1126
- * can emit — equality vs null vs operator sets vs combinators — or two
1133
+ * can emit, equality vs null vs operator sets vs combinators, or two
1127
1134
  * differently-shaped wheres would share one cached SQL string.
1128
1135
  */
1129
1136
  function fingerprintAliasWhere(qi, where, targetTable) {
@@ -1245,18 +1252,18 @@ function buildOperatorClauses(qi, column, op, params, refCtx) {
1245
1252
  params.push(qi.inParam(cv(op.notIn)));
1246
1253
  clauses.push(qi.inClause(column, qi.p(params.length), true));
1247
1254
  }
1248
- const buildLikeClause = (paramRef) => op.mode === 'insensitive' ? qi.dialect.buildInsensitiveLike(column, paramRef) : `${column} LIKE ${paramRef}`;
1255
+ const insensitive = op.mode === 'insensitive';
1249
1256
  if (op.contains !== undefined) {
1250
1257
  params.push(`%${(0, utils_js_1.escapeLike)(op.contains)}%`);
1251
- clauses.push(`${buildLikeClause(qi.p(params.length))} ESCAPE '\\'`);
1258
+ clauses.push(buildLikeClause(qi, column, qi.p(params.length), insensitive));
1252
1259
  }
1253
1260
  if (op.startsWith !== undefined) {
1254
1261
  params.push(`${(0, utils_js_1.escapeLike)(op.startsWith)}%`);
1255
- clauses.push(`${buildLikeClause(qi.p(params.length))} ESCAPE '\\'`);
1262
+ clauses.push(buildLikeClause(qi, column, qi.p(params.length), insensitive));
1256
1263
  }
1257
1264
  if (op.endsWith !== undefined) {
1258
1265
  params.push(`%${(0, utils_js_1.escapeLike)(op.endsWith)}`);
1259
- clauses.push(`${buildLikeClause(qi.p(params.length))} ESCAPE '\\'`);
1266
+ clauses.push(buildLikeClause(qi, column, qi.p(params.length), insensitive));
1260
1267
  }
1261
1268
  return clauses;
1262
1269
  }
@@ -1291,7 +1298,7 @@ function requireArrayColumns(qi) {
1291
1298
  * Resolve a {@link VectorMetric} to its pgvector distance operator from a
1292
1299
  * fixed allow-list, validating the target column is actually a `vector`
1293
1300
  * column. Throws {@link ValidationError} for an unknown metric or a
1294
- * non-vector column — a user-supplied string can never become a SQL operator.
1301
+ * non-vector column, a user-supplied string can never become a SQL operator.
1295
1302
  */
1296
1303
  function vectorOperator(qi, field, rawColumn, metric) {
1297
1304
  if (!qi.dialect.supportsVector) {
@@ -1338,8 +1345,8 @@ function pushVectorParam(qi, field, _rawColumn, to, params) {
1338
1345
  return `${qi.p(params.length)}::vector`;
1339
1346
  }
1340
1347
  /**
1341
- * Prisma-compat: a plain object on a to-one relation key —
1342
- * `where: { vendor: { name: { contains: 'x' } } }` — is an implicit `is`
1348
+ * Prisma-compat: a plain object on a to-one relation key -
1349
+ * `where: { vendor: { name: { contains: 'x' } } }`, is an implicit `is`
1343
1350
  * filter. Normalize it to `{ is: obj }` so all downstream handling (SQL,
1344
1351
  * params, fingerprint) sees one canonical shape. To-many relations still
1345
1352
  * require an explicit `some`/`every`/`none` (a bare object there is
@@ -1360,7 +1367,7 @@ function normalizeRelationFilter(_qi, relDef, filterObj) {
1360
1367
  * Case-insensitive json/jsonb column-type check. Postgres reports lowercase
1361
1368
  * udt_names, but SQLite/MySQL introspection surfaces the DECLARED type
1362
1369
  * (e.g. `JSON`), so every JSON-feature gate compares through this predicate
1363
- * — build and collect sides alike, keeping the SQL-cache lockstep.
1370
+ * - build and collect sides alike, keeping the SQL-cache lockstep.
1364
1371
  */
1365
1372
  function isJsonColumnType(_qi, colType) {
1366
1373
  const t = colType.toLowerCase();
@@ -1397,9 +1404,92 @@ function getArrayElementType(_qi, pgType) {
1397
1404
  * JSON filter, in the fixed {@link JSON_RANGE_OPERATORS} order. Shared by
1398
1405
  * the SQL-build path ({@link buildJsonFilterClauses}) and the cache-hit
1399
1406
  * param-collect path ({@link collectJsonFilterParams}) so both always agree
1400
- * on which params are pushed — and both throw identically for invalid
1407
+ * on which params are pushed, and both throw identically for invalid
1401
1408
  * shapes, so a warmed cache can never skip validation.
1402
1409
  */
1410
+ /**
1411
+ * One LIKE comparison, honoring `mode: 'insensitive'` through the dialect and
1412
+ * always carrying the `ESCAPE '\'` clause that pairs with {@link escapeLike}.
1413
+ *
1414
+ * Shared by the scalar `contains` / `startsWith` / `endsWith` operators and by
1415
+ * the JSON substring operators, so the two can never drift into escaping their
1416
+ * operands the same way but comparing them differently.
1417
+ */
1418
+ function buildLikeClause(qi, column, paramRef, insensitive) {
1419
+ const base = insensitive ? qi.dialect.buildInsensitiveLike(column, paramRef) : `${column} LIKE ${paramRef}`;
1420
+ return `${base} ESCAPE '\\'`;
1421
+ }
1422
+ /**
1423
+ * Refuse a {@link JsonFilter} carrying a key that is not a JSON operator, and
1424
+ * refuse a filter that selects a `path` but never compares it.
1425
+ *
1426
+ * Both shapes used to compile to NOTHING. `buildJsonFilterClauses` only ever
1427
+ * emitted a clause for a key it recognized, so `{ path: ['title'],
1428
+ * string_contains: 'x' }`, the Prisma spelling, and an easy typo besides -
1429
+ * produced an empty clause list, the predicate vanished, and the query
1430
+ * returned EVERY row. Inside an `AND` it silently dropped that conjunct, so a
1431
+ * tenant scope written this way widened to the whole table. The equivalent
1432
+ * typo on a scalar column has always thrown; this closes the inconsistency.
1433
+ *
1434
+ * Called from both the SQL-build path and the cache-hit param-collect path, so
1435
+ * a warmed SQL cache cannot skip the check.
1436
+ */
1437
+ function assertJsonFilterKeys(filter, column) {
1438
+ const obj = filter;
1439
+ const present = Object.keys(obj).filter((k) => obj[k] !== undefined);
1440
+ for (const key of present) {
1441
+ if (filters_js_1.JSON_FILTER_KEYS.has(key))
1442
+ continue;
1443
+ const suggestion = JSON_PRISMA_SPELLINGS[key];
1444
+ throw new errors_js_1.ValidationError(`[turbine] Unknown JSON filter operator "${key}" on ${column}.` +
1445
+ (suggestion ? ` Did you mean \`${suggestion}\`?` : '') +
1446
+ ` Supported operators: ${[...filters_js_1.JSON_FILTER_KEYS].sort().join(', ')}.`);
1447
+ }
1448
+ // `mode` and `path` are modifiers, not comparisons: a filter made only of
1449
+ // those compares nothing, which is exactly the silent no-op shape above.
1450
+ if (present.length > 0 && present.every((k) => k === 'path' || k === 'mode')) {
1451
+ throw new errors_js_1.ValidationError(`[turbine] JSON filter on ${column} selects a \`path\` but has no comparison. ` +
1452
+ `Add one of: ${[...filters_js_1.JSON_FILTER_KEYS]
1453
+ .filter((k) => k !== 'path' && k !== 'mode')
1454
+ .sort()
1455
+ .join(', ')}.`);
1456
+ }
1457
+ }
1458
+ /**
1459
+ * The Prisma spelling of each JSON operator, so a migrator who writes the
1460
+ * name they already know gets told the turbine one instead of a bare list.
1461
+ */
1462
+ const JSON_PRISMA_SPELLINGS = {
1463
+ string_contains: 'stringContains',
1464
+ string_starts_with: 'stringStartsWith',
1465
+ string_ends_with: 'stringEndsWith',
1466
+ array_contains: 'contains',
1467
+ startsWith: 'stringStartsWith',
1468
+ endsWith: 'stringEndsWith',
1469
+ };
1470
+ /**
1471
+ * Validate and enumerate the substring comparisons on a JSON filter, in the
1472
+ * fixed {@link JSON_STRING_OPERATORS} order. Shared by the build and collect
1473
+ * paths exactly like {@link jsonRangeEntries}, so both agree on the params
1474
+ * pushed and both throw identically on an invalid shape.
1475
+ */
1476
+ function jsonStringEntries(filter, column) {
1477
+ const entries = [];
1478
+ for (const [op, pattern] of Object.entries(filters_js_1.JSON_STRING_OPERATORS)) {
1479
+ const value = filter[op];
1480
+ if (value === undefined)
1481
+ continue;
1482
+ if (filter.path === undefined) {
1483
+ throw new errors_js_1.ValidationError(`[turbine] JSON operator '${op}' on ${column} requires a \`path\` ` +
1484
+ `(e.g. { path: ['meta', 'title'], ${op}: ${JSON.stringify(value)} }).`);
1485
+ }
1486
+ if (typeof value !== 'string') {
1487
+ throw new errors_js_1.ValidationError(`[turbine] JSON operator '${op}' on ${column} requires a string, got ${JSON.stringify(value)}.`);
1488
+ }
1489
+ entries.push({ op, pattern, value });
1490
+ }
1491
+ return entries;
1492
+ }
1403
1493
  function jsonRangeEntries(_qi, filter, column) {
1404
1494
  const entries = [];
1405
1495
  for (const [op, sqlOp] of Object.entries(filters_js_1.JSON_RANGE_OPERATORS)) {
@@ -1430,6 +1520,7 @@ function jsonRangeEntries(_qi, filter, column) {
1430
1520
  * stays byte-identical to {@link collectJsonFilterParams}.
1431
1521
  */
1432
1522
  function buildJsonFilterClauses(qi, column, filter, params) {
1523
+ assertJsonFilterKeys(filter, column);
1433
1524
  const clauses = [];
1434
1525
  // Lazily bind the path once; reuse the same $N in every extraction clause.
1435
1526
  let pathParamIdx = null;
@@ -1470,17 +1561,25 @@ function buildJsonFilterClauses(qi, column, filter, params) {
1470
1561
  const lhs = typeof value === 'number' ? castJsonNumeric(qi, extract) : extract;
1471
1562
  clauses.push(`${lhs} ${sqlOp} ${qi.p(params.length)}`);
1472
1563
  }
1564
+ // Substring comparisons on the extracted path. The operand is LIKE-escaped
1565
+ // exactly like the scalar `contains` family, so a value containing `%` or
1566
+ // `_` matches literally instead of turning into a wildcard.
1567
+ for (const { pattern, value } of jsonStringEntries(filter, column)) {
1568
+ const extract = pathExtract();
1569
+ params.push(pattern((0, utils_js_1.escapeLike)(value)));
1570
+ clauses.push(buildLikeClause(qi, extract, qi.p(params.length), filter.mode === 'insensitive'));
1571
+ }
1473
1572
  return clauses;
1474
1573
  }
1475
1574
  /**
1476
1575
  * Bind value for a JSON path parameter, encoded per dialect. PostgreSQL's
1477
- * `#>>` takes a `text[]` (the segments as strings — or `nativeForm` when the
1576
+ * `#>>` takes a `text[]` (the segments as strings, or `nativeForm` when the
1478
1577
  * caller has a specific native binding, e.g. JsonFilter's raw path array).
1479
1578
  * Every other engine's JSON function (`json_extract` / `JSON_EXTRACT` /
1480
1579
  * `JSON_VALUE`) takes a `'$'`-rooted JSONPath STRING: binding the raw array
1481
1580
  * would arrive as `'["a"]'` (the driver shims JSON.stringify non-primitive
1482
1581
  * params) and fail at runtime with the engine's bad-JSON-path error. The
1483
- * encoded path stays a bound parameter — never spliced into SQL text — so
1582
+ * encoded path stays a bound parameter, never spliced into SQL text, so
1484
1583
  * the build/collect param mirrors stay in lockstep and injection-safe.
1485
1584
  */
1486
1585
  function jsonPathParam(qi, path, nativeForm) {
@@ -1492,7 +1591,7 @@ function jsonPathParam(qi, path, nativeForm) {
1492
1591
  }
1493
1592
  /**
1494
1593
  * Cast an extracted JSON path text value to a numeric type for range
1495
- * comparison. PostgreSQL uses `(expr)::numeric` (exact — the right way to
1594
+ * comparison. PostgreSQL uses `(expr)::numeric` (exact, the right way to
1496
1595
  * compare JSON numbers, and `::float` would lose precision on big ints);
1497
1596
  * other dialects route through {@link Dialect.castAggregate} (SQLite/MySQL/
1498
1597
  * SQL Server have no `::` operator) as a float cast.
@@ -20,10 +20,10 @@ import type { BuilderCtx } from './where.js';
20
20
  *
21
21
  * Two value shapes need rewriting, both a JS `Date` on a temporal column:
22
22
  *
23
- * 1. A time-of-day column (`time` / `timetz`) — the driver serializes a `Date`
23
+ * 1. A time-of-day column (`time` / `timetz`), the driver serializes a `Date`
24
24
  * as a full ISO timestamp with the process offset and Postgres answers
25
25
  * `22007 invalid input syntax for type time`. Rewritten on every engine.
26
- * 2. A zone-less `date` / `timestamp` column on PostgreSQL — the driver's
26
+ * 2. A zone-less `date` / `timestamp` column on PostgreSQL, the driver's
27
27
  * local-offset serialization stores the PROCESS's calendar fields, so in a
28
28
  * non-UTC process the stored value is wrong and (because the read path
29
29
  * interprets an offset-less value as UTC) does not round-trip. Rewritten to
@@ -85,17 +85,36 @@ export declare function piiColumns(_qi: BuilderCtx, meta: TableMetadata): Set<st
85
85
  export declare function piiFields(_qi: BuilderCtx, meta: TableMetadata): string[];
86
86
  /**
87
87
  * The `RETURNING` / `OUTPUT` selection for a write on this table. A table with
88
- * no PII column returns `'*'` (every column — byte-identical SQL to before);
88
+ * no PII column returns `'*'` (every column, byte-identical SQL to before);
89
89
  * a table WITH PII columns returns an explicit quoted list of every non-PII
90
90
  * column so the PII values never leave the database on a write. A PII-tagged
91
91
  * PRIMARY KEY column is kept in the projection regardless (the returned row
92
- * must stay addressable): tag sensitive data, not keys — a PII PK is
92
+ * must stay addressable): tag sensitive data, not keys, a PII PK is
93
93
  * documented out of scope for stripping. Writes accept no `select`/`includePii`
94
94
  * (unlike reads), so this is the whole write-return policy at the SQL level;
95
95
  * {@link parseWriteRow} remains as a defense-in-depth strip (a no-op once the
96
96
  * SQL already excludes the columns). Derived purely from static per-table
97
97
  * schema metadata, so the write SQL cache needs no extra key segment.
98
98
  */
99
+ /**
100
+ * Return `data` with every `updatedAt`-tagged column the caller did not name
101
+ * set to `now`, or the original object when the table has none.
102
+ *
103
+ * Prisma's `@updatedAt` has no turbine equivalent, so a migrated application
104
+ * had to remember the field on every single update, and the value is usually
105
+ * load-bearing in the response body, so forgetting it is a silent staleness
106
+ * bug rather than a crash. The tag is opt-in per column and never inferred
107
+ * from a column's name, so a schema that does not use it emits byte-identical
108
+ * SQL and an application already managing its own timestamp is untouched.
109
+ *
110
+ * The timestamp is generated CLIENT-side (like Prisma) rather than as a SQL
111
+ * `now()`, so it flows through the same temporal coercion as any other bound
112
+ * `Date` and lands in UTC on every engine.
113
+ *
114
+ * An explicit value always wins, including an explicit `null`: naming the
115
+ * column is a statement of intent.
116
+ */
117
+ export declare function applyUpdatedAtColumns(qi: BuilderCtx, data: Record<string, unknown>): Record<string, unknown>;
99
118
  export declare function writeReturningColumns(qi: BuilderCtx): ReturningSelection;
100
119
  /**
101
120
  * String form of {@link writeReturningColumns} for a `SELECT` list (the
@@ -132,7 +151,7 @@ export declare function assertNoGeneratedColumns(qi: BuilderCtx, data: Record<st
132
151
  *
133
152
  * Supports plain values and atomic operator objects ({ set, increment,
134
153
  * decrement, multiply, divide }). An operator object is detected ONLY when
135
- * it has EXACTLY one key that is one of the 5 operator keys — this avoids
154
+ * it has EXACTLY one key that is one of the 5 operator keys, this avoids
136
155
  * misinterpreting JSON column values like `{ set: 'x' }` as operators
137
156
  * (real operator objects always have exactly one key, and a plain JSON
138
157
  * payload that happens to have a single `set` key is extremely unusual).