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
@@ -59,6 +59,62 @@ export interface BuilderCtx {
59
59
  * `resolveGlobalFilter`.
60
60
  */
61
61
  currentSkip: ResolvedSkipGlobalFilters | undefined;
62
+ /**
63
+ * Record that the statement being COMPILED right now has a SQL text whose
64
+ * LENGTH is a function of an arity the caller chose, rather than of the
65
+ * application's code. Two shapes qualify:
66
+ *
67
+ * 1. a caller-written `AND` / `OR` combinator array (one parenthesised
68
+ * branch per element), marked in {@link buildWhereClause} and its
69
+ * table-scoped twin;
70
+ * 2. an `orderBy` longer than {@link MAX_NAMED_ORDER_KEYS} (one comma-
71
+ * separated term per entry), marked in relations.ts' `buildOrderBy` and
72
+ * `buildRelationOrderClause`.
73
+ *
74
+ * WHY THIS EXISTS, since it is the only "tell the builder something about the
75
+ * shape" callback on this interface. Every distinct SQL text Turbine emits is
76
+ * parsed on the server as a NAMED prepared statement (the name is a hash of
77
+ * the text) and is never DEALLOCATEd. The client-side template cache is an
78
+ * LRU bounded at 1,000 entries; the SERVER side has no bound at all, and each
79
+ * pooled connection accumulates its own. That is fine while the set of SQL
80
+ * texts is fixed by the application's code, which it is for every clause
81
+ * except these: a `where.OR` assembled from a UI multi-select, or an
82
+ * `orderBy` assembled from an "advanced sort" panel, lets a caller mint
83
+ * unbounded distinct statements with no identifier and no value under their
84
+ * control. Measured on PostgreSQL 16: 600 distinct `OR` arities left 600
85
+ * prepared statements and 20.9 MB of CachedPlan memory resident on ONE
86
+ * connection, and 200 further executions of an existing shape reclaimed none
87
+ * of it. The `orderBy` half was measured the same way and is worse in one
88
+ * respect: repeating a single key (`[{id:'asc'}, {id:'asc'}, ...]`) reached
89
+ * eight distinct statements from ONE column, so the arity was not even
90
+ * bounded by the table's width. That particular door is closed separately and
91
+ * more directly, by refusing duplicate sort keys outright
92
+ * ({@link dedupeOrderEntries}); this mark handles what remains, which is
93
+ * the PERMUTATION space of distinct columns.
94
+ *
95
+ * `in: [...]` is deliberately NOT this shape and is not marked: it binds
96
+ * `= ANY($1)`, one parameter whatever the list length, so a thousand-element
97
+ * `in` is still one statement.
98
+ *
99
+ * What the mark does: {@link acquireSql} reads it immediately after the build
100
+ * closure returns and gives the cache entry an EMPTY prepared-statement name,
101
+ * so every execution of that shape goes out unnamed. Unnamed is what bounds
102
+ * the server: node-postgres only skips `Parse` for a statement it has already
103
+ * parsed BY NAME, so an unnamed statement is re-parsed each execution and
104
+ * replaces the single unnamed cached plan source instead of adding to the
105
+ * named table. It is the same mechanism the per-query `forceCustomPlan`
106
+ * option uses, and it changes NO SQL text.
107
+ *
108
+ * The cost is honest and worth stating: a fixed two-branch `OR` (a search box
109
+ * over name and email, say) is not actually variable-arity, and it loses its
110
+ * named statement too, because the builder sees one call and cannot know
111
+ * whether the length varies across calls. It pays one server-side parse per
112
+ * execution and gives up generic-plan promotion, which for a skewed predicate
113
+ * is frequently the better plan anyway. The `orderBy` threshold is chosen so
114
+ * that this cost lands only on shapes that are already unusual: see
115
+ * {@link MAX_NAMED_ORDER_KEYS}.
116
+ */
117
+ markVariableArity(): void;
62
118
  q(name: string): string;
63
119
  p(index: number): string;
64
120
  inParam(values: unknown): unknown;
@@ -76,6 +132,13 @@ export interface BuilderCtx {
76
132
  crossCheckCache(op: string, cacheKey: string, entry: SqlCacheEntry, build: (params: unknown[]) => string, collectedParams: unknown[]): void;
77
133
  readonly jsonEncoding: 'object' | 'positional';
78
134
  readonly camelDateFieldCache: Map<string, Set<string>>;
135
+ /**
136
+ * Per-table memo of `Object.entries(meta.relations)`. See
137
+ * `getRelationEntries` in relations.ts: the nested-row parser walks it once
138
+ * per ROW, so rebuilding the array there was the hottest allocation in the
139
+ * parse path.
140
+ */
141
+ readonly relationEntryCache: Map<string, [string, RelationDef][]>;
79
142
  limitOneClause(): string;
80
143
  buildPagination(limitPh: string | undefined, offsetPh: string | undefined, hasOrderBy: boolean): string;
81
144
  paginationRef(value: unknown, params: unknown[], arg?: string): string;
@@ -138,7 +201,7 @@ interface WhereScope {
138
201
  *
139
202
  * @internal Exposed as package-private for testing via class access.
140
203
  */
141
- export declare function fingerprintWhere(qi: BuilderCtx, where: Record<string, unknown>): string;
204
+ export declare function fingerprintWhere(qi: BuilderCtx, where: Record<string, unknown>, depth?: number): string;
142
205
  /**
143
206
  * Fingerprint the present branches of a normalized relation filter, in the
144
207
  * fixed order some→every→none→is→isNot. A `null` branch tokenizes as
@@ -146,14 +209,14 @@ export declare function fingerprintWhere(qi: BuilderCtx, where: Record<string, u
146
209
  * {@link fingerprintRelFilter} so the FULL inner shape is captured (two
147
210
  * different sub-wheres must never collide on one cached SQL text).
148
211
  */
149
- export declare function fingerprintRelationParts(qi: BuilderCtx, relDef: RelationDef, filterObj: WhereRecord): string[];
212
+ export declare function fingerprintRelationParts(qi: BuilderCtx, relDef: RelationDef, filterObj: WhereRecord, depth?: number): string[];
150
213
  /**
151
214
  * Fingerprint a relation filter sub-where for some/every/none. Thin wrapper
152
215
  * over the unified {@link fingerprintScopedWhere}. When the target table is
153
216
  * unknown, an empty-relations host makes every key scalar (matching the old
154
217
  * `meta?.relations` short-circuit).
155
218
  */
156
- export declare function fingerprintRelFilter(qi: BuilderCtx, targetTable: string, subWhere: Record<string, unknown>): string;
219
+ export declare function fingerprintRelFilter(qi: BuilderCtx, targetTable: string, subWhere: Record<string, unknown>, depth?: number): string;
157
220
  /**
158
221
  * Walk a where clause and push ONLY values into `params`, in the EXACT same
159
222
  * order that `buildWhereClause` pushes them. Used on cache hit to fill params
@@ -161,7 +224,7 @@ export declare function fingerprintRelFilter(qi: BuilderCtx, targetTable: string
161
224
  *
162
225
  * @internal Exposed as package-private for testing.
163
226
  */
164
- export declare function collectWhereParams(qi: BuilderCtx, where: Record<string, unknown>, params: unknown[]): void;
227
+ export declare function collectWhereParams(qi: BuilderCtx, where: Record<string, unknown>, params: unknown[], depth?: number): void;
165
228
  /**
166
229
  * Push a scalar WHERE value's params, mirroring {@link buildScalarClause}'s
167
230
  * emissions exactly. Both resolve the value's shape via the shared
@@ -182,8 +245,8 @@ export declare function collectScalarParams(qi: BuilderCtx, key: string, value:
182
245
  * Shared by every collect site that mirrors buildRelationFilter
183
246
  * (collectWhereParams, collectRelFilterParams, collectAliasWhereParams).
184
247
  */
185
- export declare function collectRelationFilterParams(qi: BuilderCtx, relDef: RelationDef, filterObj: Record<string, unknown>, params: unknown[]): void;
186
- export declare function collectRelFilterParams(qi: BuilderCtx, targetTable: string, subWhere: Record<string, unknown>, params: unknown[]): void;
248
+ export declare function collectRelationFilterParams(qi: BuilderCtx, relDef: RelationDef, filterObj: Record<string, unknown>, params: unknown[], depth?: number): void;
249
+ export declare function collectRelFilterParams(qi: BuilderCtx, targetTable: string, subWhere: Record<string, unknown>, params: unknown[], depth?: number): void;
187
250
  /**
188
251
  * Collect params from operator clauses. Mirrors buildOperatorClauses:
189
252
  * {@link ColumnRef} values compile into the SQL text, so they push NOTHING -
@@ -203,7 +266,9 @@ export declare function collectArrayFilterParams(qi: BuilderCtx, filter: ArrayFi
203
266
  /**
204
267
  * Collect params for a vector distance WHERE filter. Mirrors
205
268
  * {@link buildVectorFilterClauses}: the `$n::vector` query vector first, then
206
- * the comparison threshold(s).
269
+ * the comparison threshold(s), both enumerated AND validated by the shared
270
+ * {@link vectorThresholdEntries} (see there for the production-only bug that
271
+ * inlining the loop on both sides produced).
207
272
  */
208
273
  export declare function collectVectorFilterParams(qi: BuilderCtx, field: string, rawColumn: string, filter: VectorFilter, params: unknown[]): void;
209
274
  /** Build WHERE clause from a where object (supports operators, NULL, OR) */
@@ -267,7 +332,7 @@ export declare function assertMutationHasPredicate(qi: BuilderCtx, operation: 'u
267
332
  * Returns null if no conditions exist.
268
333
  * Supports: equality, operators, NULL, OR, AND, NOT, relation filters (some/every/none).
269
334
  */
270
- export declare function buildWhereClause(qi: BuilderCtx, where: Record<string, unknown>, params: unknown[]): string | null;
335
+ export declare function buildWhereClause(qi: BuilderCtx, where: Record<string, unknown>, params: unknown[], depth?: number): string | null;
271
336
  /**
272
337
  * Emit the SQL clause(s) for one scalar WHERE key onto `andClauses`, pushing
273
338
  * any params. The shape decision comes from the shared
@@ -295,7 +360,7 @@ export declare function aliasWhereScope(qi: BuilderCtx, targetTable: string, met
295
360
  * former hand-mirrored walkers, since it renders the same clauses in the same
296
361
  * ({@link walkWhere}-canonical) key order.
297
362
  */
298
- export declare function buildScopedWhere(qi: BuilderCtx, scope: WhereScope, where: Record<string, unknown>, params: unknown[]): string | null;
363
+ export declare function buildScopedWhere(qi: BuilderCtx, scope: WhereScope, where: Record<string, unknown>, params: unknown[], depth?: number): string | null;
299
364
  /**
300
365
  * Emit the SQL clause(s) for one scalar key of a scoped sub-where. Reproduces
301
366
  * the null / JSON / array / operator / equality fall-through both former
@@ -311,7 +376,7 @@ export declare function buildScopedScalarClause(qi: BuilderCtx, scope: WhereScop
311
376
  * without rebuilding SQL. Serves both {@link collectRelFilterParams} and
312
377
  * {@link collectAliasWhereParams}.
313
378
  */
314
- export declare function collectScopedWhereParams(qi: BuilderCtx, scope: WhereScope, where: Record<string, unknown>, params: unknown[]): void;
379
+ export declare function collectScopedWhereParams(qi: BuilderCtx, scope: WhereScope, where: Record<string, unknown>, params: unknown[], depth?: number): void;
315
380
  /** Param-collect mirror of {@link buildScopedScalarClause}. */
316
381
  export declare function collectScopedScalarParams(qi: BuilderCtx, scope: WhereScope, field: string, value: unknown, params: unknown[]): void;
317
382
  /**
@@ -323,17 +388,24 @@ export declare function collectScopedScalarParams(qi: BuilderCtx, scope: WhereSc
323
388
  * may differ from the pre-unification walkers as long as collisions stay
324
389
  * impossible.
325
390
  */
326
- export declare function fingerprintScopedWhere(qi: BuilderCtx, host: WhereHost, where: Record<string, unknown>): string;
391
+ export declare function fingerprintScopedWhere(qi: BuilderCtx, host: WhereHost, where: Record<string, unknown>, depth?: number): string;
327
392
  /**
328
393
  * Build relation filter SQL: WHERE EXISTS / NOT EXISTS subquery
329
394
  * Supports: some (EXISTS), every (NOT EXISTS ... NOT), none (NOT EXISTS)
330
395
  */
331
- export declare function buildRelationFilter(qi: BuilderCtx, _relName: string, relDef: RelationDef, filterObj: Record<string, unknown>, params: unknown[], parentTable?: string): string | null;
396
+ export declare function buildRelationFilter(qi: BuilderCtx, _relName: string, relDef: RelationDef, filterObj: Record<string, unknown>, params: unknown[], parentTable?: string,
397
+ /**
398
+ * Nesting depth of the WHERE walk that reached this relation filter. Each
399
+ * relation descent is a level too: `{ posts: { some: { comments: { some:
400
+ * … } } } }` recurses through here just as `NOT` does, and is exactly as
401
+ * unbounded without a cap (see {@link assertWhereDepth}).
402
+ */
403
+ depth?: number): string | null;
332
404
  /**
333
405
  * Build WHERE clause conditions for a relation filter subquery.
334
406
  * Uses the target table's column mapping to resolve field names.
335
407
  */
336
- export declare function buildSubWhereForRelation(qi: BuilderCtx, targetTable: string, subWhere: Record<string, unknown>, params: unknown[]): string | null;
408
+ export declare function buildSubWhereForRelation(qi: BuilderCtx, targetTable: string, subWhere: Record<string, unknown>, params: unknown[], depth?: number): string | null;
337
409
  /**
338
410
  * Resolve a column's Postgres type from an arbitrary table's metadata
339
411
  * (relation targets, not just `qi.table`).
@@ -440,6 +512,26 @@ export declare function buildOperatorClauses(qi: BuilderCtx, column: string, op:
440
512
  * gate) so the two paths can never diverge.
441
513
  */
442
514
  export declare function requireFullTextSearch(qi: BuilderCtx): void;
515
+ /**
516
+ * Gate the pathless JSON containment filters (`contains`, and the `equals`
517
+ * spelling that compiles to the same expression) on
518
+ * {@link Dialect.supportsJsonContains}.
519
+ *
520
+ * The engine this exists for is SQLite, whose emulation compares a decoded
521
+ * `json_each.value` against a param bound as JSON TEXT and therefore matched
522
+ * NOTHING, for every operand type: object, array, string and number alike all
523
+ * returned zero rows where PostgreSQL and MySQL returned the document (measured
524
+ * in-process, table in dialect.ts). Fewer rows with no error is precisely what
525
+ * the capability contract exists to convert into a refusal, and since the
526
+ * feature never worked there, refusing it removes nothing.
527
+ *
528
+ * Called from BOTH the build and the param-collect side, and here that is
529
+ * load-bearing rather than symmetric-for-its-own-sake: a {@link JsonFilter}
530
+ * fingerprints by which KEYS are present, not by what they hold, so every
531
+ * `contains` on a column shares one cache entry and a build-only gate would be
532
+ * skipped for the entire warm life of that entry.
533
+ */
534
+ export declare function requireJsonContains(qi: BuilderCtx, clause: 'contains' | 'equals'): void;
443
535
  /**
444
536
  * Gate the array filter operators (`has` / `hasEvery` / `hasSome` / `isEmpty`)
445
537
  * on {@link Dialect.supportsArrayColumns}. They compile to PostgreSQL array
@@ -562,6 +654,29 @@ export declare function buildArrayFilterClauses(qi: BuilderCtx, column: string,
562
654
  * {@link collectVectorFilterParams}.
563
655
  */
564
656
  export declare function buildVectorFilterClauses(qi: BuilderCtx, field: string, rawColumn: string, filter: VectorFilter, params: unknown[]): string[];
657
+ /**
658
+ * Validate and enumerate the distance comparisons on a vector filter, in the
659
+ * fixed {@link VECTOR_DISTANCE_COMPARATORS} order. Shared by the SQL-build path
660
+ * ({@link buildVectorFilterClauses}) and the cache-hit param-collect path
661
+ * ({@link collectVectorFilterParams}), exactly like {@link jsonRangeEntries}.
662
+ *
663
+ * IT IS THE VALIDATION THAT MAKES THIS SHARED, not the enumeration. Both sides
664
+ * used to inline the same `for` loop, and only the build side checked the
665
+ * threshold. That build side does not run on a cache HIT, and outside dev the
666
+ * lockstep cross-check (which re-runs it, and is what masked this) is off, so a
667
+ * warmed template bound `lt: NaN`, `lt: '5'` or `lt: { a: 1 }` straight into
668
+ * the statement. NaN is the one that matters: Postgres sorts it above every
669
+ * real distance, so `distance < NaN` matches EVERY row, i.e. the predicate
670
+ * inverts, silently, in production only. Same bug class as the 0.19.2 /
671
+ * 0.32.1 cache-hit drifts, and the same fix: one function, both paths.
672
+ *
673
+ * The "at least one comparison" refusal lives here too, so the collect path
674
+ * cannot quietly accept a filter the build path rejects.
675
+ */
676
+ export declare function vectorThresholdEntries(filter: VectorFilter, field: string): {
677
+ sqlOp: string;
678
+ threshold: number;
679
+ }[];
565
680
  /**
566
681
  * Build SQL clause for full-text search using to_tsvector @@ to_tsquery.
567
682
  * The config name is validated to prevent injection (only alphanumeric + underscore).