turbine-orm 0.56.0 → 0.58.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.
@@ -32,6 +32,15 @@
32
32
  * atomic.
33
33
  * - **Result reshaping**: `_count` objects keyed back to Prisma relation names,
34
34
  * and to-one relations surfaced as `object | null`.
35
+ * - **Turbine-native query options** (`timeout`, `forceCustomPlan`,
36
+ * `warnOnUnlimited`, `skipGlobalFilters`, `stableRelationOrder`,
37
+ * `allowFullTableScan`, `includePii`, `optimisticLock`, `distinctOn`) reach
38
+ * core on every operation whose arg surface declares them. The set is not a
39
+ * hand-maintained list here: it comes from the compiler-checked tables in
40
+ * `query/option-surface.ts`, so a new core option cannot be silently stranded
41
+ * by this layer, and a key that is neither a Prisma arg nor a turbine option
42
+ * gets a dev-mode warning instead of vanishing (see
43
+ * {@link PRISMA_ARG_KEYS} and `warnUnknownQueryOptions`).
35
44
  *
36
45
  * ## What it deliberately does NOT do (documented divergences)
37
46
  *
@@ -64,6 +73,14 @@
64
73
  * exclusive-cursor + `offset` translation.
65
74
  * - **Negative `take`** (take-from-end) and **`skip` on a nested relation
66
75
  * include** throw, Turbine's `with` clause has no offset and no reverse-take.
76
+ * - **`limit` on `updateMany` / `deleteMany`** (Prisma 6.7+) throws. Turbine has
77
+ * no row-bounded mass mutation, and dropping a SAFETY BOUND with a warning
78
+ * would turn "change at most 10 rows" into "change every matching row".
79
+ * - **Write projections** (`select` / `include` / `omit` on
80
+ * create/update/delete/upsert), **`select` on `count`**, and
81
+ * **`orderBy` / `cursor` / `take` / `skip` on `aggregate`** are accepted and
82
+ * IGNORED (they are legitimate Prisma, so they never warn); the full row / a
83
+ * plain number comes back.
67
84
  *
68
85
  * ## Type dependencies (0.41.0)
69
86
  *
