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
@@ -56,7 +56,7 @@
56
56
  import { CircularRelationError, RelationError, UnsupportedFeatureError, ValidationError } from '../errors.js';
57
57
  import { normalizeKeyColumns } from '../schema.js';
58
58
  import { dedupeOrderEntries, isOrderBySpec, isRelationPickOrderBy, orderByEntries, sortedEntries } from './filters.js';
59
- import { availableClause, markInternalCombinator, ownLookup, resolveColumnName, selectNamesNothingMessage, selectOmitExclusiveMessage, sqlToPreparedName, } from './utils.js';
59
+ import { availableClause, boundedPreparedName, markInternalCombinator, ownLookup, resolveColumnName, selectNamesNothingMessage, selectOmitExclusiveMessage, } from './utils.js';
60
60
  /**
61
61
  * Max parent keys per follow-up query. On Postgres the whole key set travels as
62
62
  * ONE array parameter (`= ANY($1)`), so this is not a bind-parameter limit, it
@@ -240,14 +240,40 @@ includePii) {
240
240
  return hidden.size === 0 ? undefined : { hidden, visible };
241
241
  }
242
242
  /**
243
- * Adjust a `select`/`omit` pair so that `fields` are guaranteed present in the
244
- * query result, returning the adjusted projection plus the list of fields that
245
- * were added ONLY for stitching and must be stripped from the final entities.
243
+ * Key under which {@link includeKeysForBatching} records the fields IT added to
244
+ * a `select`, as a `ReadonlySet<string>` of field names.
246
245
  *
247
- * Used both for the base query (parent keys) and each follow-up query (child
248
- * keys) so a caller's `select: { title: true }` on a relation still stitches even
249
- * though the FK was not requested, and the FK never appears in the output.
246
+ * Why it exists: naming a PII-tagged column in `select` is refused unless the
247
+ * query passes `includePii: UNSAFE` (see `resolveProjection`), and the loader
248
+ * has to project a correlation or ordering column that may itself be tagged.
249
+ * That column is stripped from every entity before the caller sees it, so it is
250
+ * the one legitimate way a PII name reaches a `select` without the caller
251
+ * writing it. The marker is a symbol key: `Object.keys`, `Object.entries`,
252
+ * spreads of a parsed body and `JSON.stringify` never see one, and
253
+ * `JSON.parse` cannot produce one, so request data cannot forge the exemption.
250
254
  */
