turbine-orm 0.40.1 → 0.41.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 (63) hide show
  1. package/README.md +22 -4
  2. package/dist/cjs/cli/config.js +3 -0
  3. package/dist/cjs/cli/index.js +179 -0
  4. package/dist/cjs/cli/prisma-report.js +216 -0
  5. package/dist/cjs/cli/prisma-resolve.js +335 -0
  6. package/dist/cjs/cli/prisma-schema.js +484 -0
  7. package/dist/cjs/client.js +1 -0
  8. package/dist/cjs/generate.js +279 -22
  9. package/dist/cjs/index.js +3 -2
  10. package/dist/cjs/introspect.js +203 -26
  11. package/dist/cjs/mssql.js +9 -10
  12. package/dist/cjs/mysql.js +3 -9
  13. package/dist/cjs/powdb-introspect.js +5 -10
  14. package/dist/cjs/powql.js +13 -0
  15. package/dist/cjs/prisma-compat.js +1147 -0
  16. package/dist/cjs/query/aggregates.js +67 -7
  17. package/dist/cjs/query/builder.js +388 -17
  18. package/dist/cjs/query/compound-unique.js +0 -0
  19. package/dist/cjs/query/relations.js +7 -5
  20. package/dist/cjs/query/warn-registry.js +98 -0
  21. package/dist/cjs/query/writes.js +13 -5
  22. package/dist/cjs/schema.js +47 -0
  23. package/dist/cjs/sqlite.js +4 -9
  24. package/dist/cli/config.d.ts +26 -0
  25. package/dist/cli/config.js +3 -0
  26. package/dist/cli/index.d.ts +11 -0
  27. package/dist/cli/index.js +180 -1
  28. package/dist/cli/prisma-report.d.ts +19 -0
  29. package/dist/cli/prisma-report.js +211 -0
  30. package/dist/cli/prisma-resolve.d.ts +87 -0
  31. package/dist/cli/prisma-resolve.js +330 -0
  32. package/dist/cli/prisma-schema.d.ts +116 -0
  33. package/dist/cli/prisma-schema.js +479 -0
  34. package/dist/cli/ui.d.ts +1 -1
  35. package/dist/client.d.ts +18 -2
  36. package/dist/client.js +1 -0
  37. package/dist/generate.d.ts +80 -1
  38. package/dist/generate.js +277 -25
  39. package/dist/index.d.ts +2 -2
  40. package/dist/index.js +1 -1
  41. package/dist/introspect.d.ts +92 -2
  42. package/dist/introspect.js +198 -26
  43. package/dist/mssql.js +10 -11
  44. package/dist/mysql.js +4 -10
  45. package/dist/powdb-introspect.js +5 -10
  46. package/dist/powql.js +13 -0
  47. package/dist/prisma-compat.d.ts +281 -0
  48. package/dist/prisma-compat.js +1143 -0
  49. package/dist/query/aggregates.js +67 -7
  50. package/dist/query/builder.d.ts +77 -4
  51. package/dist/query/builder.js +390 -19
  52. package/dist/query/compound-unique.d.ts +49 -0
  53. package/dist/query/compound-unique.js +0 -0
  54. package/dist/query/deferred.d.ts +18 -0
  55. package/dist/query/relations.js +7 -5
  56. package/dist/query/types.d.ts +70 -9
  57. package/dist/query/warn-registry.d.ts +57 -0
  58. package/dist/query/warn-registry.js +92 -0
  59. package/dist/query/writes.js +13 -5
  60. package/dist/schema.d.ts +75 -0
  61. package/dist/schema.js +46 -0
  62. package/dist/sqlite.js +5 -10
  63. package/package.json +6 -1