@@ -94,10 +111,12 @@
94
111
  * ```
95
112
  */
96
113
  Object.defineProperty(exports, "__esModule", { value: true });
97
- exports.CLIENT_RESERVED_KEYS = exports.COMPAT_DEFERRED = exports.Prisma = void 0;
114
+ exports.CLIENT_RESERVED_KEYS = exports.COMPAT_DEFERRED = exports.PRISMA_ARG_KEYS = exports.Prisma = void 0;
98
115
  exports.createPrismaCompatClient = createPrismaCompatClient;
99
116
  const errors_js_1 = require("./errors.js");
100
117
  const nested_write_js_1 = require("./nested-write.js");
118
+ const index_js_1 = require("./query/index.js");
119
+ const utils_js_1 = require("./query/utils.js");
101
120
  const warn_registry_js_1 = require("./query/warn-registry.js");
102
121
  // ---------------------------------------------------------------------------
103
122
  // Prisma.sql-style raw fragments (local, minimal, never imports @prisma/client)
@@ -256,6 +275,199 @@ function isPlainObject(v) {
256
275
  function renameField(mm, prismaField) {
257
276
  return mm.fields[prismaField] ?? prismaField;
258
277
  }
278
+ /** The turbine arg surface each operation compiles into. */
279
+ const OPERATION_TABLES = {
280
+ findMany: index_js_1.FIND_MANY_OPTIONS,
281
+ findFirst: index_js_1.FIND_MANY_OPTIONS,
282
+ findFirstOrThrow: index_js_1.FIND_MANY_OPTIONS,
283
+ findUnique: index_js_1.FIND_UNIQUE_OPTIONS,
284
+ findUniqueOrThrow: index_js_1.FIND_UNIQUE_OPTIONS,
285
+ create: index_js_1.CREATE_OPTIONS,
286
+ createMany: index_js_1.CREATE_MANY_OPTIONS,
287
+ update: index_js_1.UPDATE_OPTIONS,
288
+ updateMany: index_js_1.UPDATE_MANY_OPTIONS,
289
+ delete: index_js_1.DELETE_OPTIONS,
290
+ deleteMany: index_js_1.DELETE_MANY_OPTIONS,
291
+ upsert: index_js_1.UPSERT_OPTIONS,
292
+ count: index_js_1.COUNT_OPTIONS,
293
+ aggregate: index_js_1.AGGREGATE_OPTIONS,
294
+ groupBy: index_js_1.GROUP_BY_OPTIONS,
295
+ };
296
+ /**
297
+ * Every argument key Prisma itself accepts, per operation.
298
+ *
299
+ * Extracted from a generated `@prisma/client` 7.9.0 `index.d.ts` (the
300
+ * `<Model><Op>Args` blocks). It must stay the UNION across the Prisma majors
301
+ * this adapter supports, never one version's set: a key a newer major
302
+ * introduces should degrade to one noisy dev line, never to a throw, and a key
303
+ * an older major had must keep working. The drift test in
304
+ * `src/test/prisma-compat-option-surface.test.ts` re-extracts from a generated
305
+ * client when one is present and asserts this stays a superset.
306
+ */
307
+ exports.PRISMA_ARG_KEYS = {
308
+ findMany: [
309
+ 'select',
310
+ 'omit',
311
+ 'include',
312
+ 'where',
313
+ 'orderBy',
314
+ 'cursor',
315
+ 'take',
316
+ 'skip',
317
+ 'distinct',
318
+ 'relationLoadStrategy',
319
+ ],
320
+ findFirst: [
321
+ 'select',
322
+ 'omit',
323
+ 'include',
324
+ 'where',
325
+ 'orderBy',
326
+ 'cursor',
327
+ 'take',
328
+ 'skip',
329
+ 'distinct',
330
+ 'relationLoadStrategy',
331
+ ],
332
+ findFirstOrThrow: [
333
+ 'select',
334
+ 'omit',
335
+ 'include',
336
+ 'where',
337
+ 'orderBy',
338
+ 'cursor',
339
+ 'take',
340
+ 'skip',
341
+ 'distinct',
342
+ 'relationLoadStrategy',
343
+ ],
344
+ findUnique: ['select', 'omit', 'include', 'where', 'relationLoadStrategy'],
345
+ findUniqueOrThrow: ['select', 'omit', 'include', 'where', 'relationLoadStrategy'],
346
+ create: ['select', 'omit', 'include', 'data', 'relationLoadStrategy'],
347
+ createMany: ['data', 'skipDuplicates'],
348
+ update: ['select', 'omit', 'include', 'data', 'where', 'relationLoadStrategy'],
349
+ updateMany: ['data', 'where', 'limit'],
350
+ delete: ['select', 'omit', 'include', 'where', 'relationLoadStrategy'],
351
+ deleteMany: ['where', 'limit'],
352
+ upsert: ['select', 'omit', 'include', 'where', 'create', 'update', 'relationLoadStrategy'],
353
+ count: ['where', 'orderBy', 'cursor', 'take', 'skip', 'select'],
354
+ aggregate: ['where', 'orderBy', 'cursor', 'take', 'skip', '_count', '_avg', '_sum', '_min', '_max'],
355
+ groupBy: ['where', 'orderBy', 'by', 'having', 'take', 'skip', '_count', '_avg', '_sum', '_min', '_max'],
356
+ };
357
+ /**
358
+ * Turbine spellings whose Prisma equivalent is spelled differently. A caller
359
+ * who reaches for one of these is not confused about the NAME, they are
360
+ * confused about which surface they are on, so they get a specific message
361
+ * rather than a fuzzy did-you-mean.
362
+ */
363
+ const ALIAS_HINT = {
364
+ limit: 'take',
365
+ offset: 'skip',
366
+ with: 'include',
367
+ };
368
+ /**
369
+ * Known(op) = Prisma's own keys for that operation, plus every key the
370
+ * operation's turbine arg surface declares except the `'internal'` ones.
371
+ *
372
+ * The second half is NOT just the `'native'` keys. Two turbine-only options are
373
+ * classified `'prisma'` because their values carry field names and so must be
374
+ * hand-translated (`optimisticLock`, `distinctOn`); they are fully honoured, and
375
+ * warning about a key the adapter just acted on would be the worst possible
376
+ * diagnostic. `'nativeAlias'` members are in the set on purpose too: `limit` on
377
+ * `findMany` IS recognized, it simply gets the alias message rather than the
378
+ * generic one. Only `'internal'` is excluded, because nothing on this surface
379
+ * can reach it.
380
+ *
381
+ * A `Set` rather than an `in` test on a record, so an inherited
382
+ * `Object.prototype` name (`toString`, `constructor`) is treated as the unknown
383
+ * key it is.
384
+ */
385
+ const KNOWN_KEYS = (() => {
386
+ const out = {};
387
+ for (const op of Object.keys(OPERATION_TABLES)) {
388
+ out[op] = new Set([
389
+ ...exports.PRISMA_ARG_KEYS[op],
390
+ ...(0, index_js_1.optionKeysOfKind)(OPERATION_TABLES[op], 'prisma', 'native', 'nativeAlias'),
391
+ ]);
392
+ }
393
+ return out;
394
+ })();
395
+ /**
396
+ * Dev-mode notice for a key on a delegate's args object that this operation has
397
+ * no meaning for.
398
+ *
399
+ * An unrecognized key is silently ignored (JavaScript objects have no schema),
400
+ * which makes a typo, a turbine spelling, and a genuinely missing feature all
401
+ * look identical: nothing happens. The client-config warner
402
+ * (`warnUnknownConfigKeys` in client.ts) exists for the same reason and this is
403
+ * its query-level twin, down to the ranking of the suggestion.
404
+ *
405
+ * Deliberately a WARNING, never an error. Throwing would turn a working app
406
+ * into a failing one on upgrade over one stray key, and a key from a Prisma
407
+ * major newer than {@link PRISMA_ARG_KEYS} would be exactly that key. The whole
408
+ * body is wrapped so a hostile or exotic args object (a Proxy whose `ownKeys`
409
+ * throws) can never be the reason a query fails. Dev-only and once per
410
+ * `model.operation.key` per process, like every other advisory here.
411
+ */
412
+ function warnUnknownQueryOptions(model, op, args) {
413
+ if (process.env.NODE_ENV === 'production')
414
+ return;
415
+ if (!isPlainObject(args))
416
+ return;
417
+ try {
418
+ const known = KNOWN_KEYS[op];
419
+ const table = OPERATION_TABLES[op];
420
+ for (const key of Object.keys(args)) {
421
+ // `{ ...maybeOptions }` routinely materializes keys with no value.
422
+ // Nothing is being dropped when the value is undefined.
423
+ if (args[key] === undefined)
424
+ continue;
425
+ const alias = table[key] === 'nativeAlias' ? ALIAS_HINT[key] : undefined;
426
+ if (!alias && known.has(key))
427
+ continue;
428
+ if (!(0, warn_registry_js_1.shouldWarnOnce)(warn_registry_js_1.WARN_NS.unknownQueryOption, `${model}.${op}.${key}`))
429
+ continue;
430
+ if (alias) {
431
+ console.warn(`[turbine] prisma-compat: "${key}" is Turbine's spelling and is ignored here;` +
432
+ ` prisma-compat takes Prisma's "${alias}". (${model}.${op})`);
433
+ continue;
434
+ }
435
+ const suggestion = (0, utils_js_1.suggestKey)(key, known);
436
+ console.warn(`[turbine] prisma-compat: unknown option "${key}" in ${model}.${op}(), it is ignored.` +
437
+ (suggestion ? ` Did you mean "${suggestion}"?` : ''));
438
+ }
439
+ }
440
+ catch {
441
+ // Key enumeration is the only thing that can fail here, and a diagnostic
442
+ // must never be the reason a query fails.
443
+ }
444
+ }
445
+ /**
446
+ * Prisma 6.7+ accepts `limit` on `updateMany` / `deleteMany` to BOUND how many
447
+ * rows a mass mutation touches. Turbine has no row-bounded mass mutation, so
448
+ * this one is refused rather than warned about: everywhere else in this design
449
+ * a dropped option costs the caller a feature, but silently dropping a safety
450
+ * bound turns "change at most 10 rows" into "change every row".
451
+ */
452
+ function refuseRowLimit(model, op, args) {
453
+ if (!isPlainObject(args) || args.limit === undefined)
454
+ return;
455
+ throw new errors_js_1.UnsupportedFeatureError(`\`limit\` on ${op} (row-bounded mass mutation)`, 'prisma-compat', `Turbine has no row-bounded ${op}; select the rows you mean with \`where\`` +
456
+ ` (on ${model}.${op}), or page them and mutate by primary key.`);
457
+ }
458
+ /**
459
+ * Prisma's `relationLoadStrategy` and Turbine's share a name and NOT a value
460
+ * domain: Prisma has `'query' | 'join'`, Turbine has
461
+ * `'join' | 'batched' | 'auto' | 'flatten'`. Core's strategy resolution has no
462
+ * branch for `'query'` and returns the value unchanged, so forwarding it
463
+ * verbatim gave a caller who asked for the per-relation query plan the JOIN
464
+ * plan, the exact opposite of the request. Prisma's `'query'` IS Turbine's
465
+ * `'batched'` (one follow-up statement per relation); the turbine-only values
466
+ * pass through so a compat caller can still reach them.
467
+ */
468
+ function mapRelationLoadStrategy(value) {
469
+ return value === 'query' ? 'batched' : value;
470
+ }
259
471
  /**
260
472
  * Translate a Prisma `where` (or nested relation where) into a Turbine `where`.
261
473
  * Renames scalar field keys and relation keys through the map, recurses into
@@ -450,6 +662,13 @@ function modelName(ctx, mm) {
450
662
  */
