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