turbine-orm 0.81.0 → 0.90.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 (295) hide show
  1. package/README.md +26 -25
  2. package/dist/checkout.d.ts +27 -2
  3. package/dist/checkout.js +75 -4
  4. package/dist/cjs/checkout.d.ts +27 -2
  5. package/dist/cjs/checkout.js +76 -3
  6. package/dist/cjs/cli/code-first-tags.d.ts +62 -0
  7. package/dist/cjs/cli/code-first-tags.js +124 -0
  8. package/dist/cjs/cli/compile-query.js +9 -0
  9. package/dist/cjs/cli/config.d.ts +35 -6
  10. package/dist/cjs/cli/config.js +17 -1
  11. package/dist/cjs/cli/destructive.d.ts +7 -4
  12. package/dist/cjs/cli/destructive.js +224 -20
  13. package/dist/cjs/cli/error-catalog.js +36 -0
  14. package/dist/cjs/cli/error-text.d.ts +15 -0
  15. package/dist/cjs/cli/error-text.js +40 -0
  16. package/dist/cjs/cli/index.d.ts +62 -4
  17. package/dist/cjs/cli/index.js +568 -108
  18. package/dist/cjs/cli/local-server-guard.d.ts +53 -0
  19. package/dist/cjs/cli/local-server-guard.js +119 -0
  20. package/dist/cjs/cli/local-server-output.d.ts +41 -0
  21. package/dist/cjs/cli/local-server-output.js +103 -0
  22. package/dist/cjs/cli/mcp.d.ts +8 -0
  23. package/dist/cjs/cli/mcp.js +121 -32
  24. package/dist/cjs/cli/migrate.d.ts +113 -5
  25. package/dist/cjs/cli/migrate.js +270 -21
  26. package/dist/cjs/cli/observe.d.ts +6 -1
  27. package/dist/cjs/cli/observe.js +85 -55
  28. package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
  29. package/dist/cjs/cli/pii-predicate-guard.js +98 -37
  30. package/dist/cjs/cli/prisma-resolve.js +17 -13
  31. package/dist/cjs/cli/prisma-schema.js +12 -4
  32. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  33. package/dist/cjs/cli/studio.d.ts +14 -2
  34. package/dist/cjs/cli/studio.js +244 -69
  35. package/dist/cjs/cli/ui.d.ts +1 -1
  36. package/dist/cjs/client.d.ts +558 -85
  37. package/dist/cjs/client.js +1270 -395
  38. package/dist/cjs/connection-guard.d.ts +13 -0
  39. package/dist/cjs/connection-guard.js +28 -0
  40. package/dist/cjs/connection-url.d.ts +51 -16
  41. package/dist/cjs/connection-url.js +67 -34
  42. package/dist/cjs/diagnostics.d.ts +81 -0
  43. package/dist/cjs/diagnostics.js +147 -0
  44. package/dist/cjs/dialect.d.ts +449 -20
  45. package/dist/cjs/dialect.js +117 -76
  46. package/dist/cjs/engine-config.d.ts +7 -6
  47. package/dist/cjs/errors.d.ts +126 -6
  48. package/dist/cjs/errors.js +256 -13
  49. package/dist/cjs/generate.d.ts +57 -7
  50. package/dist/cjs/generate.js +635 -279
  51. package/dist/cjs/global-filters-config.d.ts +59 -0
  52. package/dist/cjs/global-filters-config.js +367 -0
  53. package/dist/cjs/index.d.ts +9 -4
  54. package/dist/cjs/index.js +10 -2
  55. package/dist/cjs/introspect.d.ts +31 -6
  56. package/dist/cjs/introspect.js +108 -50
  57. package/dist/cjs/log-codes.d.ts +12 -0
  58. package/dist/cjs/log-codes.js +77 -0
  59. package/dist/cjs/logger.d.ts +87 -0
  60. package/dist/cjs/logger.js +98 -0
  61. package/dist/cjs/mssql.d.ts +8 -5
  62. package/dist/cjs/mssql.js +352 -65
  63. package/dist/cjs/mysql.d.ts +3 -5
  64. package/dist/cjs/mysql.js +420 -70
  65. package/dist/cjs/nested-write.d.ts +3 -0
  66. package/dist/cjs/nested-write.js +179 -114
  67. package/dist/cjs/pipeline-submittable.d.ts +12 -0
  68. package/dist/cjs/pipeline-submittable.js +118 -44
  69. package/dist/cjs/pipeline.js +21 -26
  70. package/dist/cjs/powdb-introspect.d.ts +5 -2
  71. package/dist/cjs/powdb-introspect.js +20 -15
  72. package/dist/cjs/powdb-shared.d.ts +36 -5
  73. package/dist/cjs/powdb-shared.js +67 -8
  74. package/dist/cjs/powdb.d.ts +4 -0
  75. package/dist/cjs/powdb.js +183 -51
  76. package/dist/cjs/powql.d.ts +394 -51
  77. package/dist/cjs/powql.js +1829 -284
  78. package/dist/cjs/prisma-compat.d.ts +23 -15
  79. package/dist/cjs/prisma-compat.js +507 -372
  80. package/dist/cjs/query/aggregates.d.ts +17 -1
  81. package/dist/cjs/query/aggregates.js +261 -29
  82. package/dist/cjs/query/batched-loader.d.ts +25 -6
  83. package/dist/cjs/query/batched-loader.js +103 -20
  84. package/dist/cjs/query/builder.d.ts +325 -184
  85. package/dist/cjs/query/builder.js +1452 -490
  86. package/dist/cjs/query/column-ref.d.ts +61 -0
  87. package/dist/cjs/query/column-ref.js +73 -0
  88. package/dist/cjs/query/compound-unique.d.ts +21 -0
  89. package/dist/cjs/query/compound-unique.js +98 -18
  90. package/dist/cjs/query/deferred.d.ts +53 -16
  91. package/dist/cjs/query/expr.d.ts +217 -0
  92. package/dist/cjs/query/expr.js +859 -0
  93. package/dist/cjs/query/filters.d.ts +155 -53
  94. package/dist/cjs/query/filters.js +613 -76
  95. package/dist/cjs/query/index.d.ts +2 -0
  96. package/dist/cjs/query/index.js +7 -1
  97. package/dist/cjs/query/json-value.d.ts +43 -0
  98. package/dist/cjs/query/json-value.js +59 -0
  99. package/dist/cjs/query/normalize-args.d.ts +87 -0
  100. package/dist/cjs/query/normalize-args.js +527 -0
  101. package/dist/cjs/query/option-surface.d.ts +16 -4
  102. package/dist/cjs/query/option-surface.js +57 -14
  103. package/dist/cjs/query/relation-names.d.ts +14 -0
  104. package/dist/cjs/query/relation-names.js +77 -18
  105. package/dist/cjs/query/relations.d.ts +30 -3
  106. package/dist/cjs/query/relations.js +219 -63
  107. package/dist/cjs/query/row-lock.d.ts +49 -0
  108. package/dist/cjs/query/row-lock.js +138 -0
  109. package/dist/cjs/query/sql-comment.d.ts +161 -0
  110. package/dist/cjs/query/sql-comment.js +381 -0
  111. package/dist/cjs/query/stream-idle.d.ts +132 -0
  112. package/dist/cjs/query/stream-idle.js +212 -0
  113. package/dist/cjs/query/table-client.d.ts +101 -0
  114. package/dist/cjs/query/table-client.js +74 -0
  115. package/dist/cjs/query/types.d.ts +677 -298
  116. package/dist/cjs/query/utils.d.ts +105 -6
  117. package/dist/cjs/query/utils.js +426 -19
  118. package/dist/cjs/query/warn-registry.d.ts +18 -0
  119. package/dist/cjs/query/warn-registry.js +18 -0
  120. package/dist/cjs/query/where-compile.d.ts +138 -14
  121. package/dist/cjs/query/where-compile.js +513 -29
  122. package/dist/cjs/query/where.d.ts +159 -59
  123. package/dist/cjs/query/where.js +716 -246
  124. package/dist/cjs/query/writes.d.ts +112 -50
  125. package/dist/cjs/query/writes.js +1498 -200
  126. package/dist/cjs/query-events.d.ts +4 -2
  127. package/dist/cjs/query-events.js +3 -1
  128. package/dist/cjs/realtime.d.ts +7 -3
  129. package/dist/cjs/realtime.js +13 -2
  130. package/dist/cjs/schema-builder.d.ts +55 -4
  131. package/dist/cjs/schema-builder.js +382 -18
  132. package/dist/cjs/schema-metadata.d.ts +21 -1
  133. package/dist/cjs/schema-metadata.js +29 -7
  134. package/dist/cjs/schema-sql.d.ts +75 -5
  135. package/dist/cjs/schema-sql.js +497 -45
  136. package/dist/cjs/schema.d.ts +26 -5
  137. package/dist/cjs/schema.js +110 -13
  138. package/dist/cjs/sql-placeholders.d.ts +56 -0
  139. package/dist/cjs/sql-placeholders.js +204 -0
  140. package/dist/cjs/sqlite.d.ts +75 -20
  141. package/dist/cjs/sqlite.js +593 -95
  142. package/dist/cjs/supplied-pool-types.d.ts +48 -0
  143. package/dist/cjs/supplied-pool-types.js +256 -0
  144. package/dist/cjs/transaction-control.d.ts +51 -0
  145. package/dist/cjs/transaction-control.js +316 -0
  146. package/dist/cjs/transaction-state.d.ts +342 -0
  147. package/dist/cjs/transaction-state.js +707 -0
  148. package/dist/cjs/typed-sql.d.ts +37 -3
  149. package/dist/cjs/typed-sql.js +102 -8
  150. package/dist/cli/code-first-tags.d.ts +62 -0
  151. package/dist/cli/code-first-tags.js +120 -0
  152. package/dist/cli/compile-query.js +9 -0
  153. package/dist/cli/config.d.ts +35 -6
  154. package/dist/cli/config.js +16 -1
  155. package/dist/cli/destructive.d.ts +7 -4
  156. package/dist/cli/destructive.js +224 -20
  157. package/dist/cli/error-catalog.js +36 -0
  158. package/dist/cli/error-text.d.ts +15 -0
  159. package/dist/cli/error-text.js +37 -0
  160. package/dist/cli/index.d.ts +62 -4
  161. package/dist/cli/index.js +569 -112
  162. package/dist/cli/local-server-guard.d.ts +53 -0
  163. package/dist/cli/local-server-guard.js +113 -0
  164. package/dist/cli/local-server-output.d.ts +41 -0
  165. package/dist/cli/local-server-output.js +98 -0
  166. package/dist/cli/mcp.d.ts +8 -0
  167. package/dist/cli/mcp.js +121 -34
  168. package/dist/cli/migrate.d.ts +113 -5
  169. package/dist/cli/migrate.js +261 -21
  170. package/dist/cli/observe.d.ts +6 -1
  171. package/dist/cli/observe.js +84 -54
  172. package/dist/cli/pii-predicate-guard.d.ts +43 -23
  173. package/dist/cli/pii-predicate-guard.js +100 -35
  174. package/dist/cli/prisma-resolve.js +15 -11
  175. package/dist/cli/prisma-schema.js +12 -4
  176. package/dist/cli/studio-ui.generated.js +1 -1
  177. package/dist/cli/studio.d.ts +14 -2
  178. package/dist/cli/studio.js +246 -71
  179. package/dist/cli/ui.d.ts +1 -1
  180. package/dist/client.d.ts +558 -85
  181. package/dist/client.js +1275 -400
  182. package/dist/connection-guard.d.ts +13 -0
  183. package/dist/connection-guard.js +27 -0
  184. package/dist/connection-url.d.ts +51 -16
  185. package/dist/connection-url.js +63 -34
  186. package/dist/diagnostics.d.ts +81 -0
  187. package/dist/diagnostics.js +144 -0
  188. package/dist/dialect.d.ts +449 -20
  189. package/dist/dialect.js +115 -43
  190. package/dist/engine-config.d.ts +7 -6
  191. package/dist/errors.d.ts +126 -6
  192. package/dist/errors.js +252 -12
  193. package/dist/generate.d.ts +57 -7
  194. package/dist/generate.js +635 -281
  195. package/dist/global-filters-config.d.ts +59 -0
  196. package/dist/global-filters-config.js +361 -0
  197. package/dist/index.d.ts +9 -4
  198. package/dist/index.js +4 -2
  199. package/dist/introspect.d.ts +31 -6
  200. package/dist/introspect.js +106 -50
  201. package/dist/log-codes.d.ts +12 -0
  202. package/dist/log-codes.js +74 -0
  203. package/dist/logger.d.ts +87 -0
  204. package/dist/logger.js +93 -0
  205. package/dist/mssql.d.ts +8 -5
  206. package/dist/mssql.js +354 -67
  207. package/dist/mysql.d.ts +3 -5
  208. package/dist/mysql.js +423 -73
  209. package/dist/nested-write.d.ts +3 -0
  210. package/dist/nested-write.js +177 -112
  211. package/dist/pipeline-submittable.d.ts +12 -0
  212. package/dist/pipeline-submittable.js +117 -44
  213. package/dist/pipeline.js +21 -26
  214. package/dist/powdb-introspect.d.ts +5 -2
  215. package/dist/powdb-introspect.js +21 -16
  216. package/dist/powdb-shared.d.ts +36 -5
  217. package/dist/powdb-shared.js +65 -8
  218. package/dist/powdb.d.ts +4 -0
  219. package/dist/powdb.js +183 -51
  220. package/dist/powql.d.ts +394 -51
  221. package/dist/powql.js +1836 -291
  222. package/dist/prisma-compat.d.ts +23 -15
  223. package/dist/prisma-compat.js +508 -373
  224. package/dist/query/aggregates.d.ts +17 -1
  225. package/dist/query/aggregates.js +263 -32
  226. package/dist/query/batched-loader.d.ts +25 -6
  227. package/dist/query/batched-loader.js +101 -21
  228. package/dist/query/builder.d.ts +325 -184
  229. package/dist/query/builder.js +1458 -495
  230. package/dist/query/column-ref.d.ts +61 -0
  231. package/dist/query/column-ref.js +66 -0
  232. package/dist/query/compound-unique.d.ts +21 -0
  233. package/dist/query/compound-unique.js +97 -19
  234. package/dist/query/deferred.d.ts +53 -16
  235. package/dist/query/expr.d.ts +217 -0
  236. package/dist/query/expr.js +848 -0
  237. package/dist/query/filters.d.ts +155 -53
  238. package/dist/query/filters.js +597 -72
  239. package/dist/query/index.d.ts +2 -0
  240. package/dist/query/index.js +4 -0
  241. package/dist/query/json-value.d.ts +43 -0
  242. package/dist/query/json-value.js +54 -0
  243. package/dist/query/normalize-args.d.ts +87 -0
  244. package/dist/query/normalize-args.js +517 -0
  245. package/dist/query/option-surface.d.ts +16 -4
  246. package/dist/query/option-surface.js +56 -13
  247. package/dist/query/relation-names.d.ts +14 -0
  248. package/dist/query/relation-names.js +77 -19
  249. package/dist/query/relations.d.ts +30 -3
  250. package/dist/query/relations.js +220 -67
  251. package/dist/query/row-lock.d.ts +49 -0
  252. package/dist/query/row-lock.js +131 -0
  253. package/dist/query/sql-comment.d.ts +161 -0
  254. package/dist/query/sql-comment.js +374 -0
  255. package/dist/query/stream-idle.d.ts +132 -0
  256. package/dist/query/stream-idle.js +203 -0
  257. package/dist/query/table-client.d.ts +101 -0
  258. package/dist/query/table-client.js +70 -0
  259. package/dist/query/types.d.ts +677 -298
  260. package/dist/query/utils.d.ts +105 -6
  261. package/dist/query/utils.js +414 -18
  262. package/dist/query/warn-registry.d.ts +18 -0
  263. package/dist/query/warn-registry.js +18 -0
  264. package/dist/query/where-compile.d.ts +138 -14
  265. package/dist/query/where-compile.js +508 -31
  266. package/dist/query/where.d.ts +159 -59
  267. package/dist/query/where.js +711 -248
  268. package/dist/query/writes.d.ts +112 -50
  269. package/dist/query/writes.js +1496 -202
  270. package/dist/query-events.d.ts +4 -2
  271. package/dist/query-events.js +3 -1
  272. package/dist/realtime.d.ts +7 -3
  273. package/dist/realtime.js +13 -2
  274. package/dist/schema-builder.d.ts +55 -4
  275. package/dist/schema-builder.js +381 -18
  276. package/dist/schema-metadata.d.ts +21 -1
  277. package/dist/schema-metadata.js +28 -7
  278. package/dist/schema-sql.d.ts +75 -5
  279. package/dist/schema-sql.js +494 -44
  280. package/dist/schema.d.ts +26 -5
  281. package/dist/schema.js +109 -13
  282. package/dist/sql-placeholders.d.ts +56 -0
  283. package/dist/sql-placeholders.js +200 -0
  284. package/dist/sqlite.d.ts +75 -20
  285. package/dist/sqlite.js +597 -99
  286. package/dist/supplied-pool-types.d.ts +48 -0
  287. package/dist/supplied-pool-types.js +247 -0
  288. package/dist/transaction-control.d.ts +51 -0
  289. package/dist/transaction-control.js +311 -0
  290. package/dist/transaction-state.d.ts +342 -0
  291. package/dist/transaction-state.js +696 -0
  292. package/dist/typed-sql.d.ts +37 -3
  293. package/dist/typed-sql.js +100 -8
  294. package/package.json +7 -4
  295. package/skills/turbine-orm/SKILL.md +56 -6