255
+ export const STITCH_ONLY_FIELDS = Symbol.for('turbine.stitchOnlyFields');
256
+ /** The fields the batched loader (not the caller) added to this `select`, if any. */
257
+ export function stitchOnlyFields(select) {
258
+ if (!select)
259
+ return undefined;
260
+ const marked = select[STITCH_ONLY_FIELDS];
261
+ return marked instanceof Set ? marked : undefined;
262
+ }
263
+ /** Record `fields` on `select` as added by an engine loader, not by the caller. Returns `select`. */
264
+ export function markStitchOnly(select, fields) {
265
+ // Non-enumerable, like the other engine-written markers: the select object is
266
+ // passed by reference from here to the child build, and an enumerable symbol
267
+ // would show up in deep-equality comparisons of the projection.
268
+ if (fields.length > 0) {
269
+ Object.defineProperty(select, STITCH_ONLY_FIELDS, {
270
+ value: new Set(fields),
271
+ enumerable: false,
272
+ configurable: true,
273
+ });
274
+ }
275
+ return select;
276
+ }
251
277
  /**
252
278
  * The two projection SHAPE rules from `resolveProjection`, checked on the RAW
253
279
  * caller args BEFORE `includeKeysForBatching` adjusts them. The adjustment
@@ -269,6 +295,16 @@ export function assertProjectionShape(table, select, omit) {
269
295
  throw new ValidationError(selectOmitExclusiveMessage(table));
270
296
  }
271
297
  }
298
+ /**
299
+ * Adjust a `select`/`omit` pair so that `fields` are guaranteed present in the
300
+ * query result, returning the adjusted projection plus the list of fields that
301
+ * were added ONLY for stitching and must be stripped from the final entities.
302
+ *
303
+ * Used both for the base query (parent keys) and each follow-up query (child
304
+ * keys) so a caller's `select: { title: true }` on a relation still stitches even
305
+ * though the FK was not requested, and the FK never appears in the output. A
306
+ * returned `select` carries {@link STITCH_ONLY_FIELDS} for the fields added here.
307
+ */
272
308
  export function includeKeysForBatching(
273
309
  /**
274
310
  * The table the projection is compiled against. `select` / `omit` keys are
@@ -321,7 +357,7 @@ defaultProjection) {
321
357
  next[f] = true;
322
358
  strip.push(f); // not requested by the caller, added only to stitch
323
359
  }
324
- return { select: next, omit, strip };
360
+ return { select: markStitchOnly(next, strip), omit, strip };
325
361
  }
326
362
  if (omit) {
327
363
  const next = { ...omit };
@@ -347,7 +383,7 @@ defaultProjection) {
347
383
  explicit[f] = true;
348
384
  for (const f of hiddenKeys)
349
385
  explicit[f] = true;
350
- return { select: explicit, omit: undefined, strip: hiddenKeys };
386
+ return { select: markStitchOnly(explicit, hiddenKeys), omit: undefined, strip: hiddenKeys };
351
387
  }
352
388
  return { select, omit, strip: [] };
353
389
  }
@@ -429,8 +465,22 @@ export function resolveCountRelations(parentMeta, countSpec) {
429
465
  }
430
466
  return out;
431
467
  }
432
- /** Stringified stitch key, robust to number/uuid/bigint type drift across a join. */
468
+ /**
469
+ * Stringified stitch key, robust to number/uuid/bigint type drift across a join.
470
+ * A binary key is keyed by its bytes: `String()` of a Buffer decodes it as
471
+ * UTF-8, which maps different invalid sequences to the same replacement text.
472
+ * A Date by its instant, which `String()` truncates to the second.
473
+ */
433
474
  function keyOf(value) {
475
+ if (value instanceof Uint8Array) {
476
+ // No Buffer: this module runs on edge runtimes too.
477
+ let hex = '\u0000bytes:';
478
+ for (const byte of value)
479
+ hex += byte.toString(16).padStart(2, '0');
480
+ return hex;
481
+ }
482
+ if (value instanceof Date)
483
+ return `\u0000date:${value.getTime()}`;
434
484
  return String(value);
435
485
  }
436
486
  /**
@@ -599,6 +649,34 @@ function seedRelationKeys(parents, parentMeta, resolved, hasCount) {
599
649
  parent._count = existingCount ?? {};
600
650
  }
601
651
  }
652
+ /**
653
+ * A to-many relation's per-parent page (`skip` / `take` are folded into
654
+ * `offset` / `limit` at the args seam), validated as the join plan validates
655
+ * it: a non-negative integer, numeric strings coerced. Checked before anything
656
+ * runs, so both strategies accept and refuse the same values.
657
+ */
658
+ function relationPage(options, table) {
659
+ const check = (value, arg) => {
660
+ if (value === undefined)
661
+ return undefined;
662
+ const n = Number(value);
663
+ if (!Number.isSafeInteger(n) || n < 0) {
664
+ throw new ValidationError(`${arg} on "${table}" must be a non-negative integer, received: ${String(value)}`);
665
+ }
666
+ return n;
667
+ };
668
+ return {
669
+ limit: check(options.limit, 'relation limit'),
670
+ offset: check(options.offset, 'relation offset'),
671
+ };
672
+ }
673
+ /** One parent's window of its own related rows. */
674
+ function pageOf(rows, page) {
675
+ if (page.limit === undefined && !page.offset)
676
+ return rows;
677
+ const start = page.offset ?? 0;
678
+ return rows.slice(start, page.limit === undefined ? undefined : start + page.limit);
679
+ }
602
680
  /**
603
681
  * hasMany / hasOne / belongsTo: one follow-up `SELECT ... WHERE childKey = ANY($1)`
604
682
  * (chunked), grouped by the correlation key and attached (array vs single-or-null).
@@ -620,6 +698,7 @@ async function loadToOneOrMany(ctx, parents, rel, relName, options, timeout, dep
620
698
  assertCorrelationKeyProjected(parents, parentKeyField, relName, ctx.parentMeta.name);
621
699
  const keys = uniqueKeys(parents, parentKeyField);
622
700
  const single = rel.type === 'belongsTo' || rel.type === 'hasOne';
701
+ const page = single ? {} : relationPage(options, ctx.parentMeta.name);
623
702
  // The follow-up must project the child correlation key even if the caller's
624
703
  // select/omit excluded it; strip it back off afterwards so the shape matches join.
625
704
  //
@@ -638,7 +717,7 @@ async function loadToOneOrMany(ctx, parents, rel, relName, options, timeout, dep
638
717
  // again afterwards). `null` means this relation keeps the client-side slice,
639
718
  // which is the case for every shape whose ordering does not force WHICH rows
640
719
  // the limit keeps. See {@link partitionOrderBy} and {@link boundedChildQuery}.
641
- const windowOrder = single || options.limit === undefined ? null : partitionOrderBy(targetMeta, options.orderBy);
720
+ const windowOrder = single || page.limit === undefined ? null : partitionOrderBy(targetMeta, options.orderBy);
642
721
  const pushDownLimit = windowOrder !== null && ctx.buildPartitionLimit !== undefined;
643
722
  // THE ONE PII EXCEPTION IN THE READ PATH, recorded here because the contract
644
723
  // in schema.ts says a PII column is excluded from every default projection
@@ -689,7 +768,8 @@ async function loadToOneOrMany(ctx, parents, rel, relName, options, timeout, dep
689
768
  const chunkResults = await Promise.all(chunks.map(async (chunk) => {
690
769
  const deferred = buildChunk(chunk);
691
770
  const bounded = pushDownLimit
692
- ? boundedChildQuery(ctx, deferred, childKeyCol, windowOrder ?? [], options.limit)
771
+ ? // The first offset + limit rows of each parent; the slice below drops the offset.
772
+ boundedChildQuery(ctx, deferred, childKeyCol, windowOrder ?? [], (page.offset ?? 0) + page.limit)
693
773
  : deferred;
694
774
  const result = await ctx.exec(bounded.sql, bounded.params, bounded.preparedName);
695
775
  if (bounded !== deferred)
@@ -711,14 +791,13 @@ async function loadToOneOrMany(ctx, parents, rel, relName, options, timeout, dep
711
791
  // what was true of the parent side until this week.
712
792
  assertCorrelationKeyProjected(allChildren, childKeyField, relName, targetMeta.name);
713
793
  const byKey = groupBy(allChildren, childKeyField);
714
- const limit = options.limit;
715
794
  for (const parent of parents) {
716
795
  const bucket = byKey.get(keyOf(parent[parentKeyField])) ?? [];
717
796
  if (single) {
718
797
  parent[relName] = bucket[0] ?? null;
719
798
  }
720
799
  else {
721
- parent[relName] = limit !== undefined ? bucket.slice(0, limit) : bucket;
800
+ parent[relName] = pageOf(bucket, page);
722
801
  }
723
802
  }
724
803
  stripFields(allChildren, proj.strip);
@@ -779,7 +858,7 @@ function boundedChildQuery(ctx, deferred, partitionColumn, orderBy, limit) {
779
858
  return {
780
859
  sql,
781
860
  params: [...deferred.params, limit],
782
- preparedName: deferred.preparedName ? sqlToPreparedName(sql) : deferred.preparedName,
861
+ preparedName: deferred.preparedName ? boundedPreparedName(sql) : deferred.preparedName,
783
862
  transform: deferred.transform,
784
863
  };
785
864
  }
@@ -819,6 +898,7 @@ async function loadManyToMany(ctx, parents, rel, relName, options, timeout, dept
819
898
  const targetPkCol = targetMeta.primaryKey[0];
820
899
  const parentRefField = ctx.parentMeta.reverseColumnMap[sourceRefCol] ?? sourceRefCol;
821
900
  const targetPkField = targetMeta.reverseColumnMap[targetPkCol] ?? targetPkCol;
901
+ const page = relationPage(options, ctx.parentMeta.name);
822
902
  assertCorrelationKeyProjected(parents, parentRefField, relName, ctx.parentMeta.name);
823
903
  // No early return on zero parent keys: the code below flows through
824
904
  // naturally (zero junction chunks, zero target chunks), and the compile-only
@@ -910,7 +990,7 @@ async function loadManyToMany(ctx, parents, rel, relName, options, timeout, dept
910
990
  // link, which spends exactly the bytes the pushdown exists to save; and the
911
991
  // bound would still have to respect a `limit` measured per parent, not per
912
992
  // fetched row. So the m2m loader keeps the slice below, on every engine.
913
- const limit = options.limit;
993
+ const end = page.limit === undefined ? undefined : (page.offset ?? 0) + page.limit;
914
994
  for (const parent of parents) {
915
995
  const linked = new Set((targetsBySource.get(keyOf(parent[parentRefField])) ?? []).map(keyOf));
916
996
  if (linked.size === 0) {
@@ -919,13 +999,13 @@ async function loadManyToMany(ctx, parents, rel, relName, options, timeout, dept
919
999
  }
920
1000
  const out = [];
921
1001
  for (const t of targetsInOrder) {
922
- if (linked.has(keyOf(t[targetPkField]))) {
1002
+ // Checked before the push, so `limit: 0` keeps nothing.
1003
+ if (end !== undefined && out.length >= end)
1004
+ break;
1005
+ if (linked.has(keyOf(t[targetPkField])))
923
1006
  out.push(t);
924
- if (limit !== undefined && out.length >= limit)
925
- break;
926
- }
927
1007
  }
928
- parent[relName] = out;
1008
+ parent[relName] = pageOf(out, { offset: page.offset });
929
1009
  }
930
1010
  stripFields(targetsInOrder, proj.strip);
931
1011
  }