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
@@ -7,12 +7,19 @@
7
7
  * and execution rather than filter-shape bookkeeping.
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.MAX_NAMED_ORDER_KEYS = exports.VECTOR_DISTANCE_COMPARATORS = exports.VECTOR_METRIC_OPERATORS = exports.TEXT_SEARCH_KEYS = exports.ARRAY_UNIQUE_KEYS = exports.ARRAY_OPERATOR_KEYS = exports.JSONB_UNIQUE_KEYS = exports.JSON_FILTER_KEYS = exports.JSON_STRING_OPERATORS = exports.JSON_RANGE_OPERATORS = exports.JSONB_OPERATOR_KEYS = exports.UPDATE_OPERATOR_KEYS = exports.RELATION_FILTER_WRAPPER_SET = exports.RELATION_FILTER_WRAPPERS = exports.COLUMN_REF_OPERATORS = void 0;
10
+ exports.MAX_NAMED_ORDER_KEYS = exports.VECTOR_DISTANCE_COMPARATORS = exports.VECTOR_METRIC_OPERATORS = exports.TEXT_SEARCH_KEYS = exports.ARRAY_UNIQUE_KEYS = exports.ARRAY_OPERATOR_KEYS = exports.JSONB_UNIQUE_KEYS = exports.JSON_FILTER_KEYS = exports.JSON_STRING_OPERATORS = exports.JSON_RANGE_OPERATORS = exports.JSONB_OPERATOR_KEYS = exports.UPDATE_OPERATOR_KEYS = exports.isColumnRef = exports.RELATION_FILTER_WRAPPER_SET = exports.RELATION_FILTER_WRAPPERS = exports.COLUMN_REF_OPERATORS = void 0;
11
11
  exports.isWhereOperator = isWhereOperator;
12
+ exports.isPlainRecord = isPlainRecord;
13
+ exports.setOwn = setOwn;
12
14
  exports.isUnmatchedPlainObject = isUnmatchedPlainObject;
13
- exports.isColumnRef = isColumnRef;
15
+ exports.dropUndefinedKeys = dropUndefinedKeys;
16
+ exports.operandColumnKind = operandColumnKind;
17
+ exports.describeOperand = describeOperand;
18
+ exports.assertOperatorOperands = assertOperatorOperands;
19
+ exports.assertJsonNotOperand = assertJsonNotOperand;
20
+ exports.assertEqualityOperand = assertEqualityOperand;
21
+ exports.cursorFieldValue = cursorFieldValue;
14
22
  exports.fingerprintOperatorShape = fingerprintOperatorShape;
15
- exports.assertBindableEqualsOperand = assertBindableEqualsOperand;
16
23
  exports.sortedKeys = sortedKeys;
17
24
  exports.sortedEntries = sortedEntries;
18
25
  exports.fingerprintJsonFilterShape = fingerprintJsonFilterShape;
@@ -34,7 +41,12 @@ exports.dedupeOrderEntries = dedupeOrderEntries;
34
41
  exports.dedupeColumnList = dedupeColumnList;
35
42
  exports.normalizeOrderBy = normalizeOrderBy;
36
43
  exports.isEmptyOrderBy = isEmptyOrderBy;
44
+ exports.assertJsonFilterKeys = assertJsonFilterKeys;
45
+ exports.holdsUndefinedValue = holdsUndefinedValue;
46
+ exports.findUndefinedPath = findUndefinedPath;
47
+ exports.assertArrayFilter = assertArrayFilter;
37
48
  const errors_js_1 = require("../errors.js");
49
+ const column_ref_js_1 = require("./column-ref.js");
38
50
  const types_js_1 = require("./types.js");
39
51
  const utils_js_1 = require("./utils.js");
40
52
  // ---------------------------------------------------------------------------
@@ -53,19 +65,40 @@ function isWhereOperator(value) {
53
65
  return keys.length > 0 && keys.every((k) => utils_js_1.OPERATOR_KEYS.has(k));
54
66
  }
55
67
  /**
56
- * True for a *plain object literal* that reached an equality fallthrough
57
- * without matching any known filter shape, the misspelled-operator case.
58
- * Class instances (Buffer for bytea, Decimal wrappers, ...) are legitimate
59
- * bind values and return false, as do arrays and Dates.
68
+ * THE plain-object test for argument trees: an object literal (or a
69
+ * prototype-less object), never an array, Date, Buffer, typed array or other
70
+ * class instance. Class instances are values a driver binds (a Decimal, an
71
+ * interval wrapper), so no walker over caller arguments may descend into one.
60
72
  */
61
- function isUnmatchedPlainObject(value) {
62
- if (typeof value !== 'object' || value === null || Array.isArray(value) || value instanceof Date)
63
- return false;
64
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))
73
+ function isPlainRecord(value) {
74
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
65
75
  return false;
66
76
  const proto = Object.getPrototypeOf(value);
67
77
  return proto === Object.prototype || proto === null;
68
78
  }