@@ -15,10 +15,11 @@
15
15
  *
16
16
  * This module removes the drift BY CONSTRUCTION:
17
17
  * - {@link walkWhere} is the ONE enumeration. It sorts keys canonically,
18
- * skips `undefined`, dispatches the `OR`/`AND`/`NOT` combinators and
19
- * relation filters, and yields a flat, ordered {@link WhereEvent} stream.
20
- * All three consumers iterate this same stream, so their key order and
21
- * combinator structure can never diverge again.
18
+ * skips `undefined` values, applies the one combinator-shape rule
19
+ * ({@link combinatorBranches}), dispatches relation filters, and yields a
20
+ * flat, ordered {@link WhereEvent} stream. All three consumers iterate this
21
+ * same stream, so their key order, combinator structure and view of
22
+ * `undefined` can never diverge.
22
23
  * - {@link classifyScalarForSql} is the ONE scalar-shape decision the SQL
23
24
  * paths use. `buildWhereClause` and `collectWhereParams` BOTH call it with
24
25
  * the same `(rawColumn, value)`, so they always take the same branch and
@@ -34,8 +35,10 @@
34
35
  * step.
35
36
  */
36
37
  import { ValidationError } from '../errors.js';
37
- import { findArrayUniqueKey, findJsonUniqueKey, fingerprintArrayFilterShape, fingerprintJsonFilterShape, fingerprintOperatorShape, isArrayFilter, isJsonFilter, isTextSearchFilter, isUnmatchedPlainObject, isVectorFilter, isWhereOperator, sortedKeys, VECTOR_DISTANCE_COMPARATORS, } from './filters.js';
38
- import { resolveRelation } from './utils.js';
38
+ import { normalizeKeyColumns } from '../schema.js';
39
+ import { isColumnRef } from './column-ref.js';
40
+ import { describeOperand, dropUndefinedKeys, findArrayUniqueKey, findJsonUniqueKey, fingerprintArrayFilterShape, fingerprintJsonFilterShape, fingerprintOperatorShape, isArrayFilter, isJsonFilter, isPlainRecord, isTextSearchFilter, isUnmatchedPlainObject, isVectorFilter, isWhereOperator, operandColumnKind, RELATION_FILTER_WRAPPER_SET, RELATION_FILTER_WRAPPERS, sortedKeys, VECTOR_DISTANCE_COMPARATORS, } from './filters.js';
41
+ import { resolveColumnName, resolveRelation } from './utils.js';
39
42
  /**
40
43
  * Maximum nesting of `OR` / `AND` / `NOT` combinators and relation-filter
41
44
  * descents in one WHERE clause (and in one groupBy `HAVING`).
@@ -76,18 +79,246 @@ export function assertWhereDepth(depth, clause = 'where') {
76
79
  }
77
80
  /** True when a normalized relation filter carries at least one cardinality key. */