451
663
  function translateReadArgs(ctx, mm, prismaArgs, kind) {
452
664
  const t = {};
665
+ // FIRST, so a per-call `stableRelationOrder` overrides it: the client-level
666
+ // option is a default, the arg is an instruction.
667
+ if (ctx.options.stablePkOrder)
668
+ t.stableRelationOrder = true;
669
+ // Every turbine-native option the arg surface declares, in one line that
670
+ // cannot fall behind the interface (see the option-surface tables).
671
+ (0, index_js_1.applyNativeOptions)(kind === 'unique' ? index_js_1.FIND_UNIQUE_OPTIONS : index_js_1.FIND_MANY_OPTIONS, prismaArgs, t);
453
672
  if (prismaArgs.where !== undefined)
454
673
  t.where = translateWhere(ctx, mm, prismaArgs.where);
455
674
  if (prismaArgs.orderBy !== undefined)
@@ -462,17 +681,9 @@ function translateReadArgs(ctx, mm, prismaArgs, kind) {
462
681
  if (Array.isArray(prismaArgs.distinct)) {
463
682
  t.distinct = prismaArgs.distinct.map((f) => renameField(mm, f));
464
683
  }
465
- if (prismaArgs.relationLoadStrategy !== undefined)
466
- t.relationLoadStrategy = prismaArgs.relationLoadStrategy;
467
- if (typeof prismaArgs.timeout === 'number')
468
- t.timeout = prismaArgs.timeout;
469
- // Turbine-only passthrough. Prisma has no PII concept, so a compat caller
470
- // whose schema tags columns needs SOME way to opt in; without this the
471
- // adapter is a one-way door into redacted reads and refused aggregates.
472
- if (prismaArgs.includePii !== undefined)
473
- t.includePii = prismaArgs.includePii;
474
- if (ctx.options.stablePkOrder)
475
- t.stableRelationOrder = true;
684
+ if (prismaArgs.relationLoadStrategy !== undefined) {
685
+ t.relationLoadStrategy = mapRelationLoadStrategy(prismaArgs.relationLoadStrategy);
686
+ }
476
687
  // ORDER IS LOAD-BEARING: translateCursor MUST run BEFORE applyImplicitPkOrder.
477
688
  // The cursor translation reads `t.orderBy` to decide the seek direction and to
478
689
  // validate that a bare inclusive cursor names the sort key; it must see the
@@ -771,6 +982,7 @@ function translateUpsertNested(ctx, target, p) {
771
982
  const AGG_FIELD_BLOCKS = ['_sum', '_avg', '_min', '_max'];
772
983
  function translateAggregateArgs(ctx, mm, args, isGroupBy) {
773
984
  const t = {};
985
+ (0, index_js_1.applyNativeOptions)(isGroupBy ? index_js_1.GROUP_BY_OPTIONS : index_js_1.AGGREGATE_OPTIONS, args, t);
774
986
  if (args.where !== undefined)
775
987
  t.where = translateWhere(ctx, mm, args.where);
776
988
  if (args._count !== undefined)
@@ -779,12 +991,6 @@ function translateAggregateArgs(ctx, mm, args, isGroupBy) {
779
991
  if (args[block] !== undefined)
780
992
  t[block] = renameAggBlock(mm, args[block], false);
781
993
  }
782
- if (typeof args.timeout === 'number')
783
- t.timeout = args.timeout;
784
- // Turbine-only passthrough: the PII gate on groupBy keys and _min/_max needs
785
- // an opt-in that Prisma's arg shape has no equivalent for.
786
- if (args.includePii !== undefined)
787
- t.includePii = args.includePii;
788
994
  if (isGroupBy) {
789
995
  if (Array.isArray(args.by))
790
996
  t.by = args.by.map((f) => renameField(mm, f));
@@ -792,6 +998,8 @@ function translateAggregateArgs(ctx, mm, args, isGroupBy) {
792
998
  t.orderBy = translateOrderBy(ctx, mm, args.orderBy);
793
999
  if (args.having !== undefined)
794
1000
  t.having = renameHaving(mm, args.having);
1001
+ if (args.distinctOn !== undefined)
1002
+ t.distinctOn = translateDistinctOn(ctx, mm, args.distinctOn);
795
1003
  if (typeof args.take === 'number')
796
1004
  t.limit = mapTake(args.take);
797
1005
  if (typeof args.skip === 'number')
@@ -799,6 +1007,23 @@ function translateAggregateArgs(ctx, mm, args, isGroupBy) {
799
1007
  }
800
1008
  return t;
801
1009
  }
1010
+ /**
1011
+ * Translate a turbine-native groupBy `distinctOn`. Both halves of its value
1012
+ * (`columns`, `orderBy`) are FIELD NAMES, so this is the concrete reason the
1013
+ * option surface refuses to copy unknown keys through by default: a blind
1014
+ * passthrough is correct on a model whose Prisma and turbine names coincide and
1015
+ * silently wrong on one with a `@map`.
1016
+ */
1017
+ function translateDistinctOn(ctx, mm, val) {
1018
+ if (!isPlainObject(val))
1019
+ return val;
1020
+ const out = { ...val };
1021
+ if (Array.isArray(val.columns))
1022
+ out.columns = val.columns.map((f) => renameField(mm, f));
1023
+ if (val.orderBy !== undefined)
1024
+ out.orderBy = translateOrderBy(ctx, mm, val.orderBy);
1025
+ return out;
1026
+ }
802
1027
  /** Rename field keys inside an aggregate block; `_count`'s `_all` passes through. */
803
1028
  function renameAggBlock(mm, block, isCount) {
804
1029
  if (block === true || !isPlainObject(block))
@@ -1143,6 +1368,8 @@ async function createManyByRun(qi, t, runs) {
1143
1368
  const args = { data: run };
1144
1369
  if (t.skipDuplicates)
1145
1370
  args.skipDuplicates = true;
1371
+ if (t.timeout !== undefined)
1372
+ args.timeout = t.timeout;
1146
1373
  count += (await qi.createMany(args)).length;
1147
1374
  }
1148
1375
  return { count };
@@ -1158,7 +1385,14 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
1158
1385
  // async run wrapper also converts any synchronous throw from the underlying
1159
1386
  // `qi.*` build into a rejection; the array `$transaction([...])` batch path
1160
1387
  // catches the same throw from `batch.build`.
1161
- const defer = (translate, run, batch) => {
1388
+ const defer = (op, rawArgs, translateRaw, run, batch) => {
1389
+ // The unknown-key check rides the SAME deferred boundary as the translation
1390
+ // itself, for the same reason (see the comment above): a compat promise that
1391
+ // is built and never awaited must produce no output at all.
1392
+ const translate = () => {
1393
+ warnUnknownQueryOptions(modelName(ctx, mm), op, rawArgs);
1394
+ return translateRaw();
1395
+ };
1162
1396
  const batchable = batch
1163
1397
  ? {
1164
1398
  build: () => batch.build(getQI(), translate()),
@@ -1198,27 +1432,27 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
1198
1432
  return args;
1199
1433
  };
1200
1434
  return {
1201
- findMany: (args = {}) => defer(() => translateReadArgs(ctx, mm, args, 'many'), (qi, t) => qi.findMany(t).then((r) => reshapeRows(ctx, mm, r)), { build: (qi, t) => qi.buildFindMany(t), reshape: (raw) => reshapeRows(ctx, mm, raw) }),
1202
- findFirst: (args = {}) => defer(() => translateReadArgs(ctx, mm, args, 'first'), (qi, t) => qi.findFirst(t).then((r) => reshapeRowOrNull(ctx, mm, r)), { build: (qi, t) => qi.buildFindFirst(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) }),
1203
- findUnique: (args) => defer(() => translateReadArgs(ctx, mm, requireWhere(args, 'findUnique'), 'unique'), (qi, t) => qi.findUnique(t).then((r) => reshapeRowOrNull(ctx, mm, r)), { build: (qi, t) => qi.buildFindUnique(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) }),
1204
- findFirstOrThrow: (args = {}) => defer(() => translateReadArgs(ctx, mm, args, 'first'), (qi, t) => qi.findFirstOrThrow(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildFindFirstOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
1205
- findUniqueOrThrow: (args) => defer(() => translateReadArgs(ctx, mm, requireWhere(args, 'findUniqueOrThrow'), 'unique'), (qi, t) => qi.findUniqueOrThrow(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildFindUniqueOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
1206
- create: (args) => defer(() => {
1435
+ findMany: (args = {}) => defer('findMany', args, () => translateReadArgs(ctx, mm, args, 'many'), (qi, t) => qi.findMany(t).then((r) => reshapeRows(ctx, mm, r)), { build: (qi, t) => qi.buildFindMany(t), reshape: (raw) => reshapeRows(ctx, mm, raw) }),
1436
+ findFirst: (args = {}) => defer('findFirst', args, () => translateReadArgs(ctx, mm, args, 'first'), (qi, t) => qi.findFirst(t).then((r) => reshapeRowOrNull(ctx, mm, r)), { build: (qi, t) => qi.buildFindFirst(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) }),
1437
+ findUnique: (args) => defer('findUnique', args, () => translateReadArgs(ctx, mm, requireWhere(args, 'findUnique'), 'unique'), (qi, t) => qi.findUnique(t).then((r) => reshapeRowOrNull(ctx, mm, r)), { build: (qi, t) => qi.buildFindUnique(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) }),
1438
+ findFirstOrThrow: (args = {}) => defer('findFirstOrThrow', args, () => translateReadArgs(ctx, mm, args, 'first'), (qi, t) => qi.findFirstOrThrow(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildFindFirstOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
1439
+ findUniqueOrThrow: (args) => defer('findUniqueOrThrow', args, () => translateReadArgs(ctx, mm, requireWhere(args, 'findUniqueOrThrow'), 'unique'), (qi, t) => qi.findUniqueOrThrow(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildFindUniqueOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
1440
+ create: (args) => defer('create', args, () => {
1207
1441
  const t = { data: translateWriteData(ctx, mm, applyCreateDefaults(mm, args.data)) };
1208
- if (typeof args.timeout === 'number')
1209
- t.timeout = args.timeout;
1442
+ (0, index_js_1.applyNativeOptions)(index_js_1.CREATE_OPTIONS, args, t);
1210
1443
  return t;
1211
1444
  }, (qi, t) => qi.create(t).then((r) => reshapeRow(ctx, mm, r)), {
1212
1445
  build: (qi, t) => qi.buildCreate(t),
1213
1446
  reshape: (raw) => reshapeRow(ctx, mm, raw),
1214
1447
  nested: (t) => hasNestedKeys(ctx, mm, t.data),
1215
1448
  }),
1216
- createMany: (args) => defer(() => {
1449
+ createMany: (args) => defer('createMany', args, () => {
1217
1450
  const data = args.data;
1218
1451
  const rows = Array.isArray(data)
1219
1452
  ? data.map((d) => translateWriteData(ctx, mm, applyCreateDefaults(mm, d)))
1220
1453
  : [];
1221
1454
  const t = { data: rows };
1455
+ (0, index_js_1.applyNativeOptions)(index_js_1.CREATE_MANY_OPTIONS, args, t);
1222
1456
  if (args.skipDuplicates)
1223
1457
  t.skipDuplicates = true;
1224
1458
  return t;
@@ -1239,45 +1473,68 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
1239
1473
  nested: (t) => createManyRunsOf(t).length > 1,
1240
1474
  execInTx: (table, t) => createManyByRun(table(mm.table), t, createManyRunsOf(t)),
1241
1475
  }),
1242
- update: (args) => defer(() => {
1476
+ update: (args) => defer('update', args, () => {
1243
1477
  const a = requireWhere(args, 'update');
1244
- return {
1478
+ const t = {
1245
1479
  where: translateWhere(ctx, mm, a.where),
1246
1480
  data: translateWriteData(ctx, mm, applyUpdateTouch(mm, a.data)),
1247
1481
  };
1482
+ (0, index_js_1.applyNativeOptions)(index_js_1.UPDATE_OPTIONS, a, t);
1483
+ // `optimisticLock.field` is a FIELD NAME, so it is renamed rather than
1484
+ // copied: a blind passthrough would send the Prisma spelling into core
1485
+ // and break on any model whose column is `@map`ped.
1486
+ if (isPlainObject(a.optimisticLock)) {
1487
+ t.optimisticLock = {
1488
+ ...a.optimisticLock,
1489
+ field: renameField(mm, String(a.optimisticLock.field)),
1490
+ };
1491
+ }
1492
+ return t;
1248
1493
  }, (qi, t) => qi.update(t).then((r) => reshapeRow(ctx, mm, r)), {
1249
1494
  build: (qi, t) => qi.buildUpdate(t),
1250
1495
  reshape: (raw) => reshapeRow(ctx, mm, raw),
1251
1496
  nested: (t) => hasNestedKeys(ctx, mm, t.data),
1252
1497
  }),
1253
- updateMany: (args) => defer(() => {
1498
+ updateMany: (args) => defer('updateMany', args, () => {
1254
1499
  const a = args;
1500
+ refuseRowLimit(modelName(ctx, mm), 'updateMany', a);
1255
1501
  const t = {
1256
1502
  where: translateWhere(ctx, mm, a.where ?? {}),
1257
1503
  data: translateWriteData(ctx, mm, applyUpdateTouch(mm, a.data)),
1258
1504
  };
1505
+ (0, index_js_1.applyNativeOptions)(index_js_1.UPDATE_MANY_OPTIONS, a, t);
1506
+ // LAST, so it wins: a Prisma updateMany with no `where` affects every
1507
+ // row, and an explicit `allowFullTableScan: false` must not be able to
1508
+ // turn that parity into a thrown empty-where guard.
1259
1509
  if (a.where === undefined)
1260
1510
  t.allowFullTableScan = true;
1261
1511
  return t;
1262
1512
  }, (qi, t) => qi.updateMany(t), { build: (qi, t) => qi.buildUpdateMany(t), reshape: (raw) => raw }),
1263
- delete: (args) => defer(() => {
1513
+ delete: (args) => defer('delete', args, () => {
1264
1514
  const a = requireWhere(args, 'delete');
1265
- return { where: translateWhere(ctx, mm, a.where) };
1515
+ const t = { where: translateWhere(ctx, mm, a.where) };
1516
+ (0, index_js_1.applyNativeOptions)(index_js_1.DELETE_OPTIONS, a, t);
1517
+ return t;
1266
1518
  }, (qi, t) => qi.delete(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildDelete(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
1267
- deleteMany: (args = {}) => defer(() => {
1519
+ deleteMany: (args = {}) => defer('deleteMany', args, () => {
1268
1520
  const a = args;
1521
+ refuseRowLimit(modelName(ctx, mm), 'deleteMany', a);
1269
1522
  const t = { where: translateWhere(ctx, mm, a.where ?? {}) };
1523
+ (0, index_js_1.applyNativeOptions)(index_js_1.DELETE_MANY_OPTIONS, a, t);
1524
+ // LAST, so it wins. See the same note on updateMany.
1270
1525
  if (a.where === undefined)
1271
1526
  t.allowFullTableScan = true;
1272
1527
  return t;
1273
1528
  }, (qi, t) => qi.deleteMany(t), { build: (qi, t) => qi.buildDeleteMany(t), reshape: (raw) => raw }),
1274
- upsert: (args) => defer(() => {
1529
+ upsert: (args) => defer('upsert', args, () => {
1275
1530
  const a = requireWhere(args, 'upsert');
1276
- return {
1531
+ const t = {
1277
1532
  where: translateWhere(ctx, mm, a.where),
1278
1533
  create: translateWriteData(ctx, mm, applyCreateDefaults(mm, a.create)),
1279
1534
  update: translateWriteData(ctx, mm, applyUpdateTouch(mm, a.update)),
1280
1535
  };
1536
+ (0, index_js_1.applyNativeOptions)(index_js_1.UPSERT_OPTIONS, a, t);
1537
+ return t;
1281
1538
  }, (qi, t) => {
1282
1539
  // Native ON CONFLICT upsert is only Prisma-equivalent when the where
1283
1540
  // key values equal the create values AND no nested write data is
@@ -1300,16 +1557,15 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
1300
1557
  return reshapeRow(ctx, mm, await upsertLookupFirst(table(mm.table), t));
1301
1558
  },
1302
1559
  }),
1303
- count: (args = {}) => defer(() => {
1560
+ count: (args = {}) => defer('count', args, () => {
1304
1561
  const t = {};
1562
+ (0, index_js_1.applyNativeOptions)(index_js_1.COUNT_OPTIONS, args, t);
1305
1563
  if (args.where !== undefined)
1306
1564
  t.where = translateWhere(ctx, mm, args.where);
1307
- if (typeof args.timeout === 'number')
1308
- t.timeout = args.timeout;
1309
1565
  return t;
1310
1566
  }, (qi, t) => qi.count(t), { build: (qi, t) => qi.buildCount(t), reshape: (raw) => raw }),
1311
- aggregate: (args) => defer(() => translateAggregateArgs(ctx, mm, args, false), (qi, t) => qi.aggregate(t).then((r) => reshapeAggregate(ctx, mm, r)), { build: (qi, t) => qi.buildAggregate(t), reshape: (raw) => reshapeAggregate(ctx, mm, raw) }),
1312
- groupBy: (args) => defer(() => translateAggregateArgs(ctx, mm, args, true), (qi, t) => qi.groupBy(t).then((rows) => rows.map((r) => reshapeGroupRow(ctx, mm, r))), {
1567
+ aggregate: (args) => defer('aggregate', args, () => translateAggregateArgs(ctx, mm, args, false), (qi, t) => qi.aggregate(t).then((r) => reshapeAggregate(ctx, mm, r)), { build: (qi, t) => qi.buildAggregate(t), reshape: (raw) => reshapeAggregate(ctx, mm, raw) }),
1568
+ groupBy: (args) => defer('groupBy', args, () => translateAggregateArgs(ctx, mm, args, true), (qi, t) => qi.groupBy(t).then((rows) => rows.map((r) => reshapeGroupRow(ctx, mm, r))), {
1313
1569
  build: (qi, t) => qi.buildGroupBy(t),
1314
1570
  reshape: (raw) => raw.map((r) => reshapeGroupRow(ctx, mm, r)),
1315
1571
  }),
@@ -8,6 +8,8 @@
8
8
  export type { AggregateArgs, AggregateResult, ArrayFilter, ColumnRef, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByAggregateSpec, GroupByArgs, GroupByDistinctOn, GroupByResult, HavingClause, JsonFilter, JsonPathAggregateTarget, JsonPathGroupKey, JsonPathOrderBy, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderByObject, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, RelationPickBy, RelationPickOrderBy, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithOrderByObject, WithResult, } from './types.js';
9
9
  export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, InsertStatementInput, UpsertStatementInput, } from '../dialect.js';
10
10
  export { postgresDialect } from '../dialect.js';
11
+ export type { OptionKind, OptionTable } from './option-surface.js';
12
+ export { AGGREGATE_OPTIONS, ALL_OPTION_TABLES, applyNativeOptions, COUNT_OPTIONS, CREATE_MANY_OPTIONS, CREATE_OPTIONS, DELETE_MANY_OPTIONS, DELETE_OPTIONS, FIND_MANY_OPTIONS, FIND_MANY_STREAM_OPTIONS, FIND_UNIQUE_OPTIONS, GROUP_BY_OPTIONS, optionKeysOfKind, UPDATE_MANY_OPTIONS, UPDATE_OPTIONS, UPSERT_OPTIONS, } from './option-surface.js';
11
13
  export type { SqlCacheEntry } from './utils.js';
12
14
  export { buildCorrelation, escapeLike, escSingleQuote, fnv1a64Hex, LRUCache, OPERATOR_KEYS, quoteIdent, sqlToPreparedName, } from './utils.js';
13
15
  export type { DeferredQuery, MiddlewareFn, QueryEvent, QueryEventListener, QueryInterfaceOptions, ReselectExecutor, TemporalInfinityReading, } from './builder.js';
@@ -7,9 +7,26 @@
7
7
  * former monolithic `import { … } from './query.js'`.
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.QueryInterface = exports.AUTO_TO_ONE_JOIN_ROWS_MIN = exports.AUTO_TO_ONE_JOIN_ROWS_MAX = exports.AUTO_TO_ONE_JOIN_MAX_ROWS = exports.AUTO_JOIN_PENALTY_MS_PER_ROW = exports.AUTO_COUNT_BATCH_MIN_PARENT_ROWS = exports.AUTO_ASSUMED_ROUND_TRIP_MS = exports.sqlToPreparedName = exports.quoteIdent = exports.OPERATOR_KEYS = exports.LRUCache = exports.fnv1a64Hex = exports.escSingleQuote = exports.escapeLike = exports.buildCorrelation = exports.postgresDialect = void 0;
10
+ exports.QueryInterface = exports.AUTO_TO_ONE_JOIN_ROWS_MIN = exports.AUTO_TO_ONE_JOIN_ROWS_MAX = exports.AUTO_TO_ONE_JOIN_MAX_ROWS = exports.AUTO_JOIN_PENALTY_MS_PER_ROW = exports.AUTO_COUNT_BATCH_MIN_PARENT_ROWS = exports.AUTO_ASSUMED_ROUND_TRIP_MS = exports.sqlToPreparedName = exports.quoteIdent = exports.OPERATOR_KEYS = exports.LRUCache = exports.fnv1a64Hex = exports.escSingleQuote = exports.escapeLike = exports.buildCorrelation = exports.UPSERT_OPTIONS = exports.UPDATE_OPTIONS = exports.UPDATE_MANY_OPTIONS = exports.optionKeysOfKind = exports.GROUP_BY_OPTIONS = exports.FIND_UNIQUE_OPTIONS = exports.FIND_MANY_STREAM_OPTIONS = exports.FIND_MANY_OPTIONS = exports.DELETE_OPTIONS = exports.DELETE_MANY_OPTIONS = exports.CREATE_OPTIONS = exports.CREATE_MANY_OPTIONS = exports.COUNT_OPTIONS = exports.applyNativeOptions = exports.ALL_OPTION_TABLES = exports.AGGREGATE_OPTIONS = exports.postgresDialect = void 0;
11
11
  var dialect_js_1 = require("../dialect.js");
12
12
  Object.defineProperty(exports, "postgresDialect", { enumerable: true, get: function () { return dialect_js_1.postgresDialect; } });
13
+ var option_surface_js_1 = require("./option-surface.js");
14
+ Object.defineProperty(exports, "AGGREGATE_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.AGGREGATE_OPTIONS; } });
15
+ Object.defineProperty(exports, "ALL_OPTION_TABLES", { enumerable: true, get: function () { return option_surface_js_1.ALL_OPTION_TABLES; } });
16
+ Object.defineProperty(exports, "applyNativeOptions", { enumerable: true, get: function () { return option_surface_js_1.applyNativeOptions; } });
17
+ Object.defineProperty(exports, "COUNT_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.COUNT_OPTIONS; } });
18
+ Object.defineProperty(exports, "CREATE_MANY_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.CREATE_MANY_OPTIONS; } });
19
+ Object.defineProperty(exports, "CREATE_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.CREATE_OPTIONS; } });
20
+ Object.defineProperty(exports, "DELETE_MANY_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.DELETE_MANY_OPTIONS; } });
21
+ Object.defineProperty(exports, "DELETE_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.DELETE_OPTIONS; } });
22
+ Object.defineProperty(exports, "FIND_MANY_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.FIND_MANY_OPTIONS; } });
23
+ Object.defineProperty(exports, "FIND_MANY_STREAM_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.FIND_MANY_STREAM_OPTIONS; } });
24
+ Object.defineProperty(exports, "FIND_UNIQUE_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.FIND_UNIQUE_OPTIONS; } });
25
+ Object.defineProperty(exports, "GROUP_BY_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.GROUP_BY_OPTIONS; } });
26
+ Object.defineProperty(exports, "optionKeysOfKind", { enumerable: true, get: function () { return option_surface_js_1.optionKeysOfKind; } });
27
+ Object.defineProperty(exports, "UPDATE_MANY_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.UPDATE_MANY_OPTIONS; } });
28
+ Object.defineProperty(exports, "UPDATE_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.UPDATE_OPTIONS; } });
29
+ Object.defineProperty(exports, "UPSERT_OPTIONS", { enumerable: true, get: function () { return option_surface_js_1.UPSERT_OPTIONS; } });
13
30
  var utils_js_1 = require("./utils.js");
14
31
  Object.defineProperty(exports, "buildCorrelation", { enumerable: true, get: function () { return utils_js_1.buildCorrelation; } });
15
32
  Object.defineProperty(exports, "escapeLike", { enumerable: true, get: function () { return utils_js_1.escapeLike; } });
@@ -0,0 +1,100 @@
1
+ /**
2
+ * turbine-orm, the query-argument OPTION SURFACE as runtime data.
3
+ *
4
+ * ## Why this file exists
5
+ *
6
+ * TypeScript erases interfaces, so `FindManyArgs` does not exist at runtime and
7
+ * any layer that has to decide, key by key, what to do with an args object has
8
+ * to keep its own hand-written list. `turbine-orm/prisma-compat` is exactly
9
+ * such a layer: it builds a FRESH turbine args object out of Prisma-shaped
10
+ * input and copies over the keys it recognizes. Every time core gained a
11
+ * query-level option, that ad-hoc allowlist silently failed to gain it, and the
12
+ * option was accepted by the caller's type-checker and then dropped on the
13
+ * floor. There was no feedback of any kind: no error, no warning, no test.
14
+ *
15
+ * These tables are the fix. Each one is a `Record<keyof SomeArgs<Row>,
16
+ * OptionKind>`, the same mechanism `TURBINE_CONFIG_KEYS` (client.ts) uses for
17
+ * the client-config surface, and it binds the compiler in BOTH directions:
18
+ *
19
+ * - add an option to an arg interface and this file stops compiling until a
20
+ * human classifies it ("Property 'fooMode' is missing in type ..."), so an
21
+ * option can no longer be stranded BY OMISSION;
22
+ * - list a key here that is not on the interface and it fails as an excess
23
+ * property, so a table can never drift into describing an option that does
24
+ * not exist.
25
+ *
26
+ * It deliberately does NOT make "add the option in one place" sufficient: it
27
+ * makes the second edit a BUILD FAILURE rather than a silent drop. That trade is
28
+ * intentional. A passthrough-by-default translator would satisfy the shorter
29
+ * wording and be actively wrong, because two of the options below carry FIELD
30
+ * NAMES in their values (`optimisticLock.field`, `distinctOn.columns`), which a
31
+ * compat layer must rename before core ever sees them. Copying those blind
32
+ * works on a schema whose names happen to coincide and breaks on one that
33
+ * renames a column, i.e. it makes the failure mode depend on the schema.
34
+ *
35
+ * ## THE ONE RULE for classifying a new option
36
+ *
37
+ * Classify a key `'native'` ONLY when its value contains no field, relation,
38
+ * column, or model NAME. If the value names anything in the schema, it is
39
+ * `'prisma'`: a name-translating consumer has to walk it by hand.
40
+ *
41
+ * @module
42
+ */
43
+ import type { AggregateArgs, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, UpdateArgs, UpdateManyArgs, UpsertArgs } from './types.js';
44
+ /**
45
+ * How a name-translating consumer (today: `turbine-orm/prisma-compat`) must
46
+ * handle one key of a turbine query-arg interface.
47
+ *
48
+ * - `'prisma'`, the key is a Prisma concept too, or its VALUE carries names
49
+ * that live in the caller's naming space. Translated by hand; NEVER copied
50
+ * verbatim.
51
+ * - `'native'`, turbine-only and its value is opaque to naming (a boolean, a
52
+ * number, a list of table names). Copied through untouched.
53
+ * - `'nativeAlias'`, the turbine SPELLING of a concept the caller's surface
54
+ * already has under another name (`with`/`limit`/`offset` vs
55
+ * `include`/`take`/`skip`). Refused, because forwarding it would collide with
56
+ * the translated key and would carry turbine relation names into a call
57
+ * written in the caller's names. The diagnostic names the right key instead.
58
+ * - `'internal'`, not reachable through the compat surface at all
59
+ * (`batchSize` belongs to a streaming method compat does not expose), so it
60
+ * is not part of any known set and passing it is reported as unknown.
61
+ */
62
+ export type OptionKind = 'prisma' | 'native' | 'nativeAlias' | 'internal';
63
+ /**
64
+ * The generic parameter the tables are instantiated at. `keyof FindManyArgs<T>`
65
+ * is the literal union of the DECLARED key names regardless of `T`, so a
66
+ * neutral row type keeps the tables stable and free of entity coupling.
67
+ */
68
+ type Row = Record<string, unknown>;
69
+ /** One option table: every declared key of one arg interface, classified. */
70
+ export type OptionTable<A> = Readonly<Record<keyof A, OptionKind>>;
71
+ export declare const FIND_UNIQUE_OPTIONS: OptionTable<FindUniqueArgs<Row>>;
72
+ export declare const FIND_MANY_OPTIONS: OptionTable<FindManyArgs<Row>>;
73
+ export declare const FIND_MANY_STREAM_OPTIONS: OptionTable<FindManyStreamArgs<Row>>;
74
+ export declare const CREATE_OPTIONS: OptionTable<CreateArgs<Row>>;
75
+ export declare const CREATE_MANY_OPTIONS: OptionTable<CreateManyArgs<Row>>;
76
+ export declare const UPDATE_OPTIONS: OptionTable<UpdateArgs<Row>>;
77
+ export declare const UPDATE_MANY_OPTIONS: OptionTable<UpdateManyArgs<Row>>;
78
+ export declare const DELETE_OPTIONS: OptionTable<DeleteArgs<Row>>;
79
+ export declare const DELETE_MANY_OPTIONS: OptionTable<DeleteManyArgs<Row>>;
80
+ export declare const UPSERT_OPTIONS: OptionTable<UpsertArgs<Row>>;
81
+ export declare const COUNT_OPTIONS: OptionTable<CountArgs<Row>>;
82
+ export declare const AGGREGATE_OPTIONS: OptionTable<AggregateArgs<Row>>;
83
+ export declare const GROUP_BY_OPTIONS: OptionTable<GroupByArgs<Row>>;
84
+ /**
85
+ * Every table, so a test can assert the set is complete and well-formed without
86
+ * naming each one (a table stubbed out during a refactor shows up here).
87
+ */
88
+ export declare const ALL_OPTION_TABLES: Readonly<Record<string, Readonly<Record<string, OptionKind>>>>;
89
+ /**
90
+ * Copy every `'native'` key present on `src` onto `dst`.
91
+ *
92
+ * Iterates `src` (a small caller-supplied object) rather than the table, so the
93
+ * cost is proportional to what was actually passed. `undefined` values are
94
+ * skipped: `{ ...maybeOpts }` routinely materializes keys with no value, and
95
+ * writing `undefined` through would be indistinguishable from passing it.
96
+ */
97
+ export declare function applyNativeOptions(table: Readonly<Record<string, OptionKind>>, src: Record<string, unknown>, dst: Record<string, unknown>): void;
98
+ /** The keys of `table` with the given kind, as a set. */
99
+ export declare function optionKeysOfKind(table: Readonly<Record<string, OptionKind>>, ...kinds: OptionKind[]): string[];
100
+ export {};