@@ -0,0 +1,1147 @@
1
+ "use strict";
2
+ /**
3
+ * turbine-orm/prisma-compat, a typed PrismaClient-surface adapter over a
4
+ * {@link TurbineClient}.
5
+ *
6
+ * This subpath lets a codebase that speaks Prisma's `db.model.findMany(...)`
7
+ * surface run on Turbine with minimal churn. It is a **pure TypeScript shim**:
8
+ * zero new runtime dependencies, never imported by Turbine core, and driven
9
+ * entirely by the {@link PrismaCompatMap} that `turbine migrate-from-prisma`
10
+ * emits (`prisma-map.ts`). Every Prisma model / field / relation / compound
11
+ * unique name in the map was resolved against live introspected metadata, so
12
+ * the adapter only ever translates names it can prove exist.
13
+ *
14
+ * ## What it does
15
+ *
16
+ * - **Model delegates** under both the Prisma model name (`compat.User`) and
17
+ * Prisma's client-property spelling with the first letter lowercased
18
+ * (`compat.user`, what generated Prisma call sites actually use),
19
+ * translating args recursively: `include`→`with`, `select` split into scalar
20
+ * selection + relations, field/relation renames both ways through the map,
21
+ * `take`/`skip`→`limit`/`offset`, cursor pagination, and compound-unique
22
+ * selectors (including custom `@@unique(name:)` names the core `findUnique`
23
+ * derivation cannot know).
24
+ * - **`$transaction`** in both forms: a callback (`$transaction(async (tx) => …)`)
25
+ * and Prisma's lazy array batching (`$transaction([a.create(...), b.update(...)])`)
26
+ * , the un-awaited delegate calls defer to Turbine's `build*()` methods and
27
+ * run atomically through the core batch `$transaction([...])` path.
28
+ * - **Raw SQL**: `$queryRaw` / `$executeRaw` tagged templates (with
29
+ * `Prisma.sql`-style nested-fragment flattening) and the `*Unsafe` variants.
30
+ * - **Result reshaping**: `_count` objects keyed back to Prisma relation names,
31
+ * and to-one relations surfaced as `object | null`.
32
+ *
33
+ * ## What it deliberately does NOT do (documented divergences)
34
+ *
35
+ * These cannot be faithfully translated and are not attempted; each throws or is
36
+ * documented rather than silently returning wrong data:
37
+ *
38
+ * - `$extends` / client extensions, `$use` with Prisma's middleware param shape.
39
+ * - `instanceof PrismaClientKnownRequestError`, `.meta`/message byte parity
40
+ * (opt into `prismaErrorCodes` for a `.code` like `P2002`, without pretending
41
+ * `instanceof` identity).
42
+ * - `Prisma.join` / `Prisma.raw` composition beyond plain fragment flattening.
43
+ * - Fluent relation chaining (`prisma.user.findUnique().posts()`).
44
+ * - Accelerate / Pulse / driver-adapter preview features, the Mongo API, and the
45
+ * `prisma migrate`/`db` CLI family (Turbine ships its own migrations).
46
+ * - **Inclusive bare cursors whose field is not the sort key**, see
47
+ * {@link translateCursor}. A bare Prisma cursor is inclusive; translating it
48
+ * correctly needs the anchor row's sort-key value. When the cursor field is
49
+ * the single `orderBy` field (or the cursor is the single-column PK with no
50
+ * `orderBy`) it compiles to a `gte`/`lte` keyset predicate; **otherwise it
51
+ * throws** an {@link UnsupportedFeatureError} rather than emit an off-by-one
52
+ * page. Pair the cursor with `skip: 1` (Prisma's idiom) for the exact
53
+ * exclusive-cursor + `offset` translation.
54
+ * - **Negative `take`** (take-from-end) and **`skip` on a nested relation
55
+ * include** throw, Turbine's `with` clause has no offset and no reverse-take.
56
+ *
57
+ * ## Type dependencies (0.41.0)
58
+ *
59
+ * - Field-name identity fast path pairs with the generator's
60
+ * `--keep-column-names` output (byte-shaped like a snake_case Prisma client).
61
+ * - Compound-unique default selectors are handled by the core
62
+ * `findUnique`-family derivation; this adapter only translates custom
63
+ * `@@unique(name:)` names on top.
64
+ * - To-one relations return `object | null` natively once the unique-FK hasOne
65
+ * introspection default is in effect (0.41.0). Until a schema is regenerated,
66
+ * the map's `cardinality: 'one'` still drives the first-element-or-null guard,
67
+ * so the surface is correct either way.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { TurbineClient } from 'turbine-orm';
72
+ * import { createPrismaCompatClient } from 'turbine-orm/prisma-compat';
73
+ * import { SCHEMA } from './generated/turbine/metadata.js';
74
+ * import { PRISMA_MAP } from './generated/turbine/prisma-map.js';
75
+ *
76
+ * const db = new TurbineClient({ connectionString: process.env.DATABASE_URL }, SCHEMA);
77
+ * const prisma = createPrismaCompatClient(db, PRISMA_MAP);
78
+ *
79
+ * const users = await prisma.User.findMany({
80
+ * where: { email: { contains: '@acme.com' } },
81
+ * include: { posts: { orderBy: { createdAt: 'desc' }, take: 5 } },
82
+ * });
83
+ * ```
84
+ */
85
+ Object.defineProperty(exports, "__esModule", { value: true });
86
+ exports.COMPAT_DEFERRED = exports.Prisma = void 0;
87
+ exports.createPrismaCompatClient = createPrismaCompatClient;
88
+ const errors_js_1 = require("./errors.js");
89
+ // ---------------------------------------------------------------------------
90
+ // Prisma.sql-style raw fragments (local, minimal, never imports @prisma/client)
91
+ // ---------------------------------------------------------------------------
92
+ const SQL_FRAGMENT = Symbol.for('turbine.prismaCompat.sqlFragment');
93
+ function isSqlFragment(x) {
94
+ return typeof x === 'object' && x !== null && x[SQL_FRAGMENT] === true;
95
+ }
96
+ function makeSql(strings, values) {
97
+ return { strings, values, [SQL_FRAGMENT]: true };
98
+ }
99
+ /**
100
+ * `Prisma`-compatible raw-SQL helpers, a minimal local implementation so
101
+ * migrated `Prisma.sql\`…\`` / `Prisma.join(...)` / `Prisma.raw(...)` calls keep
102
+ * composing. Fragments are flattened at execution time into a single
103
+ * parameterized statement (values become `$N`), so composition is injection-safe
104
+ * by construction.
105
+ */
106
+ exports.Prisma = {
107
+ /** Tagged-template fragment: `Prisma.sql\`id = ${id}\``. */
108
+ sql(strings, ...values) {
109
+ return makeSql(strings, values);
110
+ },
111
+ /**
112
+ * Join fragments/values with a separator: `Prisma.join([1, 2, 3])` →
113
+ * `$1, $2, $3`. Each element that is not already a fragment becomes a bound
114
+ * value.
115
+ */
116
+ join(items, separator = ',', prefix = '', suffix = '') {
117
+ if (items.length === 0)
118
+ return makeSql([`${prefix}${suffix}`], []);
119
+ const strings = [prefix];
120
+ const values = [];
121
+ items.forEach((item, i) => {
122
+ values.push(item);
123
+ strings.push(i === items.length - 1 ? suffix : separator);
124
+ });
125
+ return makeSql(strings, values);
126
+ },
127
+ /**
128
+ * A raw, unparameterized SQL fragment. The string is spliced verbatim, never
129
+ * pass user input here (matches Prisma's `Prisma.raw` contract).
130
+ */
131
+ raw(sql) {
132
+ return makeSql([sql], []);
133
+ },
134
+ /** An empty fragment. */
135
+ empty: makeSql([''], []),
136
+ };
137
+ /** Turbine error code → nearest Prisma `PXXXX` code. */
138
+ const PRISMA_ERROR_CODE = {
139
+ [errors_js_1.TurbineErrorCode.UNIQUE_VIOLATION]: 'P2002',
140
+ [errors_js_1.TurbineErrorCode.NOT_FOUND]: 'P2025',
141
+ [errors_js_1.TurbineErrorCode.FOREIGN_KEY_VIOLATION]: 'P2003',
142
+ [errors_js_1.TurbineErrorCode.NOT_NULL_VIOLATION]: 'P2011',
143
+ [errors_js_1.TurbineErrorCode.TIMEOUT]: 'P2024',
144
+ };
145
+ /** Attach a Prisma-style `.code` to a TurbineError when the option is on. */
146
+ function decorate(err, prismaErrorCodes) {
147
+ if (prismaErrorCodes && err instanceof errors_js_1.TurbineError) {
148
+ const p = PRISMA_ERROR_CODE[err.code];
149
+ if (p)
150
+ err.code = p;
151
+ }
152
+ return err;
153
+ }
154
+ function buildLookups(mm) {
155
+ const reverseFields = {};
156
+ let identityFields = true;
157
+ for (const [prismaField, turbineField] of Object.entries(mm.fields)) {
158
+ reverseFields[turbineField] = prismaField;
159
+ if (prismaField !== turbineField)
160
+ identityFields = false;
161
+ }
162
+ const reverseRelations = {};
163
+ for (const [prismaRel, rel] of Object.entries(mm.relations)) {
164
+ reverseRelations[rel.name] = { prismaName: prismaRel, cardinality: rel.cardinality };
165
+ }
166
+ return { reverseFields, identityFields, reverseRelations };
167
+ }
168
+ function lookupsFor(ctx, mm) {
169
+ let l = ctx.lookups.get(mm.table);
170
+ if (!l) {
171
+ l = buildLookups(mm);
172
+ ctx.lookups.set(mm.table, l);
173
+ }
174
+ return l;
175
+ }
176
+ /** Resolve a turbine relation's target Prisma model map (for nested translation). */
177
+ function relTargetModel(ctx, mm, turbineRel) {
178
+ const rd = ctx.schema.tables[mm.table]?.relations?.[turbineRel];
179
+ if (!rd)
180
+ return undefined;
181
+ const modelName = ctx.tableToModel.get(rd.to);
182
+ return modelName ? ctx.map.models[modelName] : undefined;
183
+ }
184
+ // ---------------------------------------------------------------------------
185
+ // Argument translation
186
+ // ---------------------------------------------------------------------------
187
+ const COMBINATORS = new Set(['AND', 'OR', 'NOT']);
188
+ const RELATION_QUANTIFIERS = new Set(['some', 'every', 'none']);
189
+ /** Whether a value is a plain object usable as a compound-unique selector. */
190
+ function isPlainObject(v) {
191
+ return (typeof v === 'object' &&
192
+ v !== null &&
193
+ !Array.isArray(v) &&
194
+ !(v instanceof Date) &&
195
+ !(typeof Buffer !== 'undefined' && Buffer.isBuffer(v)));
196
+ }
197
+ function renameField(mm, prismaField) {
198
+ return mm.fields[prismaField] ?? prismaField;
199
+ }
200
+ /**
201
+ * Translate a Prisma `where` (or nested relation where) into a Turbine `where`.
202
+ * Renames scalar field keys and relation keys through the map, recurses into
203
+ * `AND`/`OR`/`NOT`, translates relation quantifier filters (`some`/`every`/
204
+ * `none`) against the target model, and rewrites compound-unique selectors -
205
+ * including custom `@@unique(name:)` names, into the core-derived selector form
206
+ * so Turbine's `findUnique`-family expansion handles them uniformly.
207
+ */
208
+ function translateWhere(ctx, mm, where) {
209
+ if (!isPlainObject(where))
210
+ return where;
211
+ const out = {};
212
+ for (const [key, val] of Object.entries(where)) {
213
+ if (COMBINATORS.has(key)) {
214
+ out[key] = Array.isArray(val) ? val.map((v) => translateWhere(ctx, mm, v)) : translateWhere(ctx, mm, val);
215
+ continue;
216
+ }
217
+ // Compound-unique selector (custom or default Prisma name).
218
+ const compound = mm.compoundUniques[key];
219
+ if (compound && isPlainObject(val) && !mm.fields[key] && !mm.relations[key]) {
220
+ const inner = {};
221
+ for (const [pk, pv] of Object.entries(val))
222
+ inner[renameField(mm, pk)] = pv;
223
+ // Turbine's core derives the selector name from the underscore-join of the
224
+ // member FIELD names, so re-key custom names onto that canonical form.
225
+ out[compound.join('_')] = inner;
226
+ continue;
227
+ }
228
+ // Relation filter.
229
+ const rel = mm.relations[key];
230
+ if (rel) {
231
+ const target = relTargetModel(ctx, mm, rel.name);
232
+ out[rel.name] = translateRelationFilter(ctx, target, val);
233
+ continue;
234
+ }
235
+ // Scalar field, key renamed, value (literal or operator object) passes
236
+ // through unchanged (Prisma operator names match Turbine's).
237
+ out[renameField(mm, key)] = val;
238
+ }
239
+ return out;
240
+ }
241
+ function translateRelationFilter(ctx, target, val) {
242
+ if (!isPlainObject(val))
243
+ return val;
244
+ const keys = Object.keys(val);
245
+ const hasQuantifier = keys.some((k) => RELATION_QUANTIFIERS.has(k) || k === 'is' || k === 'isNot');
246
+ if (hasQuantifier) {
247
+ const out = {};
248
+ for (const [k, v] of Object.entries(val)) {
249
+ out[k] =
250
+ target && (RELATION_QUANTIFIERS.has(k) || k === 'is' || k === 'isNot') ? translateWhere(ctx, target, v) : v;
251
+ }
252
+ return out;
253
+ }
254
+ // A bare object filter on a to-one relation: translate its body.
255
+ return target ? translateWhere(ctx, target, val) : val;
256
+ }
257
+ /** Translate a Prisma `orderBy` (object / array) into a Turbine `orderBy`. */
258
+ function translateOrderBy(ctx, mm, ob) {
259
+ if (Array.isArray(ob))
260
+ return ob.map((o) => translateOrderBy(ctx, mm, o));
261
+ if (!isPlainObject(ob))
262
+ return ob;
263
+ const out = {};
264
+ for (const [key, val] of Object.entries(ob)) {
265
+ if (key === '_count') {
266
+ out._count = val;
267
+ continue;
268
+ }
269
+ const rel = mm.relations[key];
270
+ if (rel) {
271
+ const target = relTargetModel(ctx, mm, rel.name);
272
+ out[rel.name] = isPlainObject(val) && !('_count' in val) && target ? translateOrderBy(ctx, target, val) : val;
273
+ continue;
274
+ }
275
+ out[renameField(mm, key)] = val;
276
+ }
277
+ return out;
278
+ }
279
+ /** Map a Prisma `take` to a Turbine `limit`. Negative take is unsupported. */
280
+ function mapTake(take) {
281
+ if (take < 0) {
282
+ throw new errors_js_1.UnsupportedFeatureError('negative take (take-from-end pagination)', 'prisma-compat', 'Turbine has no reverse-take; reverse your orderBy and use a positive take instead.');
283
+ }
284
+ return take;
285
+ }
286
+ /**
287
+ * Translate Prisma `include` / `select` into Turbine `{ select, with }`.
288
+ * `include` keeps all scalars and adds relations; `select` narrows scalars and
289
+ * may also pull relations + `_count`. The two are mutually exclusive.
290
+ */
291
+ function translateProjection(ctx, mm, args) {
292
+ const include = args.include;
293
+ const select = args.select;
294
+ if (include && select) {
295
+ throw new errors_js_1.ValidationError('[turbine] prisma-compat: `include` and `select` are mutually exclusive.');
296
+ }
297
+ const withClause = {};
298
+ let hasWith = false;
299
+ if (include) {
300
+ for (const [key, val] of Object.entries(include)) {
301
+ if (val === false || val == null)
302
+ continue;
303
+ if (key === '_count') {
304
+ withClause._count = translateCountSelect(mm, val);
305
+ hasWith = true;
306
+ continue;
307
+ }
308
+ const rel = mm.relations[key];
309
+ if (!rel) {
310
+ throw new errors_js_1.ValidationError(`[turbine] prisma-compat: unknown relation "${key}" in include on model "${modelName(ctx, mm)}".`);
311
+ }
312
+ withClause[rel.name] = translateWithOption(ctx, mm, rel.name, val);
313
+ hasWith = true;
314
+ }
315
+ return { with: hasWith ? withClause : undefined };
316
+ }
317
+ if (select) {
318
+ const scalar = {};
319
+ let hasScalar = false;
320
+ for (const [key, val] of Object.entries(select)) {
321
+ if (val === false || val == null)
322
+ continue;
323
+ if (key === '_count') {
324
+ withClause._count = translateCountSelect(mm, val);
325
+ hasWith = true;
326
+ continue;
327
+ }
328
+ const rel = mm.relations[key];
329
+ if (rel) {
330
+ withClause[rel.name] = translateWithOption(ctx, mm, rel.name, val);
331
+ hasWith = true;
332
+ continue;
333
+ }
334
+ scalar[renameField(mm, key)] = true;
335
+ hasScalar = true;
336
+ }
337
+ return { select: hasScalar ? scalar : undefined, with: hasWith ? withClause : undefined };
338
+ }
339
+ return {};
340
+ }
341
+ /** Translate a Prisma relation include payload into a Turbine `WithOptions`. */
342
+ function translateWithOption(ctx, mm, turbineRel, val) {
343
+ if (val === true)
344
+ return true;
345
+ if (!isPlainObject(val))
346
+ return true;
347
+ const target = relTargetModel(ctx, mm, turbineRel);
348
+ const opt = {};
349
+ if (val.where !== undefined)
350
+ opt.where = target ? translateWhere(ctx, target, val.where) : val.where;
351
+ if (val.orderBy !== undefined)
352
+ opt.orderBy = target ? translateOrderBy(ctx, target, val.orderBy) : val.orderBy;
353
+ if (val.take !== undefined)
354
+ opt.limit = mapTake(val.take);
355
+ if (val.skip !== undefined) {
356
+ throw new errors_js_1.UnsupportedFeatureError('skip (offset) on a nested relation include', 'prisma-compat', "Turbine's `with` clause has no offset, page the relation with a separate query.");
357
+ }
358
+ if (target && (val.select !== undefined || val.include !== undefined)) {
359
+ const proj = translateProjection(ctx, target, val);
360
+ if (proj.select)
361
+ opt.select = proj.select;
362
+ if (proj.with)
363
+ opt.with = proj.with;
364
+ }
365
+ return opt;
366
+ }
367
+ /** Translate a Prisma `_count: { select: { rel: true } }` / `true` into Turbine `with._count`. */
368
+ function translateCountSelect(mm, val) {
369
+ if (val === true)
370
+ return true;
371
+ if (isPlainObject(val) && isPlainObject(val.select)) {
372
+ const out = {};
373
+ for (const [key, v] of Object.entries(val.select)) {
374
+ if (!v)
375
+ continue;
376
+ const rel = mm.relations[key];
377
+ if (rel)
378
+ out[rel.name] = true;
379
+ }
380
+ return out;
381
+ }
382
+ return true;
383
+ }
384
+ function modelName(ctx, mm) {
385
+ return ctx.tableToModel.get(mm.table) ?? mm.table;
386
+ }
387
+ /**
388
+ * Assemble Turbine findMany-family args from Prisma read args. Order matters:
389
+ * `where` / `orderBy` are translated first so the cursor step (which may inject
390
+ * a keyset predicate) operates in the translated turbine-field space.
391
+ */
392
+ function translateReadArgs(ctx, mm, prismaArgs) {
393
+ const t = {};
394
+ if (prismaArgs.where !== undefined)
395
+ t.where = translateWhere(ctx, mm, prismaArgs.where);
396
+ if (prismaArgs.orderBy !== undefined)
397
+ t.orderBy = translateOrderBy(ctx, mm, prismaArgs.orderBy);
398
+ const proj = translateProjection(ctx, mm, prismaArgs);
399
+ if (proj.select)
400
+ t.select = proj.select;
401
+ if (proj.with)
402
+ t.with = proj.with;
403
+ if (Array.isArray(prismaArgs.distinct)) {
404
+ t.distinct = prismaArgs.distinct.map((f) => renameField(mm, f));
405
+ }
406
+ if (prismaArgs.relationLoadStrategy !== undefined)
407
+ t.relationLoadStrategy = prismaArgs.relationLoadStrategy;
408
+ if (typeof prismaArgs.timeout === 'number')
409
+ t.timeout = prismaArgs.timeout;
410
+ if (ctx.options.stablePkOrder)
411
+ t.stableRelationOrder = true;
412
+ translateCursor(ctx, mm, prismaArgs, t);
413
+ return t;
414
+ }
415
+ /** turbine field names of the model's single-column primary key, if any. */
416
+ function singleColumnPkField(ctx, mm) {
417
+ const pk = ctx.schema.tables[mm.table]?.primaryKey;
418
+ if (pk && pk.length === 1) {
419
+ const col = pk[0];
420
+ return ctx.schema.tables[mm.table]?.reverseColumnMap?.[col] ?? col;
421
+ }
422
+ return undefined;
423
+ }
424
+ /**
425
+ * Translate Prisma cursor + take/skip onto Turbine args (operating on already
426
+ * translated `t.where` / `t.orderBy` in turbine-field space).
427
+ *
428
+ * - **No cursor:** `skip`→`offset`, `take`→`limit`.
429
+ * - **Cursor + `skip: n` (n≥1):** the idiomatic exclusive-pagination case →
430
+ * Turbine's exclusive `cursor` + `offset: n-1` (skip:1 → offset 0, an exact
431
+ * match).
432
+ * - **Bare inclusive cursor** (skip absent/0): a Prisma cursor is INCLUSIVE, so
433
+ * it needs the anchor's sort-key value. Compiled to a `gte`/`lte` keyset
434
+ * predicate merged into `where` ONLY when the cursor is single-field AND that
435
+ * field is the single `orderBy` field (or, with no `orderBy`, is the
436
+ * single-column PK). Any other shape THROWS rather than emit a wrong page -
437
+ * pair the cursor with `skip: 1` for the exact exclusive translation.
438
+ */
439
+ function translateCursor(ctx, mm, prismaArgs, t) {
440
+ const cursor = prismaArgs.cursor;
441
+ const skip = prismaArgs.skip;
442
+ const take = prismaArgs.take;
443
+ if (take !== undefined)
444
+ t.limit = mapTake(take);
445
+ if (cursor === undefined) {
446
+ if (skip !== undefined)
447
+ t.offset = skip;
448
+ return;
449
+ }
450
+ // Translate cursor field names (and expand a compound-unique selector cursor).
451
+ const tcursor = {};
452
+ for (const [key, val] of Object.entries(cursor)) {
453
+ if (val === undefined)
454
+ continue;
455
+ const compound = mm.compoundUniques[key];
456
+ if (compound && isPlainObject(val)) {
457
+ for (const [pk, pv] of Object.entries(val))
458
+ tcursor[renameField(mm, pk)] = pv;
459
+ }
460
+ else {
461
+ tcursor[renameField(mm, key)] = val;
462
+ }
463
+ }
464
+ const cursorFields = Object.keys(tcursor);
465
+ if (skip !== undefined && skip >= 1) {
466
+ t.cursor = tcursor;
467
+ t.offset = skip - 1;
468
+ return;
469
+ }
470
+ // Bare inclusive cursor (skip absent or 0).
471
+ if (cursorFields.length !== 1) {
472
+ throw new errors_js_1.UnsupportedFeatureError('inclusive multi-field cursor without skip', 'prisma-compat', 'Pair the cursor with `skip: 1` (the Prisma idiom) so it maps to an exact exclusive cursor.');
473
+ }
474
+ const field = cursorFields[0];
475
+ const value = tcursor[field];
476
+ const obEntries = orderByPairs(t.orderBy);
477
+ let desc = false;
478
+ if (obEntries.length === 0) {
479
+ const pkField = singleColumnPkField(ctx, mm);
480
+ if (!pkField || field !== pkField) {
481
+ throw new errors_js_1.UnsupportedFeatureError('inclusive cursor without orderBy on a non-PK field', 'prisma-compat', `A bare Prisma cursor is inclusive; translating it needs the cursor field "${field}" to be the single-column primary key, or to be the single orderBy field. Pair the cursor with \`skip: 1\`.`);
482
+ }
483
+ t.orderBy = { [field]: 'asc' };
484
+ }
485
+ else {
486
+ if (obEntries.length !== 1 || obEntries[0][0] !== field) {
487
+ throw new errors_js_1.UnsupportedFeatureError('inclusive cursor whose field is not the sort key', 'prisma-compat', `A bare Prisma cursor is inclusive; translating it needs the cursor field "${field}" to be the single orderBy field. Order by "${field}", or pair the cursor with \`skip: 1\`.`);
488
+ }
489
+ desc = obEntries[0][1];
490
+ }
491
+ const op = desc ? 'lte' : 'gte';
492
+ t.where = mergeKeyset(t.where ?? {}, field, op, value);
493
+ }
494
+ /** Flatten a Turbine orderBy (object or single-object array) into [field, isDesc] pairs. */
495
+ function orderByPairs(ob) {
496
+ const one = Array.isArray(ob) ? (ob.length === 1 ? ob[0] : undefined) : ob;
497
+ if (!isPlainObject(one))
498
+ return [];
499
+ const out = [];
500
+ for (const [k, v] of Object.entries(one)) {
501
+ const dir = isPlainObject(v) ? v.sort : v;
502
+ out.push([k, dir === 'desc']);
503
+ }
504
+ return out;
505
+ }
506
+ /** Merge a `{ field: { gte|lte: value } }` keyset predicate into a where object. */
507
+ function mergeKeyset(where, field, op, value) {
508
+ const merged = { ...where };
509
+ const existing = merged[field];
510
+ if (isPlainObject(existing)) {
511
+ merged[field] = { ...existing, [op]: value };
512
+ }
513
+ else if (existing !== undefined) {
514
+ const prevAnd = merged.AND;
515
+ const andList = Array.isArray(prevAnd) ? prevAnd : prevAnd !== undefined ? [prevAnd] : [];
516
+ merged.AND = [...andList, { [field]: { [op]: value } }];
517
+ }
518
+ else {
519
+ merged[field] = { [op]: value };
520
+ }
521
+ return merged;
522
+ }
523
+ // --- write-data translation (nested writes) -------------------------------
524
+ const NESTED_WRITE_OPS = new Set([
525
+ 'create',
526
+ 'createMany',
527
+ 'connect',
528
+ 'connectOrCreate',
529
+ 'disconnect',
530
+ 'set',
531
+ 'delete',
532
+ 'deleteMany',
533
+ 'update',
534
+ 'updateMany',
535
+ 'upsert',
536
+ ]);
537
+ /**
538
+ * Translate Prisma create/update `data` (including nested relation write ops)
539
+ * into Turbine's shape. Scalar keys are renamed via the map; relation keys are
540
+ * renamed to their Turbine relation name and their nested-write payloads are
541
+ * translated against the target model (op names match Prisma's).
542
+ */
543
+ function translateWriteData(ctx, mm, data) {
544
+ if (!isPlainObject(data))
545
+ return data;
546
+ const out = {};
547
+ for (const [key, val] of Object.entries(data)) {
548
+ const rel = mm.relations[key];
549
+ if (rel && isPlainObject(val) && Object.keys(val).some((k) => NESTED_WRITE_OPS.has(k))) {
550
+ const target = relTargetModel(ctx, mm, rel.name);
551
+ out[rel.name] = translateNestedWrite(ctx, target, val);
552
+ continue;
553
+ }
554
+ out[renameField(mm, key)] = val;
555
+ }
556
+ return out;
557
+ }
558
+ function translateNestedWrite(ctx, target, ops) {
559
+ const out = {};
560
+ for (const [op, payload] of Object.entries(ops)) {
561
+ switch (op) {
562
+ case 'create':
563
+ case 'createMany':
564
+ out[op] = mapMaybeArray(payload, (p) => (target ? translateWriteData(ctx, target, p) : p));
565
+ break;
566
+ case 'connect':
567
+ case 'disconnect':
568
+ case 'delete':
569
+ case 'set':
570
+ out[op] = mapMaybeArray(payload, (p) => (target ? translateWhere(ctx, target, p) : p));
571
+ break;
572
+ case 'deleteMany':
573
+ case 'updateMany':
574
+ out[op] = mapMaybeArray(payload, (p) => translateWhereDataPair(ctx, target, p));
575
+ break;
576
+ case 'update':
577
+ out[op] = mapMaybeArray(payload, (p) => translateWhereDataPair(ctx, target, p));
578
+ break;
579
+ case 'connectOrCreate':
580
+ out[op] = mapMaybeArray(payload, (p) => translateConnectOrCreate(ctx, target, p));
581
+ break;
582
+ case 'upsert':
583
+ out[op] = mapMaybeArray(payload, (p) => translateUpsertNested(ctx, target, p));
584
+ break;
585
+ default:
586
+ out[op] = payload;
587
+ }
588
+ }
589
+ return out;
590
+ }
591
+ function mapMaybeArray(val, fn) {
592
+ return Array.isArray(val) ? val.map(fn) : fn(val);
593
+ }
594
+ /** A `{ where?, data }` pair (nested update/updateMany), or a bare data object. */
595
+ function translateWhereDataPair(ctx, target, p) {
596
+ if (!isPlainObject(p))
597
+ return p;
598
+ if ('data' in p || 'where' in p) {
599
+ const out = {};
600
+ if (p.where !== undefined)
601
+ out.where = target ? translateWhere(ctx, target, p.where) : p.where;
602
+ if (p.data !== undefined)
603
+ out.data = target ? translateWriteData(ctx, target, p.data) : p.data;
604
+ return out;
605
+ }
606
+ return target ? translateWriteData(ctx, target, p) : p;
607
+ }
608
+ function translateConnectOrCreate(ctx, target, p) {
609
+ if (!isPlainObject(p))
610
+ return p;
611
+ const out = {};
612
+ if (p.where !== undefined)
613
+ out.where = target ? translateWhere(ctx, target, p.where) : p.where;
614
+ if (p.create !== undefined)
615
+ out.create = target ? translateWriteData(ctx, target, p.create) : p.create;
616
+ return out;
617
+ }
618
+ function translateUpsertNested(ctx, target, p) {
619
+ if (!isPlainObject(p))
620
+ return p;
621
+ const out = {};
622
+ if (p.where !== undefined)
623
+ out.where = target ? translateWhere(ctx, target, p.where) : p.where;
624
+ if (p.create !== undefined)
625
+ out.create = target ? translateWriteData(ctx, target, p.create) : p.create;
626
+ if (p.update !== undefined)
627
+ out.update = target ? translateWriteData(ctx, target, p.update) : p.update;
628
+ return out;
629
+ }
630
+ // --- aggregate / groupBy translation --------------------------------------
631
+ const AGG_FIELD_BLOCKS = ['_sum', '_avg', '_min', '_max'];
632
+ function translateAggregateArgs(ctx, mm, args, isGroupBy) {
633
+ const t = {};
634
+ if (args.where !== undefined)
635
+ t.where = translateWhere(ctx, mm, args.where);
636
+ if (args._count !== undefined)
637
+ t._count = renameAggBlock(mm, args._count, true);
638
+ for (const block of AGG_FIELD_BLOCKS) {
639
+ if (args[block] !== undefined)
640
+ t[block] = renameAggBlock(mm, args[block], false);
641
+ }
642
+ if (typeof args.timeout === 'number')
643
+ t.timeout = args.timeout;
644
+ if (isGroupBy) {
645
+ if (Array.isArray(args.by))
646
+ t.by = args.by.map((f) => renameField(mm, f));
647
+ if (args.orderBy !== undefined)
648
+ t.orderBy = translateOrderBy(ctx, mm, args.orderBy);
649
+ if (args.having !== undefined)
650
+ t.having = renameHaving(mm, args.having);
651
+ if (typeof args.take === 'number')
652
+ t.limit = mapTake(args.take);
653
+ if (typeof args.skip === 'number')
654
+ t.offset = args.skip;
655
+ }
656
+ return t;
657
+ }
658
+ /** Rename field keys inside an aggregate block; `_count`'s `_all` passes through. */
659
+ function renameAggBlock(mm, block, isCount) {
660
+ if (block === true || !isPlainObject(block))
661
+ return block;
662
+ const out = {};
663
+ for (const [key, val] of Object.entries(block)) {
664
+ if (isCount && key === '_all') {
665
+ out._all = val;
666
+ continue;
667
+ }
668
+ out[renameField(mm, key)] = val;
669
+ }
670
+ return out;
671
+ }
672
+ function renameHaving(mm, having) {
673
+ if (!isPlainObject(having))
674
+ return having;
675
+ const out = {};
676
+ for (const [key, val] of Object.entries(having)) {
677
+ if (COMBINATORS.has(key)) {
678
+ out[key] = Array.isArray(val) ? val.map((v) => renameHaving(mm, v)) : renameHaving(mm, val);
679
+ continue;
680
+ }
681
+ if (key === '_count') {
682
+ out._count = val;
683
+ continue;
684
+ }
685
+ out[renameField(mm, key)] = val;
686
+ }
687
+ return out;
688
+ }
689
+ // ---------------------------------------------------------------------------
690
+ // Result reshaping (turbine field/relation names → prisma names)
691
+ // ---------------------------------------------------------------------------
692
+ function reshapeRows(ctx, mm, rows) {
693
+ return Array.isArray(rows) ? rows.map((r) => reshapeRow(ctx, mm, r)) : rows;
694
+ }
695
+ function reshapeRowOrNull(ctx, mm, row) {
696
+ return row == null ? null : reshapeRow(ctx, mm, row);
697
+ }
698
+ function reshapeRow(ctx, mm, row) {
699
+ if (!isPlainObject(row))
700
+ return row;
701
+ const l = lookupsFor(ctx, mm);
702
+ const out = {};
703
+ for (const [key, val] of Object.entries(row)) {
704
+ if (key === '_count') {
705
+ out._count = reshapeCount(l, val);
706
+ continue;
707
+ }
708
+ const rel = l.reverseRelations[key];
709
+ if (rel) {
710
+ const target = relTargetModel(ctx, mm, key);
711
+ let rv;
712
+ if (Array.isArray(val)) {
713
+ const mapped = target ? val.map((x) => reshapeRow(ctx, target, x)) : val;
714
+ // To-one guard: legacy 'many' metadata may still surface an array; the
715
+ // map's cardinality is authoritative → first element or null.
716
+ rv = rel.cardinality === 'one' ? (mapped.length ? mapped[0] : null) : mapped;
717
+ }
718
+ else if (isPlainObject(val)) {
719
+ rv = target ? reshapeRow(ctx, target, val) : val;
720
+ }
721
+ else {
722
+ rv = val; // null to-one
723
+ }
724
+ out[rel.prismaName] = rv;
725
+ continue;
726
+ }
727
+ out[l.identityFields ? key : (l.reverseFields[key] ?? key)] = val;
728
+ }
729
+ return out;
730
+ }
731
+ function reshapeCount(l, count) {
732
+ if (!isPlainObject(count))
733
+ return count;
734
+ const out = {};
735
+ for (const [key, val] of Object.entries(count)) {
736
+ if (key === '_all') {
737
+ out._all = val;
738
+ continue;
739
+ }
740
+ out[l.reverseRelations[key]?.prismaName ?? key] = val;
741
+ }
742
+ return out;
743
+ }
744
+ /** Reshape an aggregate result: field keys inside blocks → prisma names. */
745
+ function reshapeAggregate(ctx, mm, res) {
746
+ if (!isPlainObject(res))
747
+ return res;
748
+ const l = lookupsFor(ctx, mm);
749
+ const out = {};
750
+ for (const [key, val] of Object.entries(res)) {
751
+ if (key === '_count') {
752
+ out._count = reshapeAggFieldBlock(l, val, true);
753
+ continue;
754
+ }
755
+ if (AGG_FIELD_BLOCKS.includes(key)) {
756
+ out[key] = reshapeAggFieldBlock(l, val, false);
757
+ continue;
758
+ }
759
+ out[l.identityFields ? key : (l.reverseFields[key] ?? key)] = val;
760
+ }
761
+ return out;
762
+ }
763
+ function reshapeAggFieldBlock(l, block, isCount) {
764
+ if (!isPlainObject(block))
765
+ return block;
766
+ const out = {};
767
+ for (const [key, val] of Object.entries(block)) {
768
+ if (isCount && key === '_all') {
769
+ out._all = val;
770
+ continue;
771
+ }
772
+ out[l.reverseFields[key] ?? key] = val;
773
+ }
774
+ return out;
775
+ }
776
+ /** Reshape a groupBy row: by-field keys + aggregate blocks → prisma names. */
777
+ function reshapeGroupRow(ctx, mm, row) {
778
+ if (!isPlainObject(row))
779
+ return row;
780
+ const l = lookupsFor(ctx, mm);
781
+ const out = {};
782
+ for (const [key, val] of Object.entries(row)) {
783
+ if (key === '_count') {
784
+ out._count = reshapeAggFieldBlock(l, val, true);
785
+ continue;
786
+ }
787
+ if (AGG_FIELD_BLOCKS.includes(key)) {
788
+ out[key] = reshapeAggFieldBlock(l, val, false);
789
+ continue;
790
+ }
791
+ out[l.identityFields ? key : (l.reverseFields[key] ?? key)] = val;
792
+ }
793
+ return out;
794
+ }
795
+ // ---------------------------------------------------------------------------
796
+ // Lazy PrismaPromise-style thenable + $transaction array batching seam
797
+ // ---------------------------------------------------------------------------
798
+ /** Symbol under which a lazy delegate call exposes its batchable plan. */
799
+ exports.COMPAT_DEFERRED = Symbol.for('turbine.prismaCompat.deferred');
800
+ /**
801
+ * A lazy, Prisma-style promise: the underlying query does not run until the
802
+ * value is awaited (`.then`), and a `$transaction([...])` array can instead pull
803
+ * the batchable plan via {@link COMPAT_DEFERRED} to run it atomically.
804
+ */
805
+ class CompatPromise {
806
+ run;
807
+ promise;
808
+ /** Absent for calls that cannot be a single DeferredQuery (rare). */
809
+ [exports.COMPAT_DEFERRED];
810
+ constructor(run, batchable) {
811
+ this.run = run;
812
+ this[exports.COMPAT_DEFERRED] = batchable;
813
+ }
814
+ exec() {
815
+ this.promise ??= this.run();
816
+ return this.promise;
817
+ }
818
+ // biome-ignore lint/suspicious/noThenProperty: a `then` member is the point, this is an intentional PrismaPromise-style thenable.
819
+ then(onFulfilled, onRejected) {
820
+ return this.exec().then(onFulfilled, onRejected);
821
+ }
822
+ catch(onRejected) {
823
+ return this.exec().catch(onRejected);
824
+ }
825
+ finally(onFinally) {
826
+ return this.exec().finally(onFinally);
827
+ }
828
+ }
829
+ function batchableOf(v) {
830
+ return v instanceof CompatPromise ? v[exports.COMPAT_DEFERRED] : undefined;
831
+ }
832
+ /**
833
+ * Prisma's client-property spelling of a model name: first letter lowercased
834
+ * (`model User` -> `prisma.user`). Returns null when the spelling is identical
835
+ * (already lowercase) so callers can skip the alias.
836
+ */
837
+ function prismaPropertyAlias(model) {
838
+ const alias = model.charAt(0).toLowerCase() + model.slice(1);
839
+ return alias === model ? null : alias;
840
+ }
841
+ // ---------------------------------------------------------------------------
842
+ // Delegate construction
843
+ // ---------------------------------------------------------------------------
844
+ /**
845
+ * Build one model delegate over a query-interface accessor. `getQI` returns the
846
+ * QueryInterface for this model's table on the active connection (the base pool,
847
+ * or a transaction's connection inside `$transaction(callback)`).
848
+ */
849
+ function makeDelegate(ctx, mm, getQI) {
850
+ const pe = ctx.options.prismaErrorCodes;
851
+ const lift = (run, batchable) => new CompatPromise(async () => {
852
+ try {
853
+ return await run();
854
+ }
855
+ catch (err) {
856
+ throw decorate(err, pe);
857
+ }
858
+ }, batchable);
859
+ const requireWhere = (args, op) => {
860
+ if (!args || args.where === undefined) {
861
+ throw new errors_js_1.ValidationError(`[turbine] prisma-compat: ${op} on "${modelName(ctx, mm)}" requires a \`where\`.`);
862
+ }
863
+ return args;
864
+ };
865
+ return {
866
+ findMany: (args = {}) => {
867
+ const t = translateReadArgs(ctx, mm, args);
868
+ return lift(() => getQI()
869
+ .findMany(t)
870
+ .then((r) => reshapeRows(ctx, mm, r)), { build: () => getQI().buildFindMany(t), reshape: (raw) => reshapeRows(ctx, mm, raw) });
871
+ },
872
+ findFirst: (args = {}) => {
873
+ const t = translateReadArgs(ctx, mm, args);
874
+ return lift(() => getQI()
875
+ .findFirst(t)
876
+ .then((r) => reshapeRowOrNull(ctx, mm, r)), { build: () => getQI().buildFindFirst(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) });
877
+ },
878
+ findUnique: (args) => {
879
+ const t = translateReadArgs(ctx, mm, requireWhere(args, 'findUnique'));
880
+ return lift(() => getQI()
881
+ .findUnique(t)
882
+ .then((r) => reshapeRowOrNull(ctx, mm, r)), { build: () => getQI().buildFindUnique(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) });
883
+ },
884
+ findFirstOrThrow: (args = {}) => {
885
+ const t = translateReadArgs(ctx, mm, args);
886
+ return lift(() => getQI()
887
+ .findFirstOrThrow(t)
888
+ .then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildFindFirstOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
889
+ },
890
+ findUniqueOrThrow: (args) => {
891
+ const t = translateReadArgs(ctx, mm, requireWhere(args, 'findUniqueOrThrow'));
892
+ return lift(() => getQI()
893
+ .findUniqueOrThrow(t)
894
+ .then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildFindUniqueOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
895
+ },
896
+ create: (args) => {
897
+ const t = { data: translateWriteData(ctx, mm, args.data) };
898
+ if (typeof args.timeout === 'number')
899
+ t.timeout = args.timeout;
900
+ return lift(() => getQI()
901
+ .create(t)
902
+ .then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildCreate(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
903
+ },
904
+ createMany: (args) => {
905
+ const data = args.data;
906
+ const rows = Array.isArray(data) ? data.map((d) => translateWriteData(ctx, mm, d)) : [];
907
+ const t = { data: rows };
908
+ if (args.skipDuplicates)
909
+ t.skipDuplicates = true;
910
+ return lift(() => getQI()
911
+ .createMany(t)
912
+ .then((r) => ({ count: r.length })), { build: () => getQI().buildCreateMany(t), reshape: (raw) => ({ count: raw.length }) });
913
+ },
914
+ update: (args) => {
915
+ const a = requireWhere(args, 'update');
916
+ const t = { where: translateWhere(ctx, mm, a.where), data: translateWriteData(ctx, mm, a.data) };
917
+ return lift(() => getQI()
918
+ .update(t)
919
+ .then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildUpdate(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
920
+ },
921
+ updateMany: (args) => {
922
+ const a = args;
923
+ const t = { where: translateWhere(ctx, mm, a.where ?? {}), data: translateWriteData(ctx, mm, a.data) };
924
+ if (a.where === undefined)
925
+ t.allowFullTableScan = true;
926
+ return lift(() => getQI().updateMany(t), {
927
+ build: () => getQI().buildUpdateMany(t),
928
+ reshape: (raw) => raw,
929
+ });
930
+ },
931
+ delete: (args) => {
932
+ const a = requireWhere(args, 'delete');
933
+ const t = { where: translateWhere(ctx, mm, a.where) };
934
+ return lift(() => getQI()
935
+ .delete(t)
936
+ .then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildDelete(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
937
+ },
938
+ deleteMany: (args = {}) => {
939
+ const a = args;
940
+ const t = { where: translateWhere(ctx, mm, a.where ?? {}) };
941
+ if (a.where === undefined)
942
+ t.allowFullTableScan = true;
943
+ return lift(() => getQI().deleteMany(t), {
944
+ build: () => getQI().buildDeleteMany(t),
945
+ reshape: (raw) => raw,
946
+ });
947
+ },
948
+ upsert: (args) => {
949
+ const a = requireWhere(args, 'upsert');
950
+ const t = {
951
+ where: translateWhere(ctx, mm, a.where),
952
+ create: translateWriteData(ctx, mm, a.create),
953
+ update: translateWriteData(ctx, mm, a.update),
954
+ };
955
+ return lift(() => getQI()
956
+ .upsert(t)
957
+ .then((r) => reshapeRow(ctx, mm, r)), { build: () => getQI().buildUpsert(t), reshape: (raw) => reshapeRow(ctx, mm, raw) });
958
+ },
959
+ count: (args = {}) => {
960
+ const t = {};
961
+ if (args.where !== undefined)
962
+ t.where = translateWhere(ctx, mm, args.where);
963
+ if (typeof args.timeout === 'number')
964
+ t.timeout = args.timeout;
965
+ return lift(() => getQI().count(t), {
966
+ build: () => getQI().buildCount(t),
967
+ reshape: (raw) => raw,
968
+ });
969
+ },
970
+ aggregate: (args) => {
971
+ const t = translateAggregateArgs(ctx, mm, args, false);
972
+ return lift(() => getQI()
973
+ .aggregate(t)
974
+ .then((r) => reshapeAggregate(ctx, mm, r)), { build: () => getQI().buildAggregate(t), reshape: (raw) => reshapeAggregate(ctx, mm, raw) });
975
+ },
976
+ groupBy: (args) => {
977
+ const t = translateAggregateArgs(ctx, mm, args, true);
978
+ return lift(() => getQI()
979
+ .groupBy(t)
980
+ .then((rows) => rows.map((r) => reshapeGroupRow(ctx, mm, r))), {
981
+ build: () => getQI().buildGroupBy(t),
982
+ reshape: (raw) => raw.map((r) => reshapeGroupRow(ctx, mm, r)),
983
+ });
984
+ },
985
+ };
986
+ }
987
+ function poolOf(db) {
988
+ const pool = db.pool;
989
+ if (!pool)
990
+ throw new errors_js_1.ValidationError('[turbine] prisma-compat: raw SQL needs a TurbineClient with an active pool.');
991
+ return pool;
992
+ }
993
+ function placeholderOf(db) {
994
+ const dialect = db.dialect;
995
+ return dialect ? (n) => dialect.paramPlaceholder(n) : (n) => `$${n}`;
996
+ }
997
+ /**
998
+ * Flatten a Prisma-style tagged template, including nested {@link Sql}
999
+ * fragments, into a single `{ text, params }` pair. Values only ever become
1000
+ * bound `$N` params (never string-concatenated), so composition is
1001
+ * injection-safe. `Prisma.raw(...)` fragments are the sole verbatim splice, by
1002
+ * contract.
1003
+ */
1004
+ function flattenTemplate(strings, values, ph) {
1005
+ const params = [];
1006
+ let text = '';
1007
+ const append = (segStrings, segValues) => {
1008
+ for (let i = 0; i < segStrings.length; i++) {
1009
+ text += segStrings[i];
1010
+ if (i < segValues.length) {
1011
+ const v = segValues[i];
1012
+ if (isSqlFragment(v)) {
1013
+ append(v.strings, v.values);
1014
+ }
1015
+ else {
1016
+ params.push(v);
1017
+ text += ph(params.length);
1018
+ }
1019
+ }
1020
+ }
1021
+ };
1022
+ append(strings, values);
1023
+ return { text, params };
1024
+ }
1025
+ // ---------------------------------------------------------------------------
1026
+ // createPrismaCompatClient
1027
+ // ---------------------------------------------------------------------------
1028
+ /**
1029
+ * Create a PrismaClient-surface adapter over a {@link TurbineClient}, driven by a
1030
+ * {@link PrismaCompatMap} (the `prisma-map.ts` that `turbine
1031
+ * migrate-from-prisma` emits).
1032
+ *
1033
+ * The returned object exposes a delegate per Prisma model name (by the map's
1034
+ * keys) plus the client-level `$transaction` / `$queryRaw` / `$executeRaw`
1035
+ * surface. Model and field names are translated through the map in both
1036
+ * directions; when field names are identity (the `--keep-column-names` pairing)
1037
+ * result rekeying is skipped entirely.
1038
+ *
1039
+ * @typeParam S - Per-model type bundles (from your generated entity types) for
1040
+ * full autocompletion. Defaults to a permissive shape.
1041
+ * @param client - The TurbineClient (or generated subclass / `turbineHttp` client).
1042
+ * @param map - The resolved `PRISMA_MAP`.
1043
+ * @param options - {@link PrismaCompatOptions}.
1044
+ */
1045
+ function createPrismaCompatClient(client, map, options = {}) {
1046
+ // The adapter only ever needs the narrow {@link CompatTurbineClient} surface;
1047
+ // the real client satisfies it structurally (the cast just relaxes the strict
1048
+ // callback-param variance on `$transaction`).
1049
+ const db = client;
1050
+ const tableToModel = new Map();
1051
+ for (const [prismaModel, mm] of Object.entries(map.models))
1052
+ tableToModel.set(mm.table, prismaModel);
1053
+ const ctx = {
1054
+ map,
1055
+ schema: db.schema,
1056
+ tableToModel,
1057
+ lookups: new Map(),
1058
+ options: {
1059
+ stablePkOrder: options.stablePkOrder ?? false,
1060
+ prismaErrorCodes: options.prismaErrorCodes ?? false,
1061
+ },
1062
+ };
1063
+ // Delegates bound to the base client (each call reads db.table(...) lazily).
1064
+ const delegates = new Map();
1065
+ for (const [prismaModel, mm] of Object.entries(map.models)) {
1066
+ delegates.set(prismaModel, makeDelegate(ctx, mm, () => db.table(mm.table)));
1067
+ }
1068
+ const ph = placeholderOf(db);
1069
+ const runRaw = async (text, params) => {
1070
+ try {
1071
+ return await poolOf(db).query(text, params);
1072
+ }
1073
+ catch (err) {
1074
+ throw decorate((0, errors_js_1.wrapPgError)(err), ctx.options.prismaErrorCodes);
1075
+ }
1076
+ };
1077
+ const base = {
1078
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1079
+ $transaction: ((arg, txOptions) => {
1080
+ // Array (lazy batch) form. Wrapped so validation/build errors REJECT the
1081
+ // returned promise (Prisma's $transaction is always thenable) rather than
1082
+ // throwing synchronously.
1083
+ if (Array.isArray(arg)) {
1084
+ return (async () => {
1085
+ try {
1086
+ const batchables = arg.map((p, i) => {
1087
+ const b = batchableOf(p);
1088
+ if (!b) {
1089
+ throw new errors_js_1.ValidationError(`[turbine] prisma-compat: $transaction([...]) item ${i} is not a lazy model call. Pass un-awaited delegate calls (e.g. prisma.User.create(...)).`);
1090
+ }
1091
+ return b;
1092
+ });
1093
+ const deferreds = batchables.map((b) => b.build());
1094
+ const results = (await db.$transaction(deferreds));
1095
+ return results.map((raw, i) => batchables[i].reshape(raw));
1096
+ }
1097
+ catch (err) {
1098
+ throw decorate(err, ctx.options.prismaErrorCodes);
1099
+ }
1100
+ })();
1101
+ }
1102
+ // Callback form: hand the user a compat client bound to the tx connection.
1103
+ const fn = arg;
1104
+ return db.$transaction((tx) => {
1105
+ const txDelegates = {};
1106
+ for (const [prismaModel, mm] of Object.entries(map.models)) {
1107
+ txDelegates[prismaModel] = makeDelegate(ctx, mm, () => tx.table(mm.table));
1108
+ const alias = prismaPropertyAlias(prismaModel);
1109
+ if (alias && !(alias in map.models) && !(alias in txDelegates)) {
1110
+ txDelegates[alias] = txDelegates[prismaModel];
1111
+ }
1112
+ }
1113
+ return fn(txDelegates);
1114
+ }, txOptions);
1115
+ }),
1116
+ $queryRaw: async (strings, ...values) => {
1117
+ const { text, params } = flattenTemplate(strings, values, ph);
1118
+ return (await runRaw(text, params)).rows;
1119
+ },
1120
+ $queryRawUnsafe: async (sql, ...params) => {
1121
+ return (await runRaw(sql, params)).rows;
1122
+ },
1123
+ $executeRaw: async (strings, ...values) => {
1124
+ const { text, params } = flattenTemplate(strings, values, ph);
1125
+ return (await runRaw(text, params)).rowCount ?? 0;
1126
+ },
1127
+ $executeRawUnsafe: async (sql, ...params) => {
1128
+ return (await runRaw(sql, params)).rowCount ?? 0;
1129
+ },
1130
+ $connect: async () => { },
1131
+ $disconnect: async () => { },
1132
+ };
1133
+ // Assemble the result: model delegates keyed by Prisma model name, plus the
1134
+ // client-level base methods. A plain object suffices, every model is a known
1135
+ // key from the map, so no dynamic-access proxy is needed.
1136
+ const result = { ...base };
1137
+ for (const [prismaModel, delegate] of delegates)
1138
+ result[prismaModel] = delegate;
1139
+ // Prisma-spelling aliases (`prisma.user` for `model User`). Skipped when the
1140
+ // lowercased name is itself a model or already taken (never shadow a real key).
1141
+ for (const [prismaModel, delegate] of delegates) {
1142
+ const alias = prismaPropertyAlias(prismaModel);
1143
+ if (alias && !(alias in result))
1144
+ result[alias] = delegate;
1145
+ }
1146
+ return result;
1147
+ }