78
81
  function isRelationFilterObj(filterObj) {
79
- return ('some' in filterObj || 'every' in filterObj || 'none' in filterObj || 'is' in filterObj || 'isNot' in filterObj);
82
+ return RELATION_FILTER_WRAPPERS.some((wrapper) => wrapper in filterObj);
80
83
  }
81
84
  /**
82
- * THE canonical WHERE enumeration. Yields events in sorted-key order (skipping
83
- * `undefined`), dispatching combinators and relation filters, so every consumer
85
+ * The refusal for a relation filter not made of the relation-filter keys. The
86
+ * SQL is built from those keys alone, so anything else beside them (a typo such
87
+ * as `sum` for `some`, or `iz` for `is`) would be dropped and the filter would
88
+ * match more rows than written; inside a global filter that is a tenancy
89
+ * condition silently not applied. Same rule and wording as the PowDB engine.
90
+ */
91
+ function relationFilterShapeError(table, key, rel, detail) {
92
+ return new ValidationError(`Invalid relation filter "${key}" on "${table}": ${detail}. ` +
93
+ (rel.type === 'belongsTo' || rel.type === 'hasOne'
94
+ ? 'A to-one relation takes `{ is: {...} }`, `{ isNot: {...} }`, a bare where object, or `is: null`.'
95
+ : 'A to-many relation takes `{ some: {...} }`, `{ none: {...} }` or `{ every: {...} }`.'));
96
+ }
97
+ function unrecognizedKeys(keys) {
98
+ return `unrecognized key ${keys.map((k) => `"${k}"`).join(', ')}`;
99
+ }
100
+ /** Throws unless every key of a relation filter is a relation-filter key. */
101
+ function assertRelationFilterKeys(table, key, rel, filterObj) {
102
+ for (const k in filterObj) {
103
+ if (RELATION_FILTER_WRAPPER_SET.has(k))
104
+ continue;
105
+ const unknown = Object.keys(filterObj).filter((name) => !RELATION_FILTER_WRAPPER_SET.has(name));
106
+ throw relationFilterShapeError(table, key, rel, unrecognizedKeys(unknown));
107
+ }
108
+ }
109
+ /**
110
+ * Why a relation-filter key's value cannot be read, or `null` when it can.
111
+ * `some` / `none` / `every` take a where object; `is` / `isNot` take a where
112
+ * object or `null`. Any other value (`null` on a quantifier, a scalar, an
113
+ * array) used to compile to no condition at all, so a filter, or a tenant
114
+ * global filter, built from a missing value matched every row. Shared with the
115
+ * PowDB engine so both refuse the same values in the same words.
116
+ */
117
+ export function relationFilterValueProblem(wrapper, value) {
118
+ if (value === undefined || isPlainRecord(value))
119
+ return null;
120
+ const toOne = wrapper === 'is' || wrapper === 'isNot';
121
+ if (toOne && value === null)
122
+ return null;
123
+ return (`\`${wrapper}\` takes a where object${toOne ? ' or null' : ''}, got ${describeOperand(value)}` +
124
+ (value === null ? '; `some: {}` matches rows with a related row, `none: {}` rows with none' : ''));
125
+ }
126
+ /** Throws unless every relation-filter key holds a value the compiler reads (see {@link relationFilterValueProblem}). */
127
+ function assertRelationFilterValues(table, key, rel, filterObj) {
128
+ for (const wrapper of RELATION_FILTER_WRAPPERS) {
129
+ const problem = relationFilterValueProblem(wrapper, filterObj[wrapper]);
130
+ if (problem !== null)
131
+ throw relationFilterShapeError(table, key, rel, problem);
132
+ }
133
+ }
134
+ /**
135
+ * Why a relation filter reads a to-one key on a to-many relation, or `null`
136
+ * when it does not. `posts: { is: {...} }` compiled to "at least one post
137
+ * matches" and `isNot` to "no post matches", which is not what either word says
138
+ * about a list of rows, and the typed API never offers them there. The
139
+ * quantifiers on a to-one relation are left alone: with at most one related
140
+ * row, `some` / `none` / `every` each have one reading. Shared with the PowDB
141
+ * engine so both refuse the same filters in the same words.
142
+ */
143
+ export function relationCardinalityProblem(relDef, filterObj) {
144
+ if (relDef.type !== 'hasMany' && relDef.type !== 'manyToMany')
145
+ return null;
146
+ for (const wrapper of ['is', 'isNot']) {
147
+ const value = filterObj[wrapper];
148
+ if (value === undefined)
149
+ continue;
150
+ const instead = value === null
151
+ ? wrapper === 'is'
152
+ ? '`none: {}` for rows with no related row'
153
+ : '`some: {}` for rows with at least one'
154
+ : wrapper === 'is'
155
+ ? '`some` (at least one related row matches) or `every` (all of them do)'
156
+ : '`none` (no related row matches)';
157
+ return `\`${wrapper}\` applies to a to-one relation and this one is to-many; write ${instead}`;
158
+ }
159
+ return null;
160
+ }
161
+ /**
162
+ * A bare where object on a to-one relation is an implicit `is`
163
+ * (`{ author: { name: 'x' } }` is `{ author: { is: { name: 'x' } } }`). To-many
164
+ * relations still require a quantifier. The one rule, used by the SQL engines'
165
+ * hosts and by PowDB.
166
+ */
167
+ export function normalizeToOneRelationFilter(relDef, filterObj) {
168
+ if ((relDef.type === 'belongsTo' || relDef.type === 'hasOne') &&
169
+ !RELATION_FILTER_WRAPPERS.some((wrapper) => wrapper in filterObj)) {
170
+ return { is: filterObj };
171
+ }
172
+ return filterObj;
173
+ }
174
+ /**
175
+ * THE combinator-shape rule, shared by every walker over a where (or having)
176
+ * clause: the SQL walk below, `having`, and the `globalFilters` check at
177
+ * construction. Returns the branches `value` stands for, or throws
178
+ * {@link ValidationError} (E003).
179
+ *
180
+ * - `AND` and `NOT` take one where object (a single branch) or an array of
181
+ * them; `OR` takes an array. This is Prisma's shape.
182
+ * - Anything else is refused by name: `OR: { ... }`, a string, a number,
183
+ * `null`, and an array holding one of those. A combinator the compiler
184
+ * cannot read would compile to nothing, so a mistyped filter, or a
185
+ * tenant filter built as `{ OR: { orgId } }`, would match every row.
186
+ * - An `undefined` member is absent and is dropped. When every member was
187
+ * `undefined` the combinator itself is absent and this returns `null`, the
188
+ * same reading the args seam gives it.
189
+ *
190
+ * An array written empty is returned empty; what that means is the caller's
191
+ * rule (in a where clause: `OR: []` matches nothing, `AND: []` and `NOT: []`
192
+ * apply no condition). Returns the input array by reference when it holds no
193
+ * `undefined`, so the common path allocates nothing.
194
+ */
195
+ export function combinatorBranches(key, value, table, clause = 'where') {
196
+ if (value === undefined)
197
+ return null;
198
+ if (Array.isArray(value)) {
199
+ let holes = 0;
200
+ for (let i = 0; i < value.length; i++) {
201
+ const branch = value[i];
202
+ if (branch === undefined)
203
+ holes++;
204
+ else if (!isPlainRecord(branch))
205
+ throw combinatorShapeError(key, table, clause, branch, i);
206
+ }
207
+ if (holes === 0)
208
+ return value;
209
+ if (holes === value.length)
210
+ return null;
211
+ return value.filter((branch) => branch !== undefined);
212
+ }
213
+ if (key !== 'OR' && isPlainRecord(value))
214
+ return [value];
215
+ throw combinatorShapeError(key, table, clause, value);
216
+ }
217
+ function combinatorShapeError(key, table, clause, received, index) {
218
+ const takes = key === 'OR' ? `an array of ${clause} objects` : `a ${clause} object or an array of them`;
219
+ const got = index === undefined ? `received ${describeOperand(received)}` : `element ${index} is ${describeOperand(received)}`;
220
+ const hint = key === 'OR' && index === undefined && isPlainRecord(received)
221
+ ? ' Wrap the object in an array: `OR: [{ ... }]`.'
222
+ : ' A combinator the query cannot read would otherwise apply no condition at all.';
223
+ return new ValidationError(`\`${key}\` in the ${clause} clause on table "${table}" takes ${takes}; ${got}.${hint}`);
224
+ }
225
+ /**
226
+ * The event `OR: []` compiles to: a predicate on the table's own key that no
227
+ * row satisfies (`<pk> IN ()`, bound as an empty list), so an `OR` built from
228
+ * an empty list matches nothing, as in Prisma, instead of everything. It
229
+ * reuses the `in` operator every engine already binds as one length-independent
230
+ * placeholder, so the statement keeps one fixed shape and the three consumers
231
+ * stay in step with no special case.
232
+ */
233
+ function matchNothingEvent(host) {
234
+ const meta = host.tableMeta;
235
+ for (const column of [...(meta.primaryKey ?? []), ...(meta.allColumns ?? [])]) {
236
+ if (operandColumnKind(host.getColumnPgType(column)) !== 'scalar')
237
+ continue;
238
+ return { kind: 'scalar', key: meta.reverseColumnMap?.[column] ?? column, value: { in: [] } };
239
+ }
240
+ throw new ValidationError(`\`OR: []\` on table "${meta.name}" matches no row, and expressing that needs one plain column of the ` +
241
+ 'table, but its schema metadata lists none. Regenerate the client (`npx turbine generate`), or leave ' +
242
+ 'the empty `OR` out of the query.');
243
+ }
244
+ /**
245
+ * The members of an `OR` that are not absent (see {@link isAbsentBranch}),
246
+ * by reference when none is.
247
+ */
248
+ function presentOrBranches(host, branches) {
249
+ for (let i = 0; i < branches.length; i++) {
250
+ if (isAbsentBranch(host, branches[i], 0))
251
+ return branches.filter((branch) => !isAbsentBranch(host, branch, 0));
252
+ }
253
+ return branches;
254
+ }
255
+ /**
256
+ * Whether an `OR` member is absent below the args seam: it names keys, and
257
+ * every one holds `undefined`, a combinator made only of such members, or a
258
+ * relation filter whose keys all hold `undefined`. The seam removes such a
259
+ * member, so the walk drops it too and an internal caller that skips the seam
260
+ * gets the same `OR`. A member written empty (`{}`) is not absent: it applies
261
+ * no condition, which makes its `OR` TRUE.
262
+ */
263
+ function isAbsentBranch(host, branch, depth) {
264
+ if (depth > MAX_WHERE_DEPTH)
265
+ return false;
266
+ let named = false;
267
+ for (const key of Object.keys(branch)) {
268
+ named = true;
269
+ const value = branch[key];
270
+ if (value === undefined)
271
+ continue;
272
+ if (key === 'OR' || key === 'AND' || key === 'NOT') {
273
+ if (Array.isArray(value)) {
274
+ if (value.length > 0 &&
275
+ value.every((m) => m === undefined || (isPlainRecord(m) && isAbsentBranch(host, m, depth + 1)))) {
276
+ continue;
277
+ }
278
+ }
279
+ else if (isPlainRecord(value) && isAbsentBranch(host, value, depth + 1)) {
280
+ continue;
281
+ }
282
+ return false;
283
+ }
284
+ if (isPlainRecord(value) && resolveRelation(host.tableMeta.relations, key) && holdsOnlyUndefined(value))
285
+ continue;
286
+ return false;
287
+ }
288
+ return named;
289
+ }
290
+ /** True for an object that names keys and holds `undefined` under every one. */
291
+ function holdsOnlyUndefined(value) {
292
+ let named = false;
293
+ for (const key of Object.keys(value)) {
294
+ if (value[key] !== undefined)
295
+ return false;
296
+ named = true;
297
+ }
298
+ return named;
299
+ }
300
+ /**
301
+ * THE canonical WHERE enumeration. Yields events in sorted-key order,
302
+ * dispatching combinators and relation filters, so every consumer
84
303
  * (fingerprint, SQL build, param collect) walks identically.
85
304
  *
86
- * Combinator SKIP rules match the historical code exactly: an `OR`/`AND` whose
87
- * value is a non-array or an empty array is skipped entirely (it contributes
88
- * neither SQL, params, nor a fingerprint token); `NOT` is always emitted.
89
- * A key that names a relation but whose value is not a `{ some/every/none/is/
90
- * isNot }` filter falls through to the scalar path, exactly as before.
305
+ * `undefined`: the args seam is the authority and removes it, with every
306
+ * object it emptied, before any of this runs. This walk is the backstop for
307
+ * callers below the seam and reads only `undefined` itself: a key holding it
308
+ * is skipped, a combinator member holding it is dropped, and a relation filter
309
+ * whose wrapper keys all hold it is absent. Values are never looked inside, so
310
+ * a JSON operand is data whatever its shape.
311
+ *
312
+ * Combinators follow {@link combinatorBranches}. `AND` and `NOT` with no
313
+ * branches apply no condition; `OR` with none matches no row
314
+ * ({@link matchNothingEvent}), and an `OR` member made only of `undefined` is
315
+ * dropped ({@link isAbsentBranch}). A `NOT` array negates each branch (`NOT a AND
316
+ * NOT b`), as Prisma does; a `NOT` object negates the object as a whole. A key
317
+ * that names a relation takes only the relation-filter keys (a to-one relation
318
+ * also takes a bare where object, read as `is`), each holding a value the
319
+ * compiler reads ({@link relationFilterValueProblem}), and anything else is
320
+ * refused (E003). Only when the key also names a column, and its object names
321
+ * no relation-filter key, is it that column's filter.
91
322
  */