79
+ /**
80
+ * `target[key] = value`, always as an OWN property. A plain assignment of the
81
+ * key `__proto__` replaces the object's prototype instead, so a caller key
82
+ * spelled that way (`JSON.parse` produces it as an ordinary own key) would
83
+ * vanish from a rebuilt argument object rather than reach the check that
84
+ * refuses it, and a filter would silently stop filtering.
85
+ */
86
+ function setOwn(target, key, value) {
87
+ if (key === '__proto__') {
88
+ Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
89
+ }
90
+ else {
91
+ target[key] = value;
92
+ }
93
+ }
94
+ /**
95
+ * {@link isPlainRecord} read at an equality fallthrough: a plain object that
96
+ * matched no known filter shape is a misspelled operator, while a class
97
+ * instance (Buffer for bytea, a Decimal wrapper) is a legitimate bind value.
98
+ */
99
+ function isUnmatchedPlainObject(value) {
100
+ return isPlainRecord(value);
101
+ }
69
102
  /**
70
103
  * Operator keys that accept a {@link ColumnRef} (`{ col: 'otherField' }`)
71
104
  * value for column-to-column comparison. `in`/`notIn` and the LIKE operators
@@ -76,54 +109,410 @@ exports.COLUMN_REF_OPERATORS = new Set(['equals', 'not', 'gt', 'gte', 'lt', 'lte
76
109
  * THE relation-filter wrappers: the keys whose body is a clause against the
77
110
  * relation's TARGET table rather than against this one.
78
111
  *
79
- * One named list, here, because everything that walks a where clause has to
80
- * agree about them and the copies had already spread. It lived inlined in
81
- * `where-compile.ts` (`'some' in x || 'every' in x || …`, the SQL compiler's
82
- * own answer), again in `normalizeRelationFilter` (where.ts) as the negated
83
- * conjunction of the same five, again in `cli/pii-predicate-guard.ts`, and a
84
- * fourth time in `prisma-compat.ts` SPLIT across a
85
- * `RELATION_QUANTIFIERS` set (`some`/`every`/`none`) plus two inline
86
- * `k === 'is' || k === 'isNot'` tests, which is the copy most likely to drift
87
- * because half of it does not read as a list and a grep for the list does not
88
- * find it.
89
- *
90
- * The failure mode is not cosmetic: a wrapper the SQL compiler treats as a
91
- * relation filter but a WALKER does not is a wrapper whose body reaches the
92
- * builder unwalked. That is precisely the operand-position channel this
93
- * release closed elsewhere.
112
+ * Everything that walks a where clause has to agree about them: a wrapper the
113
+ * SQL compiler treats as a relation filter but a walker does not is a wrapper
114
+ * whose body reaches the builder unwalked. The SQL walk (`walkWhere` in
115
+ * where-compile.ts), the bare to-one normalization (`normalizeRelationFilter`
116
+ * in where.ts) and the PII predicate guard (`cli/pii-predicate-guard.ts`) all
117
+ * read this list. `prisma-compat.ts` still carries its own copy
118
+ * (`RELATION_QUANTIFIERS` plus inline `is` / `isNot` tests).
94
119
  *
95
120
  * `query/` is the right home rather than `cli/` because the direction of the
96
121
  * dependency is fixed: `cli/` and the prisma-compat shim may import from the
97
122
  * query path, and the query path may never import from `cli/`
98
123
  * (`scripts/check-import-cycles.mjs`).
99
- *
100
- * ## STATUS: a landing spot, not yet a deduplication. Read this before trusting it.
101
- *
102
- * NOTHING IMPORTS THIS YET. The three copies listed above are all still in
103
- * place and still hand-synced; declaring the canonical home did not by itself
104
- * move any of them onto it. It is kept, rather than deleted as unused, because
105
- * `cli/pii-predicate-guard.ts` names this module as where its own copy wants to
106
- * go, and deleting the destination is the one change that makes converging
107
- * harder. Wiring the copies up is a separate change: each one sits on a
108
- * different walk, so each has to be re-tested on its own.
109
- *
110
- * So do not read this constant as evidence that the walkers agree. The comment
111
- * on each copy is still the only thing holding them in step.
112
124
  */
113
125
  exports.RELATION_FILTER_WRAPPERS = ['some', 'none', 'every', 'is', 'isNot'];
114
126
  /** {@link RELATION_FILTER_WRAPPERS} as a membership set, for the walkers. */
115
127
  exports.RELATION_FILTER_WRAPPER_SET = new Set(exports.RELATION_FILTER_WRAPPERS);
128
+ // The column-reference check lives with its constructor; re-exported so the
129
+ // where modules keep one import site for every operand-shape guard.
130
+ var column_ref_js_2 = require("./column-ref.js");
131
+ Object.defineProperty(exports, "isColumnRef", { enumerable: true, get: function () { return column_ref_js_2.isColumnRef; } });
132
+ // ---------------------------------------------------------------------------
133
+ // `undefined` means absent
134
+ // ---------------------------------------------------------------------------
135
+ /**
136
+ * Recursion bound for nested `not` operator objects. Past it the operand walk
137
+ * refuses the filter by name instead of recursing further.
138
+ */
139
+ const OPERATOR_MAX_DEPTH = 64;
116
140
  /**
117
- * Check if an operator value is a column reference: a plain object whose ONLY
118
- * key is `col` with a string value. Anything else (extra keys, non-string
119
- * `col`) is treated as a plain value so JSON payloads that merely contain a
120
- * `col` property keep their equality meaning.
141
+ * `value` without its `undefined`-valued keys, one level deep. Returns the
142
+ * input BY REFERENCE when there are none, so the common path allocates
143
+ * nothing. Symbol-keyed properties (internal brands) survive a copy.
144
+ *
145
+ * This is the where walkers' backstop only. The args seam
146
+ * (`pruneUndefinedArgs` in normalize-args.ts) is the authority on `undefined`:
147
+ * it removes undefined keys at every where position and objects that became
148
+ * empty because of them. The walkers see what the seam missed (internal
149
+ * callers below it), skip a key whose value is `undefined`, and never look
150
+ * inside a value, so a JSON operand is data however it is shaped.
151
+ */
152
+ function dropUndefinedKeys(value) {
153
+ const keys = Object.keys(value);
154
+ for (const key of keys) {
155
+ if (value[key] !== undefined)
156
+ continue;
157
+ const out = copySymbols(value);
158
+ for (const k of keys)
159
+ if (value[k] !== undefined)
160
+ setOwn(out, k, value[k]);
161
+ return out;
162
+ }
163
+ return value;
164
+ }
165
+ /** A fresh object carrying `source`'s own symbol-keyed properties (brands). */
166
+ function copySymbols(source) {
167
+ const out = Object.getPrototypeOf(source) === null ? Object.create(null) : {};
168
+ for (const sym of Object.getOwnPropertySymbols(source)) {
169
+ const descriptor = Object.getOwnPropertyDescriptor(source, sym);
170
+ if (descriptor)
171
+ Object.defineProperty(out, sym, descriptor);
172
+ }
173
+ return out;
174
+ }
175
+ /** {@link OperandColumnKind} for a column type token (case-insensitive). */
176
+ function operandColumnKind(columnType) {
177
+ if (columnType === undefined)
178
+ return 'scalar';
179
+ const t = columnType.toLowerCase();
180
+ if (t === 'json' || t === 'jsonb')
181
+ return 'json';
182
+ if (t.startsWith('_'))
183
+ return 'array';
184
+ if (t === 'vector')
185
+ return 'vector';
186
+ return 'scalar';
187
+ }
188
+ /**
189
+ * Built-in containers whose instances serialize to `{}` (or nothing useful)
190
+ * when a driver JSON-encodes them, so binding one compares against `'{}'`.
121
191
  */
122
- function isColumnRef(value) {
123
- if (typeof value !== 'object' || value === null || Array.isArray(value) || value instanceof Date)
192
+ const NON_SCALAR_BUILTINS = [
193
+ Map,
194
+ Set,
195
+ WeakMap,
196
+ WeakSet,
197
+ Promise,
198
+ RegExp,
199
+ Error,
200
+ ];
201
+ /**
202
+ * A class instance the driver can bind as one value: one that says how to
203
+ * render itself (`toPostgres`, `toJSON`, or its own `toString`, as a Decimal
204
+ * or an interval wrapper does), and is not a built-in container.
205
+ */
206
+ function isBindableInstance(value) {
207
+ if (NON_SCALAR_BUILTINS.some((ctor) => value instanceof ctor))
208
+ return false;
209
+ const v = value;
210
+ return (typeof v.toPostgres === 'function' ||
211
+ typeof v.toJSON === 'function' ||
212
+ (typeof v.toString === 'function' && v.toString !== Object.prototype.toString));
213
+ }
214
+ /**
215
+ * A value the driver binds as ONE scalar: a primitive, a Date, a Buffer or
216
+ * typed array, or a class instance that renders itself (see
217
+ * {@link isBindableInstance}). A plain object, an array, a Map or Set is not,
218
+ * and neither is a function, a symbol or `undefined`.
219
+ */
220
+ function isBindableScalar(value) {
221
+ switch (typeof value) {
222
+ case 'string':
223
+ case 'number':
224
+ case 'bigint':
225
+ case 'boolean':
226
+ return true;
227
+ case 'object':
228
+ break;
229
+ default:
230
+ return false;
231
+ }
232
+ if (value === null || Array.isArray(value))
233
+ return false;
234
+ // An Invalid Date has no SQL value: drivers send it as `NaN` text, a server
235
+ // error, a thrown RangeError or, inside a JSON-encoded list, `null`, which
236
+ // matched nothing and so negated to every row.
237
+ if (value instanceof Date)
238
+ return !Number.isNaN(value.getTime());
239
+ if (ArrayBuffer.isView(value))
240
+ return true;
241
+ if ((0, column_ref_js_1.isColumnRef)(value) || isPlainRecord(value))
242
+ return false;
243
+ return isBindableInstance(value);
244
+ }
245
+ /** A non-null value operand that fits a column of `kind`. */
246
+ function fitsColumn(value, kind) {
247
+ if (isBindableScalar(value))
248
+ return true;
249
+ if (kind === 'json') {
250
+ if (value === undefined || typeof value === 'function' || typeof value === 'symbol')
251
+ return false;
252
+ if (value instanceof Date)
253
+ return false;
254
+ return !(typeof value === 'object' && value !== null && NON_SCALAR_BUILTINS.some((c) => value instanceof c));
255
+ }
256
+ if (kind === 'array' || kind === 'vector') {
257
+ return Array.isArray(value) && value.every((el) => el === null || isBindableScalar(el) || Array.isArray(el));
258
+ }
259
+ return false;
260
+ }
261
+ /** Short, value-free description of an operand for an error message. */
262
+ function describeOperand(value) {
263
+ if (value === null)
264
+ return 'null';
265
+ if (value === undefined)
266
+ return 'undefined';
267
+ if (Array.isArray(value))
268
+ return 'an array';
269
+ if ((0, column_ref_js_1.isColumnRef)(value))
270
+ return 'a column reference';
271
+ if (value instanceof Date && Number.isNaN(value.getTime()))
272
+ return 'an Invalid Date';
273
+ if (isPlainRecord(value)) {
274
+ const keys = Object.keys(value);
275
+ return keys.length === 0 ? 'an empty object' : `an object with keys ${keys.map((k) => `"${k}"`).join(', ')}`;
276
+ }
277
+ if (typeof value === 'object') {
278
+ const name = value.constructor?.name;
279
+ return typeof name === 'string' && name.length > 0 ? `a ${name} instance` : 'an object';
280
+ }
281
+ return `a ${typeof value}`;
282
+ }
283
+ const SCALAR_WORDS = 'a string, number, bigint, boolean, Date or Buffer';
284
+ function acceptedFor(kind) {
285
+ if (kind === 'array')
286
+ return `an array (or ${SCALAR_WORDS})`;
287
+ if (kind === 'vector')
288
+ return 'an array of numbers';
289
+ if (kind === 'json')
290
+ return 'a JSON value';
291
+ return SCALAR_WORDS;
292
+ }
293
+ /** `on "<column>" for table "<table>"`, for refusal messages. Built only on the throw path. */
294
+ function operandSite(ctx) {
295
+ return `on "${ctx.column}" for table "${ctx.table}"`;
296
+ }
297
+ function refuseOperand(key, value, ctx, accepted) {
298
+ throw new errors_js_1.ValidationError(`Invalid operand for '${key}' ${operandSite(ctx)}: received ${describeOperand(value)}. '${key}' takes ${accepted}.`);
299
+ }
300
+ /**
301
+ * Validate every operand of an operator object against its column, and throw
302
+ * {@link ValidationError} (E003) naming the column, the operator, what was
303
+ * received and what is accepted.
304
+ *
305
+ * An operator object must state at least one comparison. One made only of
306
+ * `mode` (or of operators whose value is `undefined`) compares nothing, so it
307
+ * is refused rather than compiled to no predicate: inside a relation filter
308
+ * that silence turned `deleteMany` into a delete of every row. The args seam
309
+ * removes the object when `undefined` emptied it, so Prisma's optional-search
310
+ * idiom (`{ contains: q, mode: 'insensitive' }` with `q` undefined) still
311
+ * reads as "no filter".
312
+ *
313
+ * Called by the SQL build AND the cache-hit param collect with the same
314
+ * arguments, so a warmed template can never bind an operand the cold build
315
+ * refuses. Recurses into a `not` operator object, bounded by `depth`. Runs on
316
+ * every operator object of every query, so it reads only the keys present and
317
+ * builds a message only when it throws.
318
+ */
319
+ function assertOperatorOperands(op, ctx, depth = 0) {
320
+ if (depth > OPERATOR_MAX_DEPTH) {
321
+ throw new errors_js_1.ValidationError(`\`not\` operators on "${ctx.column}" nest too deeply; flatten the filter.`);
322
+ }
323
+ const mode = op.mode;
324
+ if (mode !== undefined && mode !== 'default' && mode !== 'insensitive') {
325
+ throw new errors_js_1.ValidationError(`Invalid mode ${typeof mode === 'string' ? JSON.stringify(mode) : describeOperand(mode)} ${operandSite(ctx)}. ` +
326
+ "Use 'insensitive' or 'default'.");
327
+ }
328
+ // The classifier sends only operator keys here; `mode` was checked above.
329
+ let comparisons = 0;
330
+ for (const key of Object.keys(op)) {
331
+ const value = op[key];
332
+ if (value === undefined)
333
+ continue;
334
+ switch (key) {
335
+ case 'equals':
336
+ case 'not':
337
+ comparisons++;
338
+ if (value !== null && typeof value === 'object')
339
+ assertObjectEqualityOperand(key, value, ctx, depth);
340
+ else if (value !== null && !fitsColumn(value, ctx.kind)) {
341
+ refuseOperand(key, value, ctx, `${acceptedFor(ctx.kind)}, null, or col('field')`);
342
+ }
343
+ break;
344
+ case 'gt':
345
+ case 'gte':
346
+ case 'lt':
347
+ case 'lte':
348
+ comparisons++;
349
+ if (value === null) {
350
+ refuseOperand(key, value, ctx, `${acceptedFor(ctx.kind)} or col('field'); a comparison with null matches no row, use equals: null or not: null`);
351
+ }
352
+ if ((0, column_ref_js_1.isColumnRef)(value))
353
+ break;
354
+ if (ctx.kind !== 'json' && (0, column_ref_js_1.isPlainColumnRefShape)(value)) {
355
+ throw new errors_js_1.ValidationError((0, column_ref_js_1.plainColumnRefMessage)(value, key, ctx.column, ctx.table));
356
+ }
357
+ if (!fitsColumn(value, ctx.kind))
358
+ refuseOperand(key, value, ctx, `${acceptedFor(ctx.kind)} or col('field')`);
359
+ break;
360
+ case 'in':
361
+ case 'notIn':
362
+ comparisons++;
363
+ assertListOperand(key, value, ctx);
364
+ break;
365
+ case 'contains':
366
+ case 'startsWith':
367
+ case 'endsWith':
368
+ comparisons++;
369
+ if (typeof value !== 'string')
370
+ refuseOperand(key, value, ctx, 'a string');
371
+ break;
372
+ }
373
+ }
374
+ if (comparisons === 0) {
375
+ throw new errors_js_1.ValidationError(`The ${depth > 0 ? '`not` operator object' : 'filter'} ${operandSite(ctx)} names no operator: received ` +
376
+ `${describeOperand(op)}. Add one (for example { contains: 'x', mode: 'insensitive' }), or leave ` +
377
+ `"${ctx.column}" out of the filter to apply no condition to it.`);
378
+ }
379
+ }
380
+ /** An object at `equals` / `not`: a column reference, or a `not` operator object to negate. */
381
+ function assertObjectEqualityOperand(key, value, ctx, depth) {
382
+ if ((0, column_ref_js_1.isColumnRef)(value)) {
383
+ if (ctx.kind === 'json') {
384
+ refuseOperand(key, value, ctx, `${acceptedFor('json')}; column references are not supported on JSON columns`);
385
+ }
386
+ return;
387
+ }
388
+ if (ctx.kind === 'json') {
389
+ if (key === 'not')
390
+ assertJsonNotOperand(value, ctx);
391
+ if (!fitsColumn(value, 'json'))
392
+ refuseOperand(key, value, ctx, `${acceptedFor('json')} or null`);
393
+ return;
394
+ }
395
+ if ((0, column_ref_js_1.isPlainColumnRefShape)(value)) {
396
+ throw new errors_js_1.ValidationError((0, column_ref_js_1.plainColumnRefMessage)(value, key, ctx.column, ctx.table));
397
+ }
398
+ if (key === 'not' && isPlainRecord(value)) {
399
+ if (Object.keys(value).length === 0) {
400
+ refuseOperand(key, value, ctx, `a value, null, a column reference or an operator object such as { contains: 'x' }`);
401
+ }
402
+ if (!isWhereOperator(value)) {
403
+ refuseOperand(key, value, ctx, `${acceptedFor(ctx.kind)}, null, col('field'), or an operator object`);
404
+ }
405
+ assertOperatorOperands(value, ctx, depth + 1);
406
+ return;
407
+ }
408
+ if (!fitsColumn(value, ctx.kind)) {
409
+ refuseOperand(key, value, ctx, `${acceptedFor(ctx.kind)}, null, or col('field')` +
410
+ (isPlainRecord(value) ? ' (an object is a JSON value, accepted only on json/jsonb columns)' : ''));
411
+ }
412
+ }
413
+ /**
414
+ * On a JSON column `not` takes a JSON VALUE (`not: { a: 1 }` compares
415
+ * documents), so `not: { contains: {...} }` was compared as the literal document
416
+ * `{"contains": ...}` and matched nearly every row, while the same spelling on a
417
+ * text column negates the filter. An object made only of filter-operator keys
418
+ * is therefore refused, except `not: { equals: X }`, which negates the
419
+ * whole-document equality. A literal document with such keys stays expressible.
420
+ * Every engine runs it (PowDB too), so the spelling means one thing everywhere.
421
+ */
422
+ function assertJsonNotOperand(value, ctx) {
423
+ if (!isPlainRecord(value))
424
+ return;
425
+ const keys = Object.keys(value).filter((k) => value[k] !== undefined);
426
+ if (keys.length === 0 || (keys.length === 1 && keys[0] === 'equals'))
427
+ return;
428
+ if (!keys.every((k) => exports.JSON_FILTER_KEYS.has(k) || utils_js_1.OPERATOR_KEYS.has(k)))
429
+ return;
430
+ const named = keys.map((k) => `"${k}"`).join(', ');
431
+ throw new errors_js_1.ValidationError(`Invalid \`not\` ${operandSite(ctx)}, a JSON column: an object whose keys are all filter operators (${named}) ` +
432
+ 'would be compared as a literal JSON document and match nearly every row. To negate the filter, write ' +
433
+ `\`NOT: { ${ctx.column}: { ${keys[0]}: ... } }\`. To compare against a document that has these keys, ` +
434
+ 'pass it as `not: jsonValue({ ... })` or `not: { equals: { ... } }`.');
435
+ }
436
+ /** `in` / `notIn`: an array whose elements fit the column (or are null). */
437
+ function assertListOperand(key, value, ctx) {
438
+ if (!Array.isArray(value))
439
+ refuseOperand(key, value, ctx, 'an array of values');
440
+ const elementKind = ctx.kind === 'array' ? 'array' : ctx.kind;
441
+ for (const element of value) {
442
+ if (element === null || fitsColumn(element, elementKind))
443
+ continue;
444
+ throw new errors_js_1.ValidationError(`Invalid element in '${key}' ${operandSite(ctx)}: received ${describeOperand(element)}. ` +
445
+ `Every element must be ${acceptedFor(elementKind)} (or null).`);
446
+ }
447
+ }
448
+ /**
449
+ * Validate a BARE value (implicit equality, `{ id: 5 }`) against its column.
450
+ * Shared by the build and collect paths of every where scope.
451
+ */
452
+ function assertEqualityOperand(value, ctx) {
453
+ if (value === null)
454
+ return;
455
+ const where = `on "${ctx.column}" for table "${ctx.table}"`;
456
+ if ((0, column_ref_js_1.isColumnRef)(value)) {
457
+ throw new errors_js_1.ValidationError(`A column reference ${where} needs an operator: write { equals: col(${JSON.stringify(value.col)}) }` +
458
+ (ctx.kind === 'json' ? ', on a non-JSON column (references are not supported on JSON columns).' : '.'));
459
+ }
460
+ if (ctx.kind === 'json') {
461
+ if (typeof value === 'function' || typeof value === 'symbol') {
462
+ throw new errors_js_1.ValidationError(`Invalid value ${where}: received ${describeOperand(value)}. Pass a JSON value.`);
463
+ }
464
+ return;
465
+ }
466
+ if ((0, column_ref_js_1.isPlainColumnRefShape)(value)) {
467
+ throw new errors_js_1.ValidationError((0, column_ref_js_1.plainColumnRefMessage)(value, 'equals', ctx.column, ctx.table));
468
+ }
469
+ if (isPlainRecord(value)) {
470
+ const keys = Object.keys(value);
471
+ throw new errors_js_1.ValidationError(keys.length === 0
472
+ ? `Empty filter object ${where}. Provide a value or an operator like { gt: 1 }.`
473
+ : `Unknown operator${keys.length > 1 ? 's' : ''} ${keys.map((k) => `"${k}"`).join(', ')} ${where}. ` +
474
+ `Supported operators: ${[...utils_js_1.OPERATOR_KEYS].join(', ')}.`);
475
+ }
476
+ if (fitsColumn(value, ctx.kind))
477
+ return;
478
+ throw new errors_js_1.ValidationError(`Invalid value ${where}: received ${describeOperand(value)}. A bare value takes ${acceptedFor(ctx.kind)}` +
479
+ (Array.isArray(value) ? '; to match any of several values use { in: [...] }.' : '.'));
480
+ }
481
+ /** `{ equals: v }` written as an object literal with that one key. */
482
+ function isEqualsWrapper(value) {
483
+ if (!isPlainRecord(value))
124
484
  return false;
125
485
  const keys = Object.keys(value);
126
- return keys.length === 1 && keys[0] === 'col' && typeof value.col === 'string';
486
+ return keys.length === 1 && keys[0] === 'equals';
487
+ }
488
+ /**
489
+ * One `cursor` field's value, checked and unwrapped. A cursor value is the
490
+ * value the last row holds in that column, so it takes the bare-equality rule
491
+ * (`{ equals: v }` is the same as `v`); an operator object, a list on a scalar
492
+ * column or a column reference is refused (E003) in terms of cursors, not
493
+ * filters. NULL is a row's value only in a column that can hold it: in a NOT
494
+ * NULL column (the primary key included) no row has it, so it is refused
495
+ * rather than placing the page nowhere. Shared by every engine's cursor path.
496
+ */
497
+ function cursorFieldValue(field, written, ctx) {
498
+ const value = isEqualsWrapper(written) ? written.equals : written;
499
+ const site = `\`cursor\` field "${field}" on table "${ctx.table}"`;
500
+ if (value === null) {
501
+ if (ctx.nullable)
502
+ return null;
503
+ throw new errors_js_1.ValidationError(`Invalid ${site}: null. "${ctx.column}" is NOT NULL, so no row holds null there and the cursor names no ` +
504
+ "position. Pass the last row's value, or leave `cursor` out for the first page.");
505
+ }
506
+ const columnRef = (0, column_ref_js_1.isColumnRef)(value) || (0, column_ref_js_1.isPlainColumnRefShape)(value);
507
+ const operatorShaped = isPlainRecord(value) && ctx.kind !== 'json';
508
+ const list = Array.isArray(value) && ctx.kind === 'scalar';
509
+ if (columnRef || operatorShaped || list) {
510
+ throw new errors_js_1.ValidationError(`Invalid ${site}: received ${describeOperand(value)}. A cursor value is the value the last row holds in ` +
511
+ `"${field}" (for example \`cursor: { ${field}: 42 }\`), written bare or as \`{ equals: 42 }\`. ` +
512
+ 'To filter rows, use `where`.');
513
+ }
514
+ assertEqualityOperand(value, ctx);
515
+ return value;
127
516
  }
128
517
  /**
129
518
  * Fingerprint the SHAPE of a where-operator object. Null-valued `equals` /
@@ -132,43 +521,35 @@ function isColumnRef(value) {
132
521
  * warmed by `{ not: 5 }` would serve `{ not: null }` with a desynced param list.
133
522
  *
134
523
  * Column references ({@link ColumnRef}) compile the referenced column into the
135
- * SQL TEXT (no param bound), so the referenced field name is part of the shape
136
- *: `{ equals: { col: 'a' } }` and `{ equals: { col: 'b' } }` must never share
137
- * a cache entry. The name is JSON-encoded so exotic field names cannot collide
524
+ * SQL TEXT (no param bound), so the referenced field name is part of the
525
+ * shape: `{ equals: col('a') }` and `{ equals: col('b') }` must never share a
526
+ * cache entry. The name is JSON-encoded so exotic field names cannot collide
138
527
  * with other fingerprint tokens.
528
+ *
529
+ * A `not` operator object compiles to `NOT (<inner>)`, so its whole inner shape
530
+ * is part of the token, with the inner object's EFFECTIVE mode (its own, else
531
+ * the inherited one). Keys holding `undefined` are skipped, as the builders skip them.
139
532
  */
140
- function fingerprintOperatorShape(value) {
533
+ function fingerprintOperatorShape(value, inheritedMode, depth = 0) {
141
534
  const obj = value;
535
+ const effectiveMode = obj.mode !== undefined ? obj.mode : inheritedMode;
142
536
  const opKeys = Object.keys(obj)
143
- .filter((k) => k !== 'mode')
537
+ .filter((k) => k !== 'mode' && obj[k] !== undefined)
144
538
  .map((k) => {
145
539
  const v = obj[k];
146
540
  if ((k === 'equals' || k === 'not') && v === null)
147
541
  return `${k}:null`;
148
- if (exports.COLUMN_REF_OPERATORS.has(k) && isColumnRef(v))
542
+ if (exports.COLUMN_REF_OPERATORS.has(k) && (0, column_ref_js_1.isColumnRef)(v))
149
543
  return `${k}:col(${JSON.stringify(v.col)})`;
544
+ if (k === 'not' && depth < OPERATOR_MAX_DEPTH && isWhereOperator(v)) {
545
+ return `not:${fingerprintOperatorShape(v, effectiveMode, depth + 1)}`;
546
+ }
150
547
  return k;
151
548
  })
152
549
  .sort();
153
- const modeStr = value.mode === 'insensitive' ? ':i' : '';
550
+ const modeStr = effectiveMode === 'insensitive' ? ':i' : '';
154
551
  return `op(${opKeys.join(',')}${modeStr})`;
155
552
  }
156
- /**
157
- * Guard for the value of an `equals` operator reaching the plain-equality
158
- * operator path. A plain object literal can only legitimately be an equality
159
- * value on a json/jsonb column, and those route to the JSONB filter branch
160
- * BEFORE the operator branch, so any plain object that reaches here is a
161
- * mistake (e.g. `{ equals: { foo: 1 } }` on a text column). Shared by the
162
- * SQL-build path and the cache-hit param-collect path so a warmed cache can
163
- * never skip the check.
164
- */
165
- function assertBindableEqualsOperand(value, column) {
166
- if (!isUnmatchedPlainObject(value))
167
- return;
168
- throw new errors_js_1.ValidationError(`Plain-object value for operator 'equals' on ${column}: ` +
169
- `objects are only valid 'equals' values on JSON (json/jsonb) columns, ` +
170
- `where 'equals' is the JSONB containment filter.`);
171
- }
172
553
  /**
173
554
  * Object keys in sorted order, mirroring the canonical order used by every
174
555
  * cache fingerprint. The SQL-build and cache-hit param-collect paths MUST
@@ -325,7 +706,9 @@ exports.ARRAY_UNIQUE_KEYS = new Set(['has', 'hasEvery', 'hasSome', 'isEmpty']);
325
706
  * concrete value is part of the shape; the other operators are value-invariant.
326
707
  */
327
708
  function fingerprintArrayFilterShape(filter) {
328
- const keys = Object.keys(filter).sort();
709
+ const keys = Object.keys(filter)
710
+ .filter((k) => filter[k] !== undefined)
711
+ .sort();
329
712
  const suffix = filter.isEmpty === undefined ? '' : `:empty=${filter.isEmpty ? 'true' : 'false'}`;
330
713
  return `${keys.join(',')}${suffix}`;
331
714
  }
@@ -669,24 +1052,34 @@ function assertDroppedDirection(key, value, table) {
669
1052
  * has a meaning that degrades as the list grows (every added column makes the
670
1053
  * result strictly less distinct, converging on the plain query), so a long list
671
1054
  * is self-limiting in a way a long sort is not.
1055
+ *
1056
+ * Every element must be a field name: anything else (an `undefined` hole, a
1057
+ * number) is E003 here, the first place either caller reads the list.
672
1058
  */
673
- function dedupeColumnList(meta, columns) {
1059
+ function dedupeColumnList(meta, columns, label = 'distinct') {
1060
+ for (let i = 0; i < columns.length; i++) {
1061
+ const key = columns[i];
1062
+ if (typeof key === 'string' && key.length > 0)
1063
+ continue;
1064
+ throw new errors_js_1.ValidationError(`\`${label}\`${meta?.name ? ` on table "${meta.name}"` : ''} takes field names, but element ${i} is ` +
1065
+ `${key === undefined ? 'undefined' : describeOperand(key)}. Pass each field as a string, or leave the ` +
1066
+ `list out to apply no ${label}.`);
1067
+ }
674
1068
  if (columns.length < 2)
675
1069
  return null;
676
1070
  const seen = new Map();
677
1071
  let dropped;
678
1072
  const kept = [];
679
1073
  for (const key of columns) {
680
- const column = typeof key === 'string' ? resolvedOrderColumn(meta, key) : null;
681
- const first = column === null ? undefined : seen.get(column);
682
- if (first !== undefined && column !== null) {
1074
+ const column = resolvedOrderColumn(meta, key);
1075
+ const first = seen.get(column);
1076
+ if (first !== undefined) {
683
1077
  if (dropped === undefined)
684
1078
  dropped = [];
685
1079
  dropped.push({ key, first, resolved: column });
686
1080
  continue;
687
1081
  }
688
- if (column !== null)
689
- seen.set(column, key);
1082
+ seen.set(column, key);
690
1083
  kept.push(key);
691
1084
  }
692
1085
  return dropped ? { columns: kept, dropped } : null;
@@ -717,3 +1110,147 @@ function isEmptyOrderBy(orderBy) {
717
1110
  }
718
1111
  return orderBy === undefined || orderBy === null;
719
1112
  }
1113
+ // ---------------------------------------------------------------------------
1114
+ // JSON and array filter keys, and `undefined` inside a filter
1115
+ // ---------------------------------------------------------------------------
1116
+ /**
1117
+ * Refuse a {@link JsonFilter} carrying a key that is not a JSON operator, and
1118
+ * refuse a filter that selects a `path` but never compares it.
1119
+ *
1120
+ * Both shapes used to compile to NOTHING. `buildJsonFilterClauses` only ever
1121
+ * emitted a clause for a key it recognized, so `{ path: ['title'],
1122
+ * string_contains: 'x' }`, the Prisma spelling, and an easy typo besides -
1123
+ * produced an empty clause list, the predicate vanished, and the query
1124
+ * returned EVERY row. Inside an `AND` it silently dropped that conjunct, so a
1125
+ * tenant scope written this way widened to the whole table. The equivalent
1126
+ * typo on a scalar column has always thrown; this closes the inconsistency.
1127
+ *
1128
+ * Called from both the SQL-build path and the cache-hit param-collect path, so
1129
+ * a warmed SQL cache cannot skip the check.
1130
+ */
1131
+ function assertJsonFilterKeys(filter, column) {
1132
+ const obj = filter;
1133
+ const present = Object.keys(obj).filter((k) => obj[k] !== undefined);
1134
+ for (const key of present) {
1135
+ if ((0, column_ref_js_1.isColumnRef)(obj[key])) {
1136
+ throw new errors_js_1.ValidationError(`A column reference cannot be the '${key}' operand of a JSON filter on ${column}: on a json/jsonb ` +
1137
+ 'column the operand is a JSON value. Compare the column with col() only on non-JSON columns.');
1138
+ }
1139
+ }
1140
+ for (const key of present) {
1141
+ if (exports.JSON_FILTER_KEYS.has(key))
1142
+ continue;
1143
+ const suggestion = JSON_PRISMA_SPELLINGS[key];
1144
+ throw new errors_js_1.ValidationError(`Unknown JSON filter operator "${key}" on ${column}.` +
1145
+ (suggestion ? ` Did you mean \`${suggestion}\`?` : '') +
1146
+ ` Supported operators: ${[...exports.JSON_FILTER_KEYS].sort().join(', ')}.`);
1147
+ }
1148
+ // `mode` and `path` are modifiers, not comparisons: a filter made only of
1149
+ // those (or of keys holding `undefined`) compares nothing, which is exactly
1150
+ // the silent no-op shape above.
1151
+ if (present.length === 0) {
1152
+ throw new errors_js_1.ValidationError(`JSON filter on ${column} names no comparison. Add one of: ${[...exports.JSON_FILTER_KEYS]
1153
+ .filter((k) => k !== 'path' && k !== 'mode')
1154
+ .sort()
1155
+ .join(', ')}.`);
1156
+ }
1157
+ if (present.every((k) => k === 'path' || k === 'mode')) {
1158
+ throw new errors_js_1.ValidationError(`JSON filter on ${column} selects a \`path\` but has no comparison. ` +
1159
+ `Add one of: ${[...exports.JSON_FILTER_KEYS]
1160
+ .filter((k) => k !== 'path' && k !== 'mode')
1161
+ .sort()
1162
+ .join(', ')}.`);
1163
+ }
1164
+ }
1165
+ /**
1166
+ * The Prisma spelling of each JSON operator, so a migrator who writes the
1167
+ * name they already know gets told the turbine one instead of a bare list.
1168
+ */
1169
+ const JSON_PRISMA_SPELLINGS = {
1170
+ string_contains: 'stringContains',
1171
+ string_starts_with: 'stringStartsWith',
1172
+ string_ends_with: 'stringEndsWith',
1173
+ array_contains: 'contains',
1174
+ startsWith: 'stringStartsWith',
1175
+ endsWith: 'stringEndsWith',
1176
+ };
1177
+ /**
1178
+ * Whether {@link findUndefinedPath} would find anything, without building the
1179
+ * path strings. Same walk, same leaves, same plain-object rule, same depth bound.
1180
+ */
1181
+ function holdsUndefinedValue(value, depth) {
1182
+ if (value === undefined)
1183
+ return true;
1184
+ if (typeof value !== 'object' || value === null || depth > 64)
1185
+ return false;
1186
+ if (Array.isArray(value)) {
1187
+ for (let i = 0; i < value.length; i++)
1188
+ if (holdsUndefinedValue(value[i], depth + 1))
1189
+ return true;
1190
+ return false;
1191
+ }
1192
+ const proto = Object.getPrototypeOf(value);
1193
+ if (proto !== Object.prototype && proto !== null)
1194
+ return false;
1195
+ for (const key of Object.keys(value)) {
1196
+ if (holdsUndefinedValue(value[key], depth + 1))
1197
+ return true;
1198
+ }
1199
+ return false;
1200
+ }
1201
+ /**
1202
+ * The key path of the first `undefined` inside a where object (a global filter),
1203
+ * or `null`.
1204
+ * Walks plain objects and arrays; values of any other kind (Dates, Buffers,
1205
+ * column references) are leaves. Key NAMES only, never values, so the path is
1206
+ * safe in an error message.
1207
+ */
1208
+ function findUndefinedPath(value, path, depth) {
1209
+ if (value === undefined)
1210
+ return path || '(root)';
1211
+ if (typeof value !== 'object' || value === null || depth > 64)
1212
+ return null;
1213
+ if (Array.isArray(value)) {
1214
+ for (let i = 0; i < value.length; i++) {
1215
+ const hit = findUndefinedPath(value[i], `${path}[${i}]`, depth + 1);
1216
+ if (hit !== null)
1217
+ return hit;
1218
+ }
1219
+ return null;
1220
+ }
1221
+ const proto = Object.getPrototypeOf(value);
1222
+ if (proto !== Object.prototype && proto !== null)
1223
+ return null;
1224
+ for (const key of Object.keys(value)) {
1225
+ const hit = findUndefinedPath(value[key], path ? `${path}.${key}` : key, depth + 1);
1226
+ if (hit !== null)
1227
+ return hit;
1228
+ }
1229
+ return null;
1230
+ }
1231
+ /**
1232
+ * Refuse an {@link ArrayFilter} that would compile to less than it says: a key
1233
+ * that is not an array operator (it would be dropped, so `{ has: 'a', hsa: 'b' }`
1234
+ * filtered on `has` alone), an `isEmpty` that is not a boolean (it compiled to
1235
+ * nothing), or no comparison at all. Called by the SQL build and the cache-hit
1236
+ * param collect alike.
1237
+ */
1238
+ function assertArrayFilter(filter, column) {
1239
+ const obj = filter;
1240
+ let comparisons = 0;
1241
+ for (const key of Object.keys(obj)) {
1242
+ const value = obj[key];
1243
+ if (value === undefined)
1244
+ continue;
1245
+ if (!exports.ARRAY_OPERATOR_KEYS.has(key)) {
1246
+ throw new errors_js_1.ValidationError(`Unknown array filter operator "${key}" on ${column}. Supported operators: ${[...exports.ARRAY_OPERATOR_KEYS].join(', ')}.`);
1247
+ }
1248
+ if (key === 'isEmpty' && typeof value !== 'boolean') {
1249
+ throw new errors_js_1.ValidationError(`\`isEmpty\` on ${column} takes true or false, received ${describeOperand(value)}.`);
1250
+ }
1251
+ comparisons++;
1252
+ }
1253
+ if (comparisons === 0) {
1254
+ throw new errors_js_1.ValidationError(`Array filter on ${column} names no comparison. Add one of: ${[...exports.ARRAY_OPERATOR_KEYS].join(', ')}.`);
1255
+ }
1256
+ }