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
package/dist/cjs/mssql.js CHANGED
@@ -53,7 +53,7 @@
53
53
  * the row selection).
54
54
  * - **Result strategy `'output'`:** create/update/delete/upsert return their rows
55
55
  * from the same statement. `createMany` returns the inserted rows via
56
- * `OUTPUT INSERTED.*` on the multi-row VALUES insert (≤ 1000 rows / 2100 params
56
+ * `OUTPUT INSERTED.*` on the multi-row VALUES insert (≤ 1000 rows / 2098 params
57
57
  * per statement, exceeding either throws a clear `ValidationError`; chunk
58
58
  * yourself or use single `create`s).
59
59
  * - **MERGE concurrency caveat:** `MERGE` is the upsert primitive; under high
@@ -104,15 +104,33 @@ const dialect_js_1 = require("./dialect.js");
104
104
  const errors_js_1 = require("./errors.js");
105
105
  const introspect_js_1 = require("./introspect.js");
106
106
  const optional_peer_import_cjs_1 = __importDefault(require("./optional-peer-import.cjs"));
107
+ const filters_js_1 = require("./query/filters.js");
107
108
  const utils_js_1 = require("./query/utils.js");
108
109
  const schema_js_1 = require("./schema.js");
110
+ const sql_placeholders_js_1 = require("./sql-placeholders.js");
109
111
  // ---------------------------------------------------------------------------
110
112
  // SQL Server / connection limits
111
113
  // ---------------------------------------------------------------------------
112
- /** Max bound parameters per SQL Server statement. */
113
- const MSSQL_MAX_PARAMS = 2100;
114
+ /**
115
+ * Max bound parameters per SQL Server statement, as the driver sends them.
116
+ * The server's limit is 2100 per RPC call, and `mssql` runs a parameterized
117
+ * statement through `sp_executesql`, whose own `@stmt` and `@params` take two
118
+ * of them: measured, 2098 runs and 2099 is refused with error 8003.
119
+ */
120
+ const MSSQL_MAX_PARAMS = 2098;
114
121
  /** Max rows per multi-row INSERT … VALUES statement. */
115
122
  const MSSQL_MAX_INSERT_ROWS = 1000;
123
+ /** Integer column types narrower than 64 bits, in SQL Server and code-first (PostgreSQL) spellings. */
124
+ const SMALL_INTEGER_TYPES = new Set([
125
+ 'tinyint',
126
+ 'smallint',
127
+ 'int',
128
+ 'integer',
129
+ 'int2',
130
+ 'int4',
131
+ 'serial',
132
+ 'smallserial',
133
+ ]);
116
134
  /** Canonical key sort matching the query builder's `sortedEntries`/collect order. */
117
135
  function sortedRelEntries(obj) {
118
136
  return Object.entries(obj).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
@@ -124,8 +142,9 @@ function sortedRelEntries(obj) {
124
142
  * Coerce an arbitrary JS value into something `mssql` can bind. `BIT` accepts JS
125
143
  * booleans directly; `undefined`/`null` → NULL; `bigint` follows the safe-int
126
144
  * policy (number when it fits in 2^53, else a decimal string, no precision
127
- * loss); `Date`/`Uint8Array` pass through; any remaining object/array → JSON text
128
- * (matches how Turbine already pre-serializes JSON filter / `IN`-list params).
145
+ * loss); a `Date` passes through, a `Uint8Array` binds as a Buffer view (VARBINARY);
146
+ * any remaining object/array → JSON text
147
+ * (matches how Turbine already pre-serializes JSON filter params).
129
148
  */
130
149
  function toMssqlParam(value) {
131
150
  if (value === undefined || value === null)
@@ -142,10 +161,45 @@ function toMssqlParam(value) {
142
161
  }
143
162
  if (value instanceof Date)
144
163
  return value;
145
- if (value instanceof Uint8Array)
146
- return value; // Buffer is a Uint8Array subclass
164
+ // The driver infers VARBINARY only for a Buffer; any other Uint8Array was
165
+ // bound as NVARCHAR and refused ("Invalid string"). A Buffer view shares the
166
+ // bytes, so nothing is copied.
167
+ if (value instanceof Uint8Array) {
168
+ return Buffer.isBuffer(value) ? value : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
169
+ }
147
170
  return JSON.stringify(value);
148
171
  }
172
+ /**
173
+ * The JSON text an `in` / `notIn` list binds as. OPENJSON hands each element
174
+ * back as NVARCHAR text, and SQL Server converts that text to the compared
175
+ * column's type, so each element is written as the text that converts to the
176
+ * value `equals` would bind: a bigint as its digits (`JSON.stringify` refuses
177
+ * one) and a Date as its UTC date and time with no zone (the driver binds a
178
+ * Date as DATETIME2 in UTC; `JSON.stringify` adds a `Z`). No text converts to
179
+ * bytes, so a binary element is refused with the spelling that works.
180
+ */
181
+ function mssqlInListJson(values) {
182
+ const parts = values.map((value) => {
183
+ if (typeof value === 'bigint')
184
+ return value.toString();
185
+ if (value instanceof Date) {
186
+ if (Number.isNaN(value.getTime())) {
187
+ throw new errors_js_1.ValidationError('SQL Server cannot bind an Invalid Date in an `in` list. Pass a valid Date, or null.');
188
+ }
189
+ return JSON.stringify(value.toISOString().slice(0, 23));
190
+ }
191
+ if (value instanceof Uint8Array) {
192
+ throw new errors_js_1.UnsupportedFeatureError('a binary value in an `in` / `notIn` list', 'mssql', 'SQL Server receives the list as JSON text, which cannot carry bytes. Write the list as branches: ' +
193
+ '`{ OR: [{ field: value1 }, { field: value2 }] }`, or use the default `join` relation strategy for ' +
194
+ 'a relation keyed on a binary column.');
195
+ }
196
+ if (value === undefined)
197
+ return 'null';
198
+ const bound = toMssqlParam(value);
199
+ return JSON.stringify(bound);
200
+ });
201
+ return `[${parts.join(',')}]`;
202
+ }
149
203
  // ---------------------------------------------------------------------------
150
204
  // Result shaping + error translation
151
205
  // ---------------------------------------------------------------------------
@@ -194,6 +248,17 @@ function coerceBigIntColumns(rows, columns) {
194
248
  }
195
249
  }
196
250
  const EMPTY_RESULT = { rows: [], rowCount: 0 };
251
+ /** A JSON column's stored text as its value; text that is not JSON is returned as stored. */
252
+ function parseJsonText(value) {
253
+ if (typeof value !== 'string')
254
+ return value;
255
+ try {
256
+ return JSON.parse(value);
257
+ }
258
+ catch {
259
+ return value;
260
+ }
261
+ }
197
262
  /**
198
263
  * Augment a `mssql` driver error with the Postgres-shaped `.code` (SQLSTATE) and
199
264
  * detail fields that `wrapPgError` understands, so SQL Server constraint failures
@@ -260,13 +325,48 @@ function normalizeQueryArgs(arg, values) {
260
325
  return { text: arg, params: values ?? [] };
261
326
  return { text: arg.text, params: arg.values ?? values ?? [] };
262
327
  }
263
- /** Bind positional `params[]` to `@p1`, `@p2`, … inputs and run the statement. */
264
- async function runRequest(request, text, params) {
328
+ /**
329
+ * Bind one parameter. Two value kinds get an explicit SQL type, because the
330
+ * driver's inference picks the wrong one for them:
331
+ *
332
+ * - A JS `bigint` is declared BIGINT. Inferred, a small one became INT and one
333
+ * past 2^53 an NVARCHAR string, and a multi-row `VALUES` list types each
334
+ * column by the highest-precedence type across its rows, so a `createMany`
335
+ * mixing the two converted the string to INT and failed ("overflowed an int
336
+ * column").
337
+ * - A `Date` is declared DATETIME2(7). Inferred, it is the legacy DATETIME,
338
+ * whose 1/300 s ticks round milliseconds on the way in, so 23:59:59.999 was
339
+ * stored as the next day's 00:00:00.000. DATETIME2 holds the exact instant
340
+ * and converts to every temporal column type.
341
+ */
342
+ function bindParam(request, name, value, types) {
343
+ if (typeof value === 'bigint' && types?.BigInt !== undefined) {
344
+ request.input(name, types.BigInt, value);
345
+ return;
346
+ }
347
+ if (value instanceof Date && !Number.isNaN(value.getTime()) && typeof types?.DateTime2 === 'function') {
348
+ request.input(name, types.DateTime2(7), value);
349
+ return;
350
+ }
351
+ request.input(name, toMssqlParam(value));
352
+ }
353
+ /** The refusal for a placeholder the shim cannot bind (see rewritePositionalPlaceholders). */
354
+ function refusePlaceholder(message) {
355
+ throw new errors_js_1.ValidationError(message);
356
+ }
357
+ /**
358
+ * Bind positional `params[]` to `@p1`, `@p2`, … inputs and run the statement.
359
+ * A `$n` (the spelling `rawUnsafe` documents for every engine) is rewritten to
360
+ * `@pN` first: T-SQL reads `$1` as a MONEY literal, so it would run, compare
361
+ * against the number 1, and bind nothing.
362
+ */
363
+ async function runRequest(request, text, params, types) {
364
+ const sql = (0, sql_placeholders_js_1.rewritePositionalPlaceholders)(text, 'mssql', params.length, (i) => `@p${i}`, refusePlaceholder);
265
365
  for (let i = 0; i < params.length; i++) {
266
- request.input(`p${i + 1}`, toMssqlParam(params[i]));
366
+ bindParam(request, `p${i + 1}`, params[i], types);
267
367
  }
268
368
  try {
269
- const result = await request.query(text);
369
+ const result = await request.query(sql);
270
370
  return shapeResult(result);
271
371
  }
272
372
  catch (err) {
@@ -352,7 +452,7 @@ class MssqlTxClient {
352
452
  return EMPTY_RESULT;
353
453
  }
354
454
  const request = new this.sqlNS.Request(this.tx ?? this.pool);
355
- return runRequest(request, rawSql, params);
455
+ return runRequest(request, rawSql, params, this.sqlNS);
356
456
  }
357
457
  release() {
358
458
  // The transaction's connection is owned by the mssql Transaction object and is
@@ -396,7 +496,7 @@ class MssqlPool {
396
496
  // biome-ignore lint/suspicious/noExplicitAny: pg-compat query is generic over the row shape.
397
497
  async query(text, values) {
398
498
  const { text: sql, params } = normalizeQueryArgs(text, values);
399
- return runRequest(this.pool.request(), sql, params);
499
+ return runRequest(this.pool.request(), sql, params, this.sqlNS);
400
500
  }
401
501
  async connect() {
402
502
  return new MssqlTxClient(this.pool, this.sqlNS);
@@ -478,8 +578,11 @@ function mssqlColumnType(type, maxLength) {
478
578
  return 'DATE';
479
579
  if (/\bTIME\b/.test(t))
480
580
  return 'TIME';
581
+ // A bounded binary column is VARBINARY(n): VARBINARY(MAX) cannot be a key.
582
+ if (/VARBINARY/.test(t))
583
+ return `VARBINARY(${maxLength ?? 255})`;
481
584
  if (/BYTEA|BLOB/.test(t))
482
- return 'VARBINARY(MAX)';
585
+ return maxLength != null ? `VARBINARY(${maxLength})` : 'VARBINARY(MAX)';
483
586
  if (/VARCHAR/.test(t))
484
587
  return maxLength != null ? `NVARCHAR(${maxLength})` : 'NVARCHAR(255)';
485
588
  if (/CHAR/.test(t))
@@ -521,6 +624,45 @@ function mssqlOutput(returning, alias) {
521
624
  exports.mssqlDialect = {
522
625
  ...dialect_js_1.postgresDialect,
523
626
  name: 'mssql',
627
+ capabilities: Object.freeze({
628
+ distinctOn: false,
629
+ positionalJson: false,
630
+ nullsOrderingClause: false,
631
+ nullsSortHigh: false,
632
+ insensitiveInList: false,
633
+ jsonDocumentEquality: 'text',
634
+ jsonExtractTyped: false,
635
+ jsonExactNumericCast: false,
636
+ numericTextComparison: false,
637
+ enforcesForeignKeys: true,
638
+ partitionLimitPushdown: false,
639
+ utcTimestampRewrite: false,
640
+ paramTypeCasts: false,
641
+ namedEnumTypes: false,
642
+ // No JSON column type: json is NVARCHAR(MAX), so a text column holds JSON.
643
+ structuredValueColumns: 'scalars',
644
+ // The limit the driver can actually send (see MSSQL_MAX_PARAMS), not 2100.
645
+ maxBindParameters: MSSQL_MAX_PARAMS,
646
+ maxInsertRows: MSSQL_MAX_INSERT_ROWS,
647
+ upsertConflictTarget: 'explicit',
648
+ setClauseOrder: 'snapshot',
649
+ reselectLocksRows: false,
650
+ integerDivision: '/',
651
+ updateExpressions: true,
652
+ // MERGE refuses a target row matched twice (error 8672).
653
+ bulkUpsertAppliesRepeatedKeys: false,
654
+ readOnlyTransactions: false,
655
+ connectionParameters: false,
656
+ backendCancel: false,
657
+ idleTransactionTimeout: false,
658
+ abortsTransactionOnError: false,
659
+ connectionDriver: 'engine-driver',
660
+ preparesEveryStatement: false,
661
+ statementSyntax: 'mssql',
662
+ inlineForeignKeys: true,
663
+ // NVARCHAR(MAX) / VARBINARY(MAX) cannot be an index key column.
664
+ unboundedTextKeys: false,
665
+ }),
524
666
  // No RETURNING → the statement emits its rows via OUTPUT INSERTED.* in ONE
525
667
  // round-trip (Phase-0 'output' strategy, executed like 'returning').
526
668
  resultStrategy: 'output',
@@ -544,6 +686,17 @@ exports.mssqlDialect = {
544
686
  // is Postgres-only (out of scope here).
545
687
  supportsLateralJoin: false,
546
688
  supportsPlanCacheMode: false,
689
+ // SQL Server locks through table hints, not a trailing clause: not in this
690
+ // release.
691
+ rowLockModes: [],
692
+ // PERCENTILE_CONT is a window function here (OVER required), so it does not
693
+ // combine with other aggregates in one statement.
694
+ supportsPercentileAggregates: false,
695
+ // MERGE ... WITH (HOLDLOCK).
696
+ supportsBulkUpsert: true,
697
+ // Both MERGE forms alias the target `T` (and the incoming rows `S`), so the
698
+ // conflict-UPDATE reads the existing row through `T.`, never the table name.
699
+ upsertTargetAlias: 'T',
547
700
  // sp_getapplock / sp_releaseapplock exist (used by a future migrate adapter).
548
701
  supportsAdvisoryLock: true,
549
702
  // No in-band EXPLAIN: SQL Server's SHOWPLAN is a session toggle
@@ -602,7 +755,9 @@ exports.mssqlDialect = {
602
755
  // returned `1`, i.e. the strategy silently changed the caller's value
603
756
  // types. Above 2^53 all three keep the string, which is the point of
604
757
  // carrying text at all.
605
- if (t === 'bigint') {
758
+ // `int8` is the spelling a code-first (`defineSchema`) schema records for
759
+ // the same BIGINT column, so it needs the same rule.
760
+ if (t === 'bigint' || t === 'int8') {
606
761
  return {
607
762
  sql: (ref) => `CAST(${ref} AS NVARCHAR(50))`,
608
763
  decode: (value) => {
@@ -620,7 +775,8 @@ exports.mssqlDialect = {
620
775
  // nvarchar cell as a JSON *string* (only `buildRelationSubquery`'s nested
621
776
  // relations are JSON_QUERY-wrapped, never a scalar column), so parsing the
622
777
  // row hands back exactly the string the driver returns for a top-level
623
- // read. Both routes already agree, and casting would only add noise. This
778
+ // read. Both routes already agree, and casting would only add noise; the
779
+ // text is parsed for both by `decodeColumnValue` in the row parser. This
624
780
  // is the divergence MySQL DOES have, where JSON_OBJECT embeds a real JSON
625
781
  // column as a nested value while the driver hands back text.
626
782
  //
@@ -634,19 +790,41 @@ exports.mssqlDialect = {
634
790
  }
635
791
  return undefined;
636
792
  },
793
+ // A json/jsonb column is NVARCHAR(MAX) here, so the driver hands back the
794
+ // document's text, and FOR JSON PATH embeds that text as a JSON string, which
795
+ // the relation parser turns back into the same text. Parsing it once in the
796
+ // row parser gives both paths the value PostgreSQL returns.
797
+ decodeColumnValue(columnType) {
798
+ const t = columnType.toLowerCase();
799
+ return t === 'json' || t === 'jsonb' ? parseJsonText : undefined;
800
+ },
637
801
  castAggregate(expr, target) {
638
802
  return `CAST(${expr} AS ${target === 'int' ? 'INT' : 'FLOAT'})`;
639
803
  },
804
+ // AVG over an integer column is an integer here (56.4 came back 56), and
805
+ // casting the RESULT cannot recover the fraction. Widen the input instead:
806
+ // FLOAT for the narrow integers, the PostgreSQL `avg` value in double
807
+ // precision; DECIMAL for BIGINT, which keeps an exact average (AVG of
808
+ // DECIMAL(38,0) is DECIMAL(38,6)) where FLOAT would round past 2^53.
809
+ averageArgument(columnRef, columnType) {
810
+ const t = columnType.toLowerCase();
811
+ if (t === 'bigint' || t === 'int8' || t === 'bigserial')
812
+ return `CAST(${columnRef} AS DECIMAL(38, 0))`;
813
+ if (SMALL_INTEGER_TYPES.has(t))
814
+ return `CAST(${columnRef} AS FLOAT)`;
815
+ return undefined;
816
+ },
640
817
  // No array params. OPENJSON expands a single JSON-array param into a row set,
641
818
  // keeping ONE placeholder (so the SQL cache stays valid regardless of list
642
819
  // length) and handling the empty list (OPENJSON('[]') → zero rows). SQL Server
643
- // implicitly converts the nvarchar `value` to the column's type, so this works
644
- // for numbers AND strings. OPENJSON requires SQL Server 2016+.
820
+ // implicitly converts the nvarchar `value` to the column's type, and
821
+ // mssqlInListJson writes each element as the text that converts to the value
822
+ // `equals` binds. OPENJSON requires SQL Server 2016+.
645
823
  buildInClause(expr, paramRef, negated) {
646
824
  return `${expr} ${negated ? 'NOT IN' : 'IN'} (SELECT [value] FROM OPENJSON(${paramRef}))`;
647
825
  },
648
826
  inClauseParam(values) {
649
- return JSON.stringify(values ?? []);
827
+ return mssqlInListJson(Array.isArray(values) ? values : []);
650
828
  },
651
829
  // OUTPUT replaces RETURNING, injected mid-statement by the statement builders,
652
830
  // never as a trailing clause.
@@ -659,7 +837,7 @@ exports.mssqlDialect = {
659
837
  },
660
838
  buildBulkInsertStatement(input) {
661
839
  // No UNNEST in SQL Server, emit multi-row VALUES with flattened, named `@pN`
662
- // placeholders. Enforce the engine's 1000-row / 2100-param statement limits.
840
+ // placeholders. Enforce the engine's 1000-row / 2098-param statement limits.
663
841
  const rowCount = input.rowValues.length;
664
842
  const paramCount = input.rowValues.reduce((n, row) => n + row.length, 0);
665
843
  if (rowCount > MSSQL_MAX_INSERT_ROWS) {
@@ -718,6 +896,35 @@ exports.mssqlDialect = {
718
896
  `WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${sourceVals})` +
719
897
  `${out};`);
720
898
  },
899
+ buildBulkUpsertStatement(input) {
900
+ // MERGE, with HOLDLOCK: without it two concurrent MERGEs can both see "not
901
+ // matched" for one key and the second insert fails on the unique key. A
902
+ // NULL in a key column never matches (T.c = S.c is not true), so the row
903
+ // is inserted, as PostgreSQL's ON CONFLICT treats it. `ignore` drops the
904
+ // WHEN MATCHED arm, so only the inserted rows come back through OUTPUT.
905
+ // The bind-parameter limit (MSSQL_MAX_PARAMS) is enforced by the caller's chunking.
906
+ let n = 0;
907
+ const tuples = input.rowValues.map((row) => `(${row.map(() => this.paramPlaceholder(++n)).join(', ')})`).join(', ');
908
+ const params = input.rowValues.flat();
909
+ const alias = this.upsertTargetAlias ?? 'T';
910
+ const on = input.conflictColumns.map((c) => `${alias}.${c} = S.${c}`).join(' AND ');
911
+ const cols = input.columns.join(', ');
912
+ let matched = '';
913
+ if (input.update) {
914
+ const update = input.update(params, { target: alias, incoming: 'S' });
915
+ matched =
916
+ `WHEN MATCHED${update.where ? ` AND (${update.where})` : ''} ` +
917
+ `THEN UPDATE SET ${update.setClauses.join(', ')} `;
918
+ }
919
+ return {
920
+ sql: `MERGE INTO ${input.table} WITH (HOLDLOCK) AS ${alias} ` +
921
+ `USING (VALUES ${tuples}) AS S (${cols}) ON (${on}) ` +
922
+ matched +
923
+ `WHEN NOT MATCHED THEN INSERT (${cols}) VALUES (${input.columns.map((c) => `S.${c}`).join(', ')})` +
924
+ `${mssqlOutput(input.returning, 'INSERTED')};`,
925
+ params,
926
+ };
927
+ },
721
928
  // UPDATE/DELETE inject OUTPUT mid-statement (between SET and WHERE / FROM and
722
929
  // WHERE), a trailing clause would be invalid T-SQL.
723
930
  buildUpdateStatement(input) {
@@ -761,6 +968,35 @@ exports.mssqlDialect = {
761
968
  buildJsonPathExtract(column, pathParamRef) {
762
969
  return `JSON_VALUE(${column}, ${pathParamRef})`;
763
970
  },
971
+ // `JSON_VALUE` returns the same text for 5 and "5", so `{ path, equals }`
972
+ // reads the value's JSON TYPE from OPENJSON over the parent of the path
973
+ // (type 0 null, 1 string, 2 number, 3 boolean) and compares within it.
974
+ // Every part is bound, so the SQL text is the same for every operand type.
975
+ // Numbers compare as float (5 equals 5.0; beyond 2^53 precision is lost);
976
+ // strings compare with a binary collation, i.e. case-sensitively, whatever
977
+ // the column's collation. Arrays and objects are refused: OPENJSON returns
978
+ // their stored text, so whitespace and key order would decide the match.
979
+ buildJsonPathEquals(input) {
980
+ const { value, path } = input;
981
+ if (path.length === 0) {
982
+ throw new errors_js_1.UnsupportedFeatureError(`JSON \`equals\` on the whole document (${input.label})`, 'mssql', 'SQL Server stores the document as text, so whitespace and key order would decide the match. Compare a ' +
983
+ 'scalar with `{ path: [...], equals }`, or run the query on PostgreSQL or MySQL.');
984
+ }
985
+ if (value !== null && typeof value === 'object') {
986
+ throw new errors_js_1.UnsupportedFeatureError(`JSON path \`equals\` with ${Array.isArray(value) ? 'an array' : 'an object'} (${input.label})`, 'mssql', 'SQL Server returns an array or object at a path as its stored text, so whitespace and key order would ' +
987
+ 'decide the match. Compare a scalar at a deeper path, or run the query on PostgreSQL or MySQL.');
988
+ }
989
+ const type = value === null ? 0 : typeof value === 'string' ? 1 : typeof value === 'boolean' ? 3 : 2;
990
+ const text = value === null ? null : String(value);
991
+ const parent = input.bind(input.encodePath(path.slice(0, -1)));
992
+ const key = input.bind(String(path[path.length - 1]));
993
+ const typeRef = input.bind(type);
994
+ const v = input.bind(text);
995
+ return (`EXISTS (SELECT 1 FROM OPENJSON(${input.column}, ${parent}) AS [jpe] ` +
996
+ `WHERE [jpe].[key] = ${key} AND [jpe].[type] = ${typeRef} AND ([jpe].[type] = 0 ` +
997
+ `OR ([jpe].[type] = 2 AND TRY_CONVERT(float, [jpe].[value]) = TRY_CONVERT(float, ${v})) ` +
998
+ `OR ([jpe].[type] <> 2 AND [jpe].[value] = ${v} COLLATE Latin1_General_BIN2)))`);
999
+ },
764
1000
  // ---- Type mapping -------------------------------------------------------
765
1001
  typeToTypeScript(dialectType, nullable) {
766
1002
  return mssqlTypeToTs(dialectType, nullable);
@@ -771,6 +1007,18 @@ exports.mssqlDialect = {
771
1007
  buildColumnType(input) {
772
1008
  return mssqlColumnType(input.type, input.maxLength);
773
1009
  },
1010
+ /**
1011
+ * SQL Server has no enum type: an NVARCHAR column long enough for the
1012
+ * longest label (at least 255), and a CHECK that admits only the labels.
1013
+ * The CHECK compares under the column's collation, so on a case-insensitive
1014
+ * default collation `'ACTIVE'` passes for a label `'active'`, as it does in
1015
+ * every other text comparison there.
1016
+ */
1017
+ buildEnumColumn(column, labels) {
1018
+ const literals = labels.map((label) => `N'${label.replace(/'/g, "''")}'`);
1019
+ const longest = Math.max(255, ...labels.map((label) => label.length));
1020
+ return { type: 'VARCHAR', maxLength: longest, check: `${column} IN (${literals.join(', ')})` };
1021
+ },
774
1022
  buildColumnDefinition(input) {
775
1023
  const isSerial = /serial/i.test(input.type);
776
1024
  const parts = [input.name, this.buildColumnType(input)];
@@ -880,7 +1128,7 @@ exports.mssqlDialect = {
880
1128
  * as a string.
881
1129
  *
882
1130
  * **Param-push order** strictly mirrors `collectRelationSubqueryParams`:
883
- * - manyToMany / to-many with limit|orderBy: `where` → `limit` → nested;
1131
+ * - manyToMany / to-many with limit|offset|orderBy: `where` → `limit` → `offset` → nested;
884
1132
  * - to-one / unordered-unlimited to-many: nested → `where` (no limit).
885
1133
  */
886
1134
  function buildForJsonSubquery(dialect, ctx) {
@@ -888,9 +1136,27 @@ function buildForJsonSubquery(dialect, ctx) {
888
1136
  const q = (name) => dialect.quoteIdentifier(name);
889
1137
  const qTarget = q(targetTable);
890
1138
  const qParent = q(parentRef);
891
- const orderEntries = spec !== true && spec.orderBy ? Object.entries(spec.orderBy).filter(([, dir]) => dir !== undefined) : [];
1139
+ // `orderByEntries` flattens the array form (`[{ views: 'desc' }, { id: 'asc' }]`)
1140
+ // into the same entry list as the object form, as the core relation builder
1141
+ // does; `Object.entries` read an array's indexes as field names.
1142
+ const orderEntries = spec !== true && spec.orderBy ? (0, filters_js_1.orderByEntries)(spec.orderBy).filter(([, dir]) => dir !== undefined) : [];
892
1143
  const hasOrder = orderEntries.length > 0;
893
1144
  const hasLimit = spec !== true && spec.limit !== undefined;
1145
+ // `skip` arrives folded into `offset` (the args seam does that, and refuses
1146
+ // it on a to-one relation).
1147
+ const offset = spec === true ? undefined : spec.offset;
1148
+ const hasOffset = offset !== undefined;
1149
+ /** Bind the relation offset after the limit, the order the param-collect mirror pushes them. */
1150
+ const bindOffset = () => {
1151
+ if (!hasOffset)
1152
+ return undefined;
1153
+ const n = Number(offset);
1154
+ if (!Number.isSafeInteger(n) || n < 0) {
1155
+ throw new errors_js_1.ValidationError(`relation offset on "${targetTable}" must be a non-negative integer, received: ${String(offset)}`);
1156
+ }
1157
+ params.push(n);
1158
+ return dialect.paramPlaceholder(params.length);
1159
+ };
894
1160
  /**
895
1161
  * `<alias>.<col> AS [<field>]` selection for the FOR JSON object keys.
896
1162
  *
@@ -924,7 +1190,10 @@ function buildForJsonSubquery(dialect, ctx) {
924
1190
  return cols;
925
1191
  };
926
1192
  /** ORDER BY + OFFSET/FETCH paging clause for a to-many FOR JSON subquery. */
927
- const buildPaging = (a, limitPlaceholder) => {
1193
+ const buildPaging = (a, limitPlaceholder, offsetPlaceholder) => {
1194
+ const offsetRows = `OFFSET ${offsetPlaceholder ?? '0'} ROWS`;
1195
+ const fetch = limitPlaceholder !== undefined ? ` FETCH NEXT ${limitPlaceholder} ROWS ONLY` : '';
1196
+ const paged = limitPlaceholder !== undefined || offsetPlaceholder !== undefined;
928
1197
  if (hasOrder) {
929
1198
  const orderBy = orderEntries
930
1199
  .map(([k, dir]) => {
@@ -957,19 +1226,15 @@ function buildForJsonSubquery(dialect, ctx) {
957
1226
  return `${a}.${q(col)} ${safeDir}`;
958
1227
  })
959
1228
  .join(', ');
960
- // FOR JSON permits ORDER BY; OFFSET/FETCH is only needed to apply a LIMIT.
961
- return limitPlaceholder !== undefined
962
- ? ` ORDER BY ${orderBy} OFFSET 0 ROWS FETCH NEXT ${limitPlaceholder} ROWS ONLY`
963
- : ` ORDER BY ${orderBy}`;
964
- }
965
- if (limitPlaceholder !== undefined) {
966
- return ` ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH NEXT ${limitPlaceholder} ROWS ONLY`;
1229
+ // FOR JSON permits ORDER BY; OFFSET/FETCH is only needed to page.
1230
+ return paged ? ` ORDER BY ${orderBy} ${offsetRows}${fetch}` : ` ORDER BY ${orderBy}`;
967
1231
  }
968
- return '';
1232
+ // OFFSET/FETCH requires an ORDER BY.
1233
+ return paged ? ` ORDER BY (SELECT NULL) ${offsetRows}${fetch}` : '';
969
1234
  };
970
1235
  // ----- manyToMany: JOIN target through the junction table -----------------
971
1236
  if (relDef.type === 'manyToMany') {
972
- return buildForJsonManyToMany(dialect, ctx, { colSelect, buildNested, buildPaging, hasLimit });
1237
+ return buildForJsonManyToMany(dialect, ctx, { colSelect, buildNested, buildPaging, bindOffset, hasLimit });
973
1238
  }
974
1239
  const isToOne = relDef.type === 'belongsTo' || relDef.type === 'hasOne';
975
1240
  // Correlation direction is about WHERE THE FK LIVES, not cardinality:
@@ -988,17 +1253,19 @@ function buildForJsonSubquery(dialect, ctx) {
988
1253
  return `SELECT TOP 1 ${cols} FROM ${qTarget} ${alias} WHERE ${where} FOR JSON PATH, WITHOUT_ARRAY_WRAPPER, INCLUDE_NULL_VALUES`;
989
1254
  }
990
1255
  // ----- hasMany ------------------------------------------------------------
991
- const willWrap = hasLimit || hasOrder;
1256
+ const willWrap = hasLimit || hasOrder || hasOffset;
992
1257
  let nestedCols;
993
1258
  let extra;
994
1259
  let limitPlaceholder;
1260
+ let offsetPlaceholder;
995
1261
  if (willWrap) {
996
- // Wrap order: where → limit → nested.
1262
+ // Wrap order: where → limit → offset → nested.
997
1263
  extra = ctx.buildWhere(alias);
998
1264
  if (hasLimit) {
999
1265
  params.push(Number(spec.limit));
1000
1266
  limitPlaceholder = dialect.paramPlaceholder(params.length);
1001
1267
  }
1268
+ offsetPlaceholder = bindOffset();
1002
1269
  nestedCols = buildNested(alias);
1003
1270
  }
1004
1271
  else {
@@ -1008,7 +1275,7 @@ function buildForJsonSubquery(dialect, ctx) {
1008
1275
  }
1009
1276
  const where = extra ? `${correlation} AND ${extra}` : correlation;
1010
1277
  const cols = [...colSelect(alias), ...nestedCols].join(', ');
1011
- const paging = buildPaging(alias, limitPlaceholder);
1278
+ const paging = buildPaging(alias, limitPlaceholder, offsetPlaceholder);
1012
1279
  return `SELECT ISNULL((SELECT ${cols} FROM ${qTarget} ${alias} WHERE ${where}${paging} FOR JSON PATH, INCLUDE_NULL_VALUES), '[]')`;
1013
1280
  }
1014
1281
  /** `FOR JSON PATH` subquery for a manyToMany relation (JOIN target through junction). */
@@ -1042,17 +1309,18 @@ function buildForJsonManyToMany(dialect, ctx, h) {
1042
1309
  `but referenceKey has ${refKeys.length}.`);
1043
1310
  }
1044
1311
  const correlation = sourceKeys.map((jcol, i) => `${jalias}.${q(jcol)} = ${qParent}.${q(refKeys[i])}`).join(' AND ');
1045
- // Param order mirrors collectRelationSubqueryParams: where → limit → nested.
1312
+ // Param order mirrors collectRelationSubqueryParams: where → limit → offset → nested.
1046
1313
  const extra = ctx.buildWhere(alias);
1047
1314
  let limitPlaceholder;
1048
1315
  if (h.hasLimit) {
1049
1316
  params.push(Number(spec.limit));
1050
1317
  limitPlaceholder = dialect.paramPlaceholder(params.length);
1051
1318
  }
1319
+ const offsetPlaceholder = h.bindOffset();
1052
1320
  const nestedCols = h.buildNested(alias);
1053
1321
  const where = extra ? `${correlation} AND ${extra}` : correlation;
1054
1322
  const cols = [...h.colSelect(alias), ...nestedCols].join(', ');
1055
- const paging = h.buildPaging(alias, limitPlaceholder);
1323
+ const paging = h.buildPaging(alias, limitPlaceholder, offsetPlaceholder);
1056
1324
  return (`SELECT ISNULL((SELECT ${cols} FROM ${qTarget} ${alias} ` +
1057
1325
  `JOIN ${qJunction} ${jalias} ON ${joinOn} WHERE ${where}${paging} ` +
1058
1326
  `FOR JSON PATH, INCLUDE_NULL_VALUES), '[]')`);
@@ -1086,8 +1354,8 @@ const bitIsTrue = (v) => v === true || v === 1 || v === '1' || v === 'true';
1086
1354
  * same logical schema, legacy-first naming, per-column disambiguation, and
1087
1355
  * collision resolution against scalar column fields included.
1088
1356
  */
1089
- function buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, columnsByTable) {
1090
- return (0, introspect_js_1.deriveEngineRelations)(tableNames, foreignKeys, pkByTable, columnsByTable);
1357
+ function buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, columnsByTable, logger) {
1358
+ return (0, introspect_js_1.deriveEngineRelations)(tableNames, foreignKeys, pkByTable, columnsByTable, logger);
1091
1359
  }
1092
1360
  /**
1093
1361
  * Introspect a SQL Server database into the same {@link SchemaMetadata} shape the
@@ -1098,6 +1366,8 @@ function buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, colum
1098
1366
  *
1099
1367
  * @param exec Runs a parameterized (`@p1`, `@p2`, …) query and returns rows.
1100
1368
  * @param schema The SQL Server schema to introspect (default `dbo`).
1369
+ * @param options Optional include / exclude table-name filters, and the logger
1370
+ * that receives relation-naming notes (console when absent).
1101
1371
  */
1102
1372
  async function introspectMssqlWith(exec, schema = 'dbo', options = {}) {
1103
1373
  // ----- Tables -----
@@ -1236,16 +1506,19 @@ async function introspectMssqlWith(exec, schema = 'dbo', options = {}) {
1236
1506
  }
1237
1507
  }
1238
1508
  // ----- Relations -----
1239
- const relationsByTable = buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, columnsByTable);
1509
+ // One field per column, as the PostgreSQL introspector requires.
1510
+ for (const [tableName, cols] of columnsByTable)
1511
+ (0, introspect_js_1.assertDistinctColumnFields)(tableName, cols);
1512
+ const relationsByTable = buildRelationsFromForeignKeys(tableNames, foreignKeys, pkByTable, columnsByTable, options.logger);
1240
1513
  // ----- Assemble TableMetadata -----
1241
- const tables = {};
1514
+ const tables = (0, introspect_js_1.nullRecord)();
1242
1515
  for (const tableName of tableNames) {
1243
1516
  const columns = columnsByTable.get(tableName) ?? [];
1244
- const columnMap = {};
1245
- const reverseColumnMap = {};
1517
+ const columnMap = (0, introspect_js_1.nullRecord)();
1518
+ const reverseColumnMap = (0, introspect_js_1.nullRecord)();
1246
1519
  const dateColumns = new Set();
1247
- const dialectTypes = {};
1248
- const pgTypes = {};
1520
+ const dialectTypes = (0, introspect_js_1.nullRecord)();
1521
+ const pgTypes = (0, introspect_js_1.nullRecord)();
1249
1522
  const allColumns = [];
1250
1523
  for (const col of columns) {
1251
1524
  columnMap[col.field] = col.name;
@@ -1267,7 +1540,7 @@ async function introspectMssqlWith(exec, schema = 'dbo', options = {}) {
1267
1540
  allColumns,
1268
1541
  primaryKey: pkByTable.get(tableName) ?? [],
1269
1542
  uniqueColumns: uniqueByTable.get(tableName) ?? [],
1270
- relations: relationsByTable.get(tableName) ?? {},
1543
+ relations: relationsByTable.get(tableName) ?? (0, introspect_js_1.nullRecord)(),
1271
1544
  indexes: indexesByTable.get(tableName) ?? [],
1272
1545
  };
1273
1546
  }
@@ -1293,6 +1566,7 @@ async function introspectMssql(options) {
1293
1566
  return await introspectMssqlWith(exec, options.schema ?? 'dbo', {
1294
1567
  include: options.include,
1295
1568
  exclude: options.exclude,
1569
+ logger: options.logger,
1296
1570
  });
1297
1571
  }
1298
1572
  finally {
@@ -1401,35 +1675,48 @@ async function turbineMssql(target, schema, options = {}) {
1401
1675
  owns = true;
1402
1676
  }
1403
1677
  }
1404
- // Probe the server version (fail fast on SQL Server < 2016).
1678
+ let client;
1405
1679
  try {
1406
- const rows = (await pool.query("SELECT CAST(SERVERPROPERTY('ProductMajorVersion') AS INT) AS v")).rows;
1407
- assertSupportedVersion(Number(rows[0]?.v ?? 0));
1680
+ // Probe the server version (fail fast on SQL Server < 2016).
1681
+ try {
1682
+ const rows = (await pool.query("SELECT CAST(SERVERPROPERTY('ProductMajorVersion') AS INT) AS v")).rows;
1683
+ assertSupportedVersion(Number(rows[0]?.v ?? 0));
1684
+ }
1685
+ catch (err) {
1686
+ if (err instanceof errors_js_1.ConnectionError)
1687
+ throw err;
1688
+ // A non-version probe failure (permissions, etc.) should not block startup.
1689
+ }
1690
+ // Everything that is not a SQL Server introspection option is client config
1691
+ // and rides through untouched, so a new TurbineConfig option works here the
1692
+ // day it lands. The engine-owned keys come last, so no caller can unbind the
1693
+ // dialect.
1694
+ const { schema: _dbSchema, ...clientConfig } = options;
1695
+ client = new client_js_1.TurbineClient({
1696
+ ...clientConfig,
1697
+ pool,
1698
+ dialect: exports.mssqlDialect,
1699
+ preparedStatements: false,
1700
+ }, schema);
1408
1701
  }
1409
1702
  catch (err) {
1410
- if (err instanceof errors_js_1.ConnectionError)
1411
- throw err;
1412
- // A non-version probe failure (permissions, etc.) should not block startup.
1703
+ // A failed factory hands the caller nothing to close, so a pool it BUILT
1704
+ // (already connected) is closed here rather than left holding the process
1705
+ // open; the client constructor refuses a bad config, a static global filter
1706
+ // naming no table or column for one. A pool the caller supplied stays open.
1707
+ if (owns)
1708
+ await pool.end().catch(() => { });
1709
+ throw err;
1413
1710
  }
1414
- // Everything that is not a SQL Server introspection option is client config
1415
- // and rides through untouched, so a new TurbineConfig option works here the
1416
- // day it lands. The engine-owned keys come last, so no caller can unbind the
1417
- // dialect.
1418
- const { schema: _dbSchema, ...clientConfig } = options;
1419
- const client = new client_js_1.TurbineClient({
1420
- ...clientConfig,
1421
- pool,
1422
- dialect: exports.mssqlDialect,
1423
- preparedStatements: false,
1424
- }, schema);
1425
1711
  if (owns) {
1426
1712
  // Turbine built this pool, so disconnect()/end() must close it. External pools
1427
1713
  // (injection) stay the caller's responsibility (disconnect() no-op), consistent
1428
1714
  // with turbineHttp / turbineMysql / turbineSqlite.
1429
1715
  const baseDisconnect = client.disconnect.bind(client);
1430
- const close = async () => {
1431
- await baseDisconnect();
1716
+ const close = async (options) => {
1717
+ const result = options === undefined ? await baseDisconnect() : await baseDisconnect(options);
1432
1718
  await pool.end();
1719
+ return result ?? undefined;
1433
1720
  };
1434
1721
  const patch = client;
1435
1722
  patch.disconnect = close;