92
323
  export function walkWhere(host, where) {
93
324
  const events = [];
@@ -95,22 +326,35 @@ export function walkWhere(host, where) {
95
326
  const value = where[key];
96
327
  if (value === undefined)
97
328
  continue;
98
- if (key === 'OR') {
99
- const arr = value;
100
- if (!Array.isArray(arr) || arr.length === 0)
329
+ if (key === 'OR' || key === 'AND' || key === 'NOT') {
330
+ const branches = combinatorBranches(key, value, host.tableMeta.name);
331
+ if (branches === null)
101
332
  continue;
102
- events.push({ kind: 'or', conditions: arr });
103
- continue;
104
- }
105
- if (key === 'AND') {
106
- const arr = value;
107
- if (!Array.isArray(arr) || arr.length === 0)
108
- continue;
109
- events.push({ kind: 'and', conditions: arr });
110
- continue;
111
- }
112
- if (key === 'NOT') {
113
- events.push({ kind: 'not', condition: value });
333
+ if (key === 'OR') {
334
+ if (branches.length === 0) {
335
+ events.push(matchNothingEvent(host));
336
+ }
337
+ else {
338
+ const present = presentOrBranches(host, branches);
339
+ // Every member held only `undefined`: the `OR` itself is absent.
340
+ if (present.length > 0)
341
+ events.push({ kind: 'or', conditions: present });
342
+ }
343
+ }
344
+ else if (branches.length === 0) {
345
+ // `AND: []` and `NOT: []` apply no condition.
346
+ }
347
+ else if (key === 'AND') {
348
+ events.push(Array.isArray(value)
349
+ ? { kind: 'and', conditions: branches }
350
+ : { kind: 'and', conditions: branches, fixedArity: true });
351
+ }
352
+ else if (!Array.isArray(value)) {
353
+ events.push({ kind: 'not', condition: branches[0] });
354
+ }
355
+ else {
356
+ events.push({ kind: 'and', conditions: branches.map((branch) => ({ NOT: branch })) });
357
+ }
114
358
  continue;
115
359
  }
116
360
  // Resolved rather than looked up, so a relation filter accepts the
@@ -119,17 +363,250 @@ export function walkWhere(host, where) {
119
363
  // these events), so resolving here cannot drift between them; the emitted
120
364
  // event carries the DECLARED name, which is what reaches the fingerprint.
121
365
  const resolvedRel = resolveRelation(host.tableMeta.relations, key);
122
- if (resolvedRel && typeof value === 'object' && value !== null && !Array.isArray(value)) {
123
- const filterObj = host.normalizeRelationFilter(resolvedRel.def, value);
366
+ if (resolvedRel && isPlainRecord(value)) {
367
+ const present = dropUndefinedKeys(value);
368
+ if (present !== value && Object.keys(present).length === 0)
369
+ continue;
370
+ const filterObj = host.normalizeRelationFilter(resolvedRel.def, present);
124
371
  if (isRelationFilterObj(filterObj)) {
372
+ assertRelationFilterKeys(host.tableMeta.name, key, resolvedRel.def, filterObj);
373
+ assertRelationFilterValues(host.tableMeta.name, key, resolvedRel.def, filterObj);
374
+ const cardinality = relationCardinalityProblem(resolvedRel.def, filterObj);
375
+ if (cardinality !== null)
376
+ throw relationFilterShapeError(host.tableMeta.name, key, resolvedRel.def, cardinality);
125
377
  events.push({ kind: 'relation', key: resolvedRel.name, relDef: resolvedRel.def, filterObj });
126
378
  continue;
127
379
  }
380
+ // A to-many relation's object names none of its quantifiers. It is a
381
+ // column filter only when the key also names a column.
382
+ if (resolveColumnName(host.tableMeta, key) === undefined) {
383
+ const keys = Object.keys(filterObj);
384
+ const detail = keys.length === 0 ? 'the filter object is empty' : unrecognizedKeys(keys);
385
+ throw relationFilterShapeError(host.tableMeta.name, key, resolvedRel.def, detail);
386
+ }
128
387
  }
129
388
  events.push({ kind: 'scalar', key, value });
130
389
  }
131
390
  return events;
132
391
  }
392
+ export function foldWhere(host, where, hostFor, depth = 0, options) {
393
+ assertWhereDepth(depth);
394
+ const parts = [];
395
+ for (const event of walkWhere(host, where)) {
396
+ switch (event.kind) {
397
+ case 'or':
398
+ parts.push(foldAny(event.conditions.map((cond) => foldWhere(host, cond, hostFor, depth + 1, options))));
399
+ break;
400
+ case 'and':
401
+ for (const cond of event.conditions)
402
+ parts.push(foldWhere(host, cond, hostFor, depth + 1, options));
403
+ break;
404
+ case 'not':
405
+ parts.push(negateFold(foldWhere(host, event.condition, hostFor, depth + 1, options)));
406
+ break;
407
+ case 'relation':
408
+ parts.push(foldRelationFilter(host, event.relDef, event.filterObj, hostFor, depth + 1, options));
409
+ break;
410
+ case 'scalar':
411
+ parts.push(foldScalar(host, event.key, event.value, options));
412
+ break;
413
+ }
414
+ }
415
+ return foldAll(parts);
416
+ }
417
+ /** AND of folded parts; an `'empty'` part contributes no SQL and is skipped. */
418
+ function foldAll(parts) {
419
+ let out = 'empty';
420
+ for (const part of parts) {
421
+ if (part === 'false')
422
+ return 'false';
423
+ if (part === 'row')
424
+ out = 'row';
425
+ else if (part === 'true' && out === 'empty')
426
+ out = 'true';
427
+ }
428
+ return out;
429
+ }
430
+ /**
431
+ * OR of folded branches. A branch that compiles to nothing applies no
432
+ * condition, so it is TRUE for every row and so is the `OR` (the compiler
433
+ * writes it as a constant TRUE).
434
+ */
435
+ function foldAny(parts) {
436
+ let out = 'false';
437
+ for (const part of parts) {
438
+ if (part === 'true' || part === 'empty')
439
+ return 'true';
440
+ if (part === 'row')
441
+ out = 'row';
442
+ }
443
+ return out;
444
+ }
445
+ function negateFold(fold) {
446
+ return fold === 'true' ? 'false' : fold === 'false' ? 'true' : fold;
447
+ }
448
+ /**
449
+ * A relation filter's branches, as the compiler emits them: `some` / `is` is
450
+ * EXISTS over the sub-where, `none` / `isNot` NOT EXISTS, and `every` NOT
451
+ * EXISTS over its negation (nothing at all when its sub-where is empty).
452
+ */
453
+ function foldRelationFilter(host, relDef, filterObj, hostFor, depth, options) {
454
+ const target = hostFor(relDef.to);
455
+ if (!target)
456
+ return 'empty';
457
+ const sub = (wrapper) => foldWhere(target, filterObj[wrapper], hostFor, depth, options);
458
+ // Every row has exactly one related row: a NOT NULL foreign key the engine
459
+ // enforces, to a target whose global filter cannot hide that row.
460
+ const notNull = options?.notNull;
461
+ const alwaysRelated = notNull?.foreignKeys === true &&
462
+ relDef.type === 'belongsTo' &&
463
+ !notNull.hasGlobalFilter(relDef.to) &&
464
+ normalizeKeyColumns(relDef.foreignKey).every((column) => columnIsNotNull(host.tableMeta, column));
465
+ const parts = [];
466
+ for (const wrapper of RELATION_FILTER_WRAPPERS) {
467
+ const value = filterObj[wrapper];
468
+ if (value === undefined)
469
+ continue;
470
+ const exists = wrapper === 'some' || wrapper === 'is';
471
+ const notExists = wrapper === 'none' || wrapper === 'isNot';
472
+ if (value === null) {
473
+ // `is: null` is NOT EXISTS and `isNot: null` EXISTS.
474
+ parts.push(alwaysRelated ? (wrapper === 'is' ? 'false' : 'true') : 'row');
475
+ continue;
476
+ }
477
+ const inner = sub(wrapper);
478
+ const matchesAll = inner === 'empty' || inner === 'true';
479
+ if (exists)
480
+ parts.push(inner === 'false' ? 'false' : alwaysRelated && matchesAll ? 'true' : 'row');
481
+ else if (notExists)
482
+ parts.push(inner === 'false' ? 'true' : alwaysRelated && matchesAll ? 'false' : 'row');
483
+ else
484
+ parts.push(matchesAll ? inner : 'row');
485
+ }
486
+ return foldAll(parts);
487
+ }
488
+ /** Whether the schema says `column` of `meta` never holds NULL. Unknown nullability is not NOT NULL. */
489
+ function columnIsNotNull(meta, column) {
490
+ for (const col of meta.columns) {
491
+ if (col.name === column)
492
+ return col.nullable === false;
493
+ }
494
+ return false;
495
+ }
496
+ /**
497
+ * A column's filter folds as an operator object holding an empty `in` /
498
+ * `notIn` list, and, with {@link WhereFoldOptions.notNull} on a NOT NULL
499
+ * column, as the shapes listed on {@link foldWhere}.
500
+ */
501
+ function foldScalar(host, key, value, options) {
502
+ if (value !== null && !isPlainRecord(value))
503
+ return 'row';
504
+ const column = resolveColumnName(host.tableMeta, key);
505
+ if (column === undefined)
506
+ return 'row';
507
+ const notNull = options?.notNull !== undefined && columnIsNotNull(host.tableMeta, column);
508
+ if (value === null)
509
+ return notNull ? 'false' : 'row';
510
+ const kind = classifyScalarForSql(host, column, value).kind;
511
+ if (kind === 'array')
512
+ return notNull ? foldArrayFilter(value) : 'row';
513
+ if (kind !== 'operator')
514
+ return 'row';
515
+ const json = operandColumnKind(host.getColumnPgType(column)) === 'json';
516
+ return foldOperator(value, json, 0, notNull && !json ? { host, column } : undefined);
517
+ }
518
+ /** On a NOT NULL array column: `hasEvery: []` holds for every row and `hasSome: []` for none. */
519
+ function foldArrayFilter(filter) {
520
+ const parts = [];
521
+ for (const key of Object.keys(filter)) {
522
+ const value = filter[key];
523
+ if (value === undefined)
524
+ continue;
525
+ if ((key === 'hasEvery' || key === 'hasSome') && Array.isArray(value) && value.length === 0) {
526
+ parts.push(key === 'hasEvery' ? 'true' : 'false');
527
+ }
528
+ else {
529
+ parts.push('row');
530
+ }
531
+ }
532
+ return foldAll(parts);
533
+ }
534
+ /**
535
+ * The operator object's clauses, ANDed: `in: []` is FALSE and `notIn: []` TRUE
536
+ * on every engine (an empty `= ANY` / `IN (subquery)` is false even for a NULL
537
+ * column), and a `not` operator object (non-JSON columns) negates its fold.
538
+ * With `notNull` (a NOT NULL, non-JSON column): `not: null` is TRUE and
539
+ * `equals: null` FALSE, an empty `contains` / `startsWith` / `endsWith` is
540
+ * TRUE (`LIKE '%%'`), and a comparison with the column itself is TRUE for
541
+ * `equals` / `gte` / `lte` and FALSE for `not` / `gt` / `lt`.
542
+ */
543
+ function foldOperator(op, json, depth, notNull) {
544
+ const parts = [];
545
+ for (const key of Object.keys(op)) {
546
+ const value = op[key];
547
+ if (value === undefined || key === 'mode')
548
+ continue;
549
+ if ((key === 'in' || key === 'notIn') && Array.isArray(value) && value.length === 0) {
550
+ parts.push(key === 'in' ? 'false' : 'true');
551
+ }
552
+ else if (key === 'not' && !json && depth < MAX_WHERE_DEPTH && isWhereOperator(value)) {
553
+ parts.push(negateFold(foldOperator(value, json, depth + 1, notNull)));
554
+ }
555
+ else if (notNull) {
556
+ parts.push(foldNotNullComparison(key, value, notNull));
557
+ }
558
+ else {
559
+ parts.push('row');
560
+ }
561
+ }
562
+ return foldAll(parts);
563
+ }
564
+ const SELF_COMPARISON_TRUE = new Set(['equals', 'gte', 'lte']);
565
+ const SELF_COMPARISON_FALSE = new Set(['not', 'gt', 'lt']);
566
+ function foldNotNullComparison(key, value, at) {
567
+ if (value === null) {
568
+ if (key === 'not')
569
+ return 'true';
570
+ if (key === 'equals')
571
+ return 'false';
572
+ return 'row';
573
+ }
574
+ if ((key === 'contains' || key === 'startsWith' || key === 'endsWith') && value === '')
575
+ return 'true';
576
+ if (isColumnRef(value) && resolveColumnName(at.host.tableMeta, value.col) === at.column) {
577
+ if (SELF_COMPARISON_TRUE.has(key))
578
+ return 'true';
579
+ if (SELF_COMPARISON_FALSE.has(key))
580
+ return 'false';
581
+ }
582
+ return 'row';
583
+ }
584
+ /**
585
+ * A {@link WhereHost} over plain table metadata, for callers with no SQL
586
+ * builder (PowDB, construction-time checks). Same column-type and to-one rules
587
+ * as the SQL engines' scoped hosts.
588
+ */
589
+ export function metadataWhereHost(meta) {
590
+ return {
591
+ tableMeta: meta,
592
+ normalizeRelationFilter: normalizeToOneRelationFilter,
593
+ getColumnPgType: (column) => meta.dialectTypes?.[column] ?? meta.pgTypes?.[column] ?? 'text',
594
+ isJsonColumnType: (colType) => operandColumnKind(colType) === 'json',
595
+ };
596
+ }
597
+ /**
598
+ * {@link foldWhere} for `table` in `tables`, with plain metadata hosts. Also
599
+ * validates the whole clause shape at every depth (combinators, relation-filter
600
+ * keys and values), so a construction-time check can run it on a static filter.
601
+ */
602
+ export function foldWhereFor(tables, table, where, options) {
603
+ const hostFor = (name) => {
604
+ const meta = Object.hasOwn(tables, name) ? tables[name] : undefined;
605
+ return meta ? metadataWhereHost(meta) : undefined;
606
+ };
607
+ const host = hostFor(table);
608
+ return host ? foldWhere(host, where, hostFor, 0, options) : 'row';
609
+ }
133
610
  /**
134
611
  * Column-aware SQL classification of a scalar WHERE value. Reproduces the SQL
135
612
  * builder's linear fall-through: a JSON/array-shaped value on a non-JSON/array