prisma-generator-effect 1.0.0 → 1.1.2-beta.1

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 (3) hide show
  1. package/dist/index.js +998 -463
  2. package/dist/src/index.js +1976 -0
  3. package/package.json +19 -18
@@ -0,0 +1,1976 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const node_child_process_1 = require("node:child_process");
8
+ const promises_1 = __importDefault(require("node:fs/promises"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const generator_helper_1 = require("@prisma/generator-helper");
11
+ const header = `// This file was generated by prisma-generator-effect, do not edit manually.\n`;
12
+ // Utility function to convert PascalCase to camelCase
13
+ function toCamelCase(str) {
14
+ return str.charAt(0).toLowerCase() + str.slice(1);
15
+ }
16
+ (0, generator_helper_1.generatorHandler)({
17
+ onManifest() {
18
+ return {
19
+ defaultOutput: "../generated/effect",
20
+ prettyName: "Prisma Effect Generator",
21
+ // No engines required - we only read the DMMF schema
22
+ requiresEngines: [],
23
+ };
24
+ },
25
+ async onGenerate(options) {
26
+ const models = options.dmmf.datamodel.models;
27
+ const outputDir = options.generator.output?.value;
28
+ const schemaDir = node_path_1.default.dirname(options.schemaPath);
29
+ const configPath = options.generator.config.clientImportPath;
30
+ const clientImportPath = Array.isArray(configPath)
31
+ ? configPath[0]
32
+ : (configPath ?? "@prisma/client");
33
+ // Custom error configuration: "path/to/module#ErrorClassName"
34
+ // Path is relative to schema.prisma, e.g., "./errors#PrismaError"
35
+ // The module must export:
36
+ // - The error class (e.g., `export class PrismaError extends ...`)
37
+ // - A mapper function named `mapPrismaError` with signature:
38
+ // `(error: unknown, operation: string, model: string) => YourErrorType`
39
+ const errorConfigRaw = options.generator.config.errorImportPath;
40
+ const errorImportPathRaw = Array.isArray(errorConfigRaw)
41
+ ? errorConfigRaw[0]
42
+ : errorConfigRaw;
43
+ // Import file extension for generated imports (e.g., "js", "ts", or "" for no extension)
44
+ // Useful for ESM compatibility where imports need explicit extensions
45
+ const importExtConfigRaw = options.generator.config.importFileExtension;
46
+ const importFileExtension = Array.isArray(importExtConfigRaw)
47
+ ? importExtConfigRaw[0]
48
+ : (importExtConfigRaw ?? "");
49
+ // Telemetry configuration: enable Effect.fn tracing for all operations
50
+ // Set to "true" to wrap operations with Effect.fn (adds operation names to traces)
51
+ // Set to "false" to use Effect.fnUntraced (no telemetry overhead)
52
+ const telemetryConfigRaw = options.generator.config.enableTelemetry;
53
+ const enableTelemetry = Array.isArray(telemetryConfigRaw)
54
+ ? telemetryConfigRaw[0] === "true"
55
+ : telemetryConfigRaw === "true";
56
+ if (!outputDir) {
57
+ throw new Error("No output directory specified");
58
+ }
59
+ // Get datasource provider (e.g., "sqlite", "postgresql", "mysql", etc.)
60
+ // Used to avoid generating createManyAndReturn/updateManyAndReturn when the DB doesn't support them
61
+ const datasources = options.datasources;
62
+ const provider = datasources?.[0]?.provider;
63
+ const supportsManyAndReturn = provider === "postgresql" ||
64
+ provider === "postgres" ||
65
+ provider === "prisma+postgres" ||
66
+ provider === "cockroachdb" ||
67
+ provider === "sqlite";
68
+ // Helper to add file extension to a path if configured
69
+ const addExtension = (filePath) => {
70
+ if (!importFileExtension)
71
+ return filePath;
72
+ // Don't add extension if path already has one
73
+ const ext = node_path_1.default.extname(filePath);
74
+ if (ext)
75
+ return filePath;
76
+ return `${filePath}.${importFileExtension}`;
77
+ };
78
+ // Convert errorImportPath from schema-relative to output-relative
79
+ let errorImportPath;
80
+ if (errorImportPathRaw) {
81
+ const [modulePath, className] = errorImportPathRaw.split("#");
82
+ if (!modulePath || !className) {
83
+ throw new Error(`Invalid errorImportPath format: "${errorImportPathRaw}". Expected "path/to/module#ErrorClassName"`);
84
+ }
85
+ // If it's a relative path, convert from schema-relative to output-relative
86
+ if (modulePath.startsWith(".")) {
87
+ const absoluteErrorPath = node_path_1.default.resolve(schemaDir, modulePath);
88
+ const relativeToOutput = node_path_1.default.relative(outputDir, absoluteErrorPath);
89
+ // Ensure it starts with ./ or ../
90
+ const normalizedPath = relativeToOutput.startsWith(".")
91
+ ? relativeToOutput
92
+ : `./${relativeToOutput}`;
93
+ // Add file extension if configured
94
+ const pathWithExtension = addExtension(normalizedPath);
95
+ errorImportPath = `${pathWithExtension}#${className}`;
96
+ }
97
+ else {
98
+ // Package import (e.g., "@myorg/errors#PrismaError"), use as-is
99
+ errorImportPath = errorImportPathRaw;
100
+ }
101
+ }
102
+ // Clean output directory
103
+ await promises_1.default.rm(outputDir, { recursive: true, force: true });
104
+ await promises_1.default.mkdir(outputDir, { recursive: true });
105
+ // Generate unified index file with PrismaService
106
+ await generateUnifiedService([...models], outputDir, clientImportPath, errorImportPath, enableTelemetry, supportsManyAndReturn);
107
+ },
108
+ });
109
+ function generateRawSqlOperations(customError, enableTelemetry) {
110
+ // With custom error, use mapError which maps to user's error type
111
+ // Without custom error, use mapError which maps to PrismaError union
112
+ const errorType = customError ? customError.className : "PrismaError";
113
+ const wrapTelemetry = (name, generatorFn) => enableTelemetry
114
+ ? `Effect.fn("${name}")(${generatorFn})`
115
+ : `Effect.fnUntraced(${generatorFn})`;
116
+ return `
117
+ $executeRaw: ${wrapTelemetry("Prisma.$executeRaw", `function* (args) {
118
+ const actualClient = yield* clientOrTx(client);
119
+ return yield* Effect.tryPromise<any, ${errorType}>({
120
+ try: () => (Array.isArray(args) ? actualClient.$executeRaw(args[0], ...args.slice(1)) : actualClient.$executeRaw(args)) as any,
121
+ catch: (error) => mapError(error, "$executeRaw", "Prisma")
122
+ });
123
+ }`)},
124
+
125
+ $executeRawUnsafe: ${wrapTelemetry("Prisma.$executeRawUnsafe", `function* (query, ...values) {
126
+ const actualClient = yield* clientOrTx(client);
127
+ return yield* Effect.tryPromise<any, ${errorType}>({
128
+ try: () => actualClient.$executeRawUnsafe(query, ...values) as any,
129
+ catch: (error) => mapError(error, "$executeRawUnsafe", "Prisma")
130
+ });
131
+ }`)},
132
+
133
+ $queryRaw: ${wrapTelemetry("Prisma.$queryRaw", `function* (args) {
134
+ const actualClient = yield* clientOrTx(client);
135
+ return yield* Effect.tryPromise<any, ${errorType}>({
136
+ try: () => (Array.isArray(args) ? actualClient.$queryRaw(args[0], ...args.slice(1)) : actualClient.$queryRaw(args)) as any,
137
+ catch: (error) => mapError(error, "$queryRaw", "Prisma")
138
+ });
139
+ }`)},
140
+
141
+ $queryRawUnsafe: ${wrapTelemetry("Prisma.$queryRawUnsafe", `function* (query, ...values) {
142
+ const actualClient = yield* clientOrTx(client);
143
+ return yield* Effect.tryPromise<any, ${errorType}>({
144
+ try: () => actualClient.$queryRawUnsafe(query, ...values) as any,
145
+ catch: (error) => mapError(error, "$queryRawUnsafe", "Prisma")
146
+ });
147
+ }`)},`;
148
+ }
149
+ /**
150
+ * Generate type aliases for a model to reduce redundant type computation.
151
+ * TypeScript performance is significantly improved when complex types are
152
+ * computed once and reused via aliases rather than inline.
153
+ */
154
+ function generateModelTypeAliases(models, supportsManyAndReturn) {
155
+ const operations = [
156
+ "findUnique",
157
+ "findUniqueOrThrow",
158
+ "findFirst",
159
+ "findFirstOrThrow",
160
+ "findMany",
161
+ "create",
162
+ "createMany",
163
+ ...(supportsManyAndReturn ? ["createManyAndReturn"] : []),
164
+ "delete",
165
+ "update",
166
+ "deleteMany",
167
+ "updateMany",
168
+ ...(supportsManyAndReturn ? ["updateManyAndReturn"] : []),
169
+ "upsert",
170
+ "count",
171
+ "aggregate",
172
+ "groupBy",
173
+ ];
174
+ return models
175
+ .map((model) => {
176
+ const modelName = model.name;
177
+ const modelNameCamel = toCamelCase(modelName);
178
+ const argsAliases = operations
179
+ .map((op) => `type ${modelName}${capitalize(op)}Args = PrismaNamespace.Args<BasePrismaClient['${modelNameCamel}'], '${op}'>`)
180
+ .join("\n");
181
+ return argsAliases;
182
+ })
183
+ .join("\n\n");
184
+ }
185
+ /**
186
+ * Generate the IPrismaService interface that defines the contract for all Prisma operations.
187
+ * This interface is used to type-check the service implementation and provides explicit types.
188
+ */
189
+ function generatePrismaInterface(models, customError, supportsManyAndReturn) {
190
+ const errorType = customError ? customError.className : "PrismaError";
191
+ // Generate model operation interfaces
192
+ const modelInterfaces = models
193
+ .map((model) => {
194
+ const modelName = model.name;
195
+ const modelNameCamel = toCamelCase(modelName);
196
+ const delegate = `BasePrismaClient['${modelNameCamel}']`;
197
+ // Helper to get Args type alias
198
+ const argsType = (op) => `${modelName}${capitalize(op)}Args`;
199
+ // Helper for error types based on operation
200
+ const errorTypeFor = (op) => {
201
+ if (customError)
202
+ return customError.className;
203
+ // Map operations to their specific error types
204
+ if (["findUniqueOrThrow", "findFirstOrThrow"].includes(op)) {
205
+ return "PrismaFindOrThrowError";
206
+ }
207
+ else if ([
208
+ "findUnique",
209
+ "findFirst",
210
+ "findMany",
211
+ "count",
212
+ "aggregate",
213
+ "groupBy",
214
+ ].includes(op)) {
215
+ return "PrismaFindError";
216
+ }
217
+ else if (["create", "createMany", "createManyAndReturn"].includes(op)) {
218
+ return "PrismaCreateError";
219
+ }
220
+ else if (["update", "updateMany", "updateManyAndReturn", "upsert"].includes(op)) {
221
+ return "PrismaUpdateError";
222
+ }
223
+ else if (["delete", "deleteMany"].includes(op)) {
224
+ return "PrismaDeleteError";
225
+ }
226
+ return "PrismaError";
227
+ };
228
+ return ` ${modelNameCamel}: {
229
+ findUnique: <A extends ${argsType("findUnique")}>(
230
+ args: A
231
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'findUnique'> | null, ${errorTypeFor("findUnique")}>
232
+
233
+ findUniqueOrThrow: <A extends ${argsType("findUniqueOrThrow")}>(
234
+ args: A
235
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'findUniqueOrThrow'>, ${errorTypeFor("findUniqueOrThrow")}>
236
+
237
+ findFirst: <A extends ${argsType("findFirst")}>(
238
+ args: A
239
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'findFirst'> | null, ${errorTypeFor("findFirst")}>
240
+
241
+ findFirstOrThrow: <A extends ${argsType("findFirstOrThrow")}>(
242
+ args: A
243
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'findFirstOrThrow'>, ${errorTypeFor("findFirstOrThrow")}>
244
+
245
+ findMany: <A extends ${argsType("findMany")}>(
246
+ args?: A
247
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'findMany'>, ${errorTypeFor("findMany")}>
248
+
249
+ create: <A extends ${argsType("create")}>(
250
+ args: A
251
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'create'>, ${errorTypeFor("create")}>
252
+
253
+ createMany: <A extends ${argsType("createMany")}>(
254
+ args: A
255
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'createMany'>, ${errorTypeFor("createMany")}>${supportsManyAndReturn
256
+ ? `
257
+
258
+ createManyAndReturn: <A extends ${argsType("createManyAndReturn")}>(
259
+ args: A
260
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'createManyAndReturn'>, ${errorTypeFor("createManyAndReturn")}>`
261
+ : ""}
262
+
263
+ delete: <A extends ${argsType("delete")}>(
264
+ args: A
265
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'delete'>, ${errorTypeFor("delete")}>
266
+
267
+ update: <A extends ${argsType("update")}>(
268
+ args: A
269
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'update'>, ${errorTypeFor("update")}>
270
+
271
+ deleteMany: <A extends ${argsType("deleteMany")}>(
272
+ args?: A
273
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'deleteMany'>, ${errorTypeFor("deleteMany")}>
274
+
275
+ updateMany: <A extends ${argsType("updateMany")}>(
276
+ args: A
277
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'updateMany'>, ${errorTypeFor("updateMany")}>${supportsManyAndReturn
278
+ ? `
279
+
280
+ updateManyAndReturn: <A extends ${argsType("updateManyAndReturn")}>(
281
+ args: A
282
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'updateManyAndReturn'>, ${errorTypeFor("updateManyAndReturn")}>`
283
+ : ""}
284
+
285
+ upsert: <A extends ${argsType("upsert")}>(
286
+ args: A
287
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'upsert'>, ${errorTypeFor("upsert")}>
288
+
289
+ count: <A extends ${argsType("count")}>(
290
+ args?: A
291
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'count'>, ${errorTypeFor("count")}>
292
+
293
+ aggregate: <A extends ${argsType("aggregate")}>(
294
+ args: A
295
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'aggregate'>, ${errorTypeFor("aggregate")}>
296
+
297
+ groupBy: <A extends ${argsType("groupBy")}>(
298
+ args: A
299
+ ) => EffectType<PrismaNamespace.Result<${delegate}, A, 'groupBy'>, ${errorTypeFor("groupBy")}>
300
+ }`;
301
+ })
302
+ .join("\n\n");
303
+ return `/**
304
+ * Interface defining all Prisma operations with explicit types.
305
+ * This provides a type contract that the service implementation must satisfy.
306
+ */
307
+ export interface IPrismaService {
308
+ client: BasePrismaClient
309
+ // Transaction operations
310
+ $transaction: <R, E, A>(
311
+ effect: EffectType<A, E, R>
312
+ ) => EffectType<A, E | ${errorType}, Exclude<R, PrismaTransactionClientService>>
313
+
314
+ $transactionWith: <R, E, A>(
315
+ effect: EffectType<A, E, R>,
316
+ options: TransactionOptions
317
+ ) => EffectType<A, E | ${errorType}, R>
318
+
319
+ $isolatedTransaction: <R, E, A>(
320
+ effect: EffectType<A, E, R>
321
+ ) => EffectType<A, E | ${errorType}, R>
322
+
323
+ $isolatedTransactionWith: <R, E, A>(
324
+ effect: EffectType<A, E, R>,
325
+ options: TransactionOptions
326
+ ) => EffectType<A, E | ${errorType}, R>
327
+
328
+ // Raw SQL operations
329
+ $executeRaw: (
330
+ args: PrismaNamespace.Sql | [PrismaNamespace.Sql, ...any[]]
331
+ ) => EffectType<number, ${errorType}>
332
+
333
+ $executeRawUnsafe: (
334
+ query: string,
335
+ ...values: any[]
336
+ ) => EffectType<number, ${errorType}>
337
+
338
+ $queryRaw: <T = unknown>(
339
+ args: PrismaNamespace.Sql | [PrismaNamespace.Sql, ...any[]]
340
+ ) => EffectType<T, ${errorType}>
341
+
342
+ $queryRawUnsafe: <T = unknown>(
343
+ query: string,
344
+ ...values: any[]
345
+ ) => EffectType<T, ${errorType}>
346
+
347
+ // Model operations
348
+ ${modelInterfaces}
349
+ }
350
+ `;
351
+ }
352
+ function capitalize(str) {
353
+ return str.charAt(0).toUpperCase() + str.slice(1);
354
+ }
355
+ function generateModelOperations(models, customError, enableTelemetry, supportsManyAndReturn) {
356
+ return models
357
+ .map((model) => {
358
+ const modelName = model.name;
359
+ const modelNameCamel = toCamelCase(modelName);
360
+ // Use pre-computed type alias for delegate
361
+ const _delegate = `BasePrismaClient['${modelNameCamel}']`;
362
+ // Helper to get pre-computed Args type alias
363
+ const _argsType = (op) => `${modelName}${capitalize(op)}Args`;
364
+ // Cast Promise results to ensure consistent typing across Prisma versions
365
+ // This handles Prisma 7's GlobalOmitConfig
366
+ // @deprecated just cast as any now
367
+ const promiseCast = () => {
368
+ return " as any";
369
+ };
370
+ // Aggregate/groupBy use complex internal types that need stronger casts
371
+ // @deprecated just cast as any now
372
+ const strongPromiseCast = () => {
373
+ return " as any";
374
+ };
375
+ // With custom error: all operations use single error type and mapError
376
+ // Without custom error: use per-operation error types and mappers
377
+ const errorType = (opErrorType) => customError ? customError.className : opErrorType;
378
+ const mapperFn = (defaultMapper) => customError ? "mapError" : defaultMapper;
379
+ // Telemetry: wrap operations with Effect.fn or Effect.fnUntraced
380
+ const wrapTelemetry = (name, generatorFn) => enableTelemetry
381
+ ? `Effect.fn("${name}")(${generatorFn})`
382
+ : `Effect.fnUntraced(${generatorFn})`;
383
+ // Implementation without explicit types - the IPrismaService interface provides all type checking
384
+ return ` ${modelNameCamel}: {
385
+ findUnique: ${wrapTelemetry(`Prisma.${modelNameCamel}.findUnique`, `function* (args) {
386
+ const actualClient = yield* clientOrTx(client);
387
+ return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
388
+ try: () => actualClient.${modelNameCamel}.findUnique(args as any)${promiseCast()},
389
+ catch: (error) => ${mapperFn("mapFindError")}(error, "findUnique", "${modelName}")
390
+ });
391
+ }`)},
392
+
393
+ findUniqueOrThrow: ${wrapTelemetry(`Prisma.${modelNameCamel}.findUniqueOrThrow`, `function* (args) {
394
+ const actualClient = yield* clientOrTx(client);
395
+ return yield* Effect.tryPromise<any, ${errorType("PrismaFindOrThrowError")}>({
396
+ try: () => actualClient.${modelNameCamel}.findUniqueOrThrow(args as any)${promiseCast()},
397
+ catch: (error) => ${mapperFn("mapFindOrThrowError")}(error, "findUniqueOrThrow", "${modelName}")
398
+ });
399
+ }`)},
400
+
401
+ findFirst: ${wrapTelemetry(`Prisma.${modelNameCamel}.findFirst`, `function* (args) {
402
+ const actualClient = yield* clientOrTx(client);
403
+ return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
404
+ try: () => actualClient.${modelNameCamel}.findFirst(args as any)${promiseCast()},
405
+ catch: (error) => ${mapperFn("mapFindError")}(error, "findFirst", "${modelName}")
406
+ });
407
+ }`)},
408
+
409
+ findFirstOrThrow: ${wrapTelemetry(`Prisma.${modelNameCamel}.findFirstOrThrow`, `function* (args) {
410
+ const actualClient = yield* clientOrTx(client);
411
+ return yield* Effect.tryPromise<any, ${errorType("PrismaFindOrThrowError")}>({
412
+ try: () => actualClient.${modelNameCamel}.findFirstOrThrow(args as any)${promiseCast()},
413
+ catch: (error) => ${mapperFn("mapFindOrThrowError")}(error, "findFirstOrThrow", "${modelName}")
414
+ });
415
+ }`)},
416
+
417
+ findMany: ${wrapTelemetry(`Prisma.${modelNameCamel}.findMany`, `function* (args) {
418
+ const actualClient = yield* clientOrTx(client);
419
+ return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
420
+ try: () => actualClient.${modelNameCamel}.findMany(args as any)${promiseCast()},
421
+ catch: (error) => ${mapperFn("mapFindError")}(error, "findMany", "${modelName}")
422
+ });
423
+ }`)},
424
+
425
+ create: ${wrapTelemetry(`Prisma.${modelNameCamel}.create`, `function* (args) {
426
+ const actualClient = yield* clientOrTx(client);
427
+ return yield* Effect.tryPromise<any, ${errorType("PrismaCreateError")}>({
428
+ try: () => actualClient.${modelNameCamel}.create(args as any)${promiseCast()},
429
+ catch: (error) => ${mapperFn("mapCreateError")}(error, "create", "${modelName}")
430
+ });
431
+ }`)},
432
+
433
+ createMany: ${wrapTelemetry(`Prisma.${modelNameCamel}.createMany`, `function* (args) {
434
+ const actualClient = yield* clientOrTx(client);
435
+ return yield* Effect.tryPromise<any, ${errorType("PrismaCreateError")}>({
436
+ try: () => actualClient.${modelNameCamel}.createMany(args as any),
437
+ catch: (error) => ${mapperFn("mapCreateError")}(error, "createMany", "${modelName}")
438
+ });
439
+ }`)},${supportsManyAndReturn
440
+ ? `
441
+
442
+ createManyAndReturn: ${wrapTelemetry(`Prisma.${modelNameCamel}.createManyAndReturn`, `function* (args) {
443
+ const actualClient = yield* clientOrTx(client);
444
+ return yield* Effect.tryPromise<any, ${errorType("PrismaCreateError")}>({
445
+ try: () => actualClient.${modelNameCamel}.createManyAndReturn(args as any)${promiseCast()},
446
+ catch: (error) => ${mapperFn("mapCreateError")}(error, "createManyAndReturn", "${modelName}")
447
+ });
448
+ }`)},`
449
+ : ""}
450
+
451
+ delete: ${wrapTelemetry(`Prisma.${modelNameCamel}.delete`, `function* (args) {
452
+ const actualClient = yield* clientOrTx(client);
453
+ return yield* Effect.tryPromise<any, ${errorType("PrismaDeleteError")}>({
454
+ try: () => actualClient.${modelNameCamel}.delete(args as any)${promiseCast()},
455
+ catch: (error) => ${mapperFn("mapDeleteError")}(error, "delete", "${modelName}")
456
+ });
457
+ }`)},
458
+
459
+ update: ${wrapTelemetry(`Prisma.${modelNameCamel}.update`, `function* (args) {
460
+ const actualClient = yield* clientOrTx(client);
461
+ return yield* Effect.tryPromise<any, ${errorType("PrismaUpdateError")}>({
462
+ try: () => actualClient.${modelNameCamel}.update(args as any)${promiseCast()},
463
+ catch: (error) => ${mapperFn("mapUpdateError")}(error, "update", "${modelName}")
464
+ });
465
+ }`)},
466
+
467
+ deleteMany: ${wrapTelemetry(`Prisma.${modelNameCamel}.deleteMany`, `function* (args) {
468
+ const actualClient = yield* clientOrTx(client);
469
+ return yield* Effect.tryPromise<any, ${errorType("PrismaDeleteManyError")}>({
470
+ try: () => actualClient.${modelNameCamel}.deleteMany(args as any),
471
+ catch: (error) => ${mapperFn("mapDeleteManyError")}(error, "deleteMany", "${modelName}")
472
+ });
473
+ }`)},
474
+
475
+ updateMany: ${wrapTelemetry(`Prisma.${modelNameCamel}.updateMany`, `function* (args) {
476
+ const actualClient = yield* clientOrTx(client);
477
+ return yield* Effect.tryPromise<any, ${errorType("PrismaUpdateManyError")}>({
478
+ try: () => actualClient.${modelNameCamel}.updateMany(args as any),
479
+ catch: (error) => ${mapperFn("mapUpdateManyError")}(error, "updateMany", "${modelName}")
480
+ });
481
+ }`)},${supportsManyAndReturn
482
+ ? `
483
+
484
+ updateManyAndReturn: ${wrapTelemetry(`Prisma.${modelNameCamel}.updateManyAndReturn`, `function* (args) {
485
+ const actualClient = yield* clientOrTx(client);
486
+ return yield* Effect.tryPromise<any, ${errorType("PrismaUpdateManyError")}>({
487
+ try: () => actualClient.${modelNameCamel}.updateManyAndReturn(args as any)${promiseCast()},
488
+ catch: (error) => ${mapperFn("mapUpdateManyError")}(error, "updateManyAndReturn", "${modelName}")
489
+ });
490
+ }`)},`
491
+ : ""}
492
+
493
+ upsert: ${wrapTelemetry(`Prisma.${modelNameCamel}.upsert`, `function* (args) {
494
+ const actualClient = yield* clientOrTx(client);
495
+ return yield* Effect.tryPromise<any, ${errorType("PrismaCreateError")}>({
496
+ try: () => actualClient.${modelNameCamel}.upsert(args as any)${promiseCast()},
497
+ catch: (error) => ${mapperFn("mapCreateError")}(error, "upsert", "${modelName}")
498
+ });
499
+ }`)},
500
+
501
+ // Aggregation operations
502
+ count: ${wrapTelemetry(`Prisma.${modelNameCamel}.count`, `function* (args) {
503
+ const actualClient = yield* clientOrTx(client);
504
+ return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
505
+ try: () => actualClient.${modelNameCamel}.count(args as any)${promiseCast()},
506
+ catch: (error) => ${mapperFn("mapFindError")}(error, "count", "${modelName}")
507
+ });
508
+ }`)},
509
+
510
+ aggregate: ${wrapTelemetry(`Prisma.${modelNameCamel}.aggregate`, `function* (args) {
511
+ const actualClient = yield* clientOrTx(client);
512
+ return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
513
+ try: () => actualClient.${modelNameCamel}.aggregate(args as any)${strongPromiseCast()},
514
+ catch: (error) => ${mapperFn("mapFindError")}(error, "aggregate", "${modelName}")
515
+ });
516
+ }`)},
517
+
518
+ groupBy: ${wrapTelemetry(`Prisma.${modelNameCamel}.groupBy`, `function* (args) {
519
+ const actualClient = yield* clientOrTx(client);
520
+ return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
521
+ try: () => actualClient.${modelNameCamel}.groupBy(args as any)${strongPromiseCast()},
522
+ catch: (error) => ${mapperFn("mapFindError")}(error, "groupBy", "${modelName}")
523
+ });
524
+ }`)}
525
+ }`;
526
+ })
527
+ .join(",\n\n");
528
+ }
529
+ // Parse error import path like "./errors#PrismaError" into { path, className }
530
+ function parseErrorImportPath(errorImportPath) {
531
+ if (!errorImportPath)
532
+ return null;
533
+ const [path, className] = errorImportPath.split("#");
534
+ if (!path || !className) {
535
+ throw new Error(`Invalid errorImportPath format: "${errorImportPath}". Expected "path/to/module#ErrorClassName"`);
536
+ }
537
+ return { path, className };
538
+ }
539
+ async function generateUnifiedService(models, outputDir, clientImportPath, errorImportPath, enableTelemetry, supportsManyAndReturn) {
540
+ const customError = parseErrorImportPath(errorImportPath);
541
+ const rawSqlOperations = generateRawSqlOperations(customError, enableTelemetry);
542
+ const modelTypeAliases = generateModelTypeAliases(models, supportsManyAndReturn);
543
+ const prismaInterface = generatePrismaInterface(models, customError, supportsManyAndReturn);
544
+ const modelOperations = generateModelOperations(models, customError, enableTelemetry, supportsManyAndReturn);
545
+ // Generate different content based on whether custom error is configured
546
+ const serviceContent = customError
547
+ ? generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelTypeAliases, prismaInterface, modelOperations, enableTelemetry)
548
+ : generateDefaultErrorService(clientImportPath, rawSqlOperations, modelTypeAliases, prismaInterface, modelOperations, enableTelemetry);
549
+ const outputPath = node_path_1.default.join(outputDir, "index.ts");
550
+ await promises_1.default.writeFile(outputPath, serviceContent);
551
+ // Format the generated file with Biome
552
+ try {
553
+ (0, node_child_process_1.execSync)(`npx @biomejs/biome format --write "${outputPath}"`, {
554
+ stdio: "inherit",
555
+ cwd: process.cwd(),
556
+ });
557
+ }
558
+ catch (error) {
559
+ console.warn("Warning: Failed to format generated code with Biome:", error);
560
+ }
561
+ }
562
+ /**
563
+ * Generate service with custom user-provided error class.
564
+ * All operations use a single error type and a simple mapError function.
565
+ */
566
+ function generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelTypeAliases, prismaInterface, modelOperations, enableTelemetry) {
567
+ const _errorType = customError.className;
568
+ return `${header}
569
+ import { Effect, Exit, Layer, Option, Scope, ServiceMap } from "effect"
570
+ import type { Effect as EffectType, Error as EffectError, Services as EffectServices, Success as EffectSuccess } from "effect/Effect"
571
+ import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "${clientImportPath}"
572
+ import { ${customError.className}, mapPrismaError } from "${customError.path}"
573
+
574
+ // ============================================================================
575
+ // Type aliases for model operations (performance optimization)
576
+ // These are computed once and reused, reducing TypeScript's type-checking workload
577
+ // ============================================================================
578
+
579
+ ${modelTypeAliases}
580
+
581
+ // Symbol used to identify intentional rollbacks vs actual errors
582
+ const ROLLBACK = Symbol.for("prisma.effect.rollback")
583
+
584
+ // Type for the flat transaction client with commit/rollback control
585
+ type FlatTransactionClient = PrismaNamespace.TransactionClient & {
586
+ $commit: () => Promise<void>
587
+ $rollback: () => Promise<void>
588
+ }
589
+
590
+ /** Transaction options for $transaction and $isolatedTransaction */
591
+ type TransactionOptions = {
592
+ maxWait?: number
593
+ timeout?: number
594
+ isolationLevel?: PrismaNamespace.TransactionIsolationLevel
595
+ }
596
+
597
+ /**
598
+ * Context tag for the Prisma client instance.
599
+ *
600
+ * Use \`PrismaClient.layer()\` or \`PrismaClient.layerEffect()\` to create a layer.
601
+ *
602
+ * @example
603
+ * // Prisma 7 - adapter or accelerateUrl is required
604
+ * const layer = PrismaClient.layer({ adapter: myAdapter })
605
+ *
606
+ * // With transaction options (Prisma uses these as defaults for $transaction)
607
+ * const layer = PrismaClient.layer({
608
+ * adapter: myAdapter,
609
+ * transactionOptions: { isolationLevel: "Serializable", timeout: 10000 }
610
+ * })
611
+ */
612
+ export class PrismaClient extends ServiceMap.Service<PrismaClient, BasePrismaClient>()("PrismaClient") {
613
+ /**
614
+ * Create a PrismaClient layer with the given options.
615
+ * The client will be automatically disconnected when the layer scope ends.
616
+ *
617
+ * Pass options directly - the signature matches PrismaClient's constructor.
618
+ * Prisma 7: requires either \`adapter\`
619
+ *
620
+ * @example
621
+ * // Prisma 7 with adapter
622
+ * const layer = PrismaClient.layer({ adapter: myAdapter })
623
+ * // With transaction options
624
+ * const layer = PrismaClient.layer({
625
+ * adapter: myAdapter,
626
+ * transactionOptions: { isolationLevel: "Serializable" }
627
+ * })
628
+ */
629
+ static layer: (
630
+ ...args: ConstructorParameters<typeof BasePrismaClient>
631
+ ) => Layer.Layer<PrismaClient, never, never> = (...args) => Layer.effect(
632
+ PrismaClient,
633
+ Effect.gen(function* () {
634
+ const prisma: BasePrismaClient = new BasePrismaClient(...args)
635
+ yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
636
+ return prisma
637
+ })
638
+ )
639
+
640
+ /**
641
+ * Create a PrismaClient layer where options are computed via an Effect.
642
+ * Useful when you need to fetch configuration from environment, config service, or create adapters.
643
+ * The client will be automatically disconnected when the layer scope ends.
644
+ *
645
+ * @example
646
+ * // Get config from a service
647
+ * const layer = PrismaClient.layerEffect(
648
+ * Effect.gen(function* () {
649
+ * const config = yield* ConfigService
650
+ * return { adapter: createAdapter(config.databaseUrl) }
651
+ * })
652
+ * )
653
+ *
654
+ * // With transaction options
655
+ * const layer = PrismaClient.layerEffect(
656
+ * Effect.gen(function* () {
657
+ * return {
658
+ * adapter: myAdapter,
659
+ * transactionOptions: { isolationLevel: "Serializable" }
660
+ * }
661
+ * })
662
+ * )
663
+ */
664
+ static layerEffect: <R, E>(
665
+ optionsEffect: EffectType<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
666
+ ) => Layer.Layer<PrismaClient, E, Exclude<R, Scope.Scope>> = (optionsEffect) => Layer.effect(
667
+ PrismaClient,
668
+ Effect.gen(function* () {
669
+ const options: ConstructorParameters<typeof BasePrismaClient>[0] = yield* optionsEffect
670
+ const prisma: BasePrismaClient = new BasePrismaClient(options)
671
+ yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
672
+ return prisma
673
+ })
674
+ )
675
+ }
676
+
677
+ /**
678
+ * Context tag for the transaction client within a transaction scope.
679
+ * This service is only available inside \`$transaction\` calls.
680
+ * Use \`Effect.serviceOption(PrismaTransactionClientService)\` to check if you're in a transaction.
681
+ */
682
+ export class PrismaTransactionClientService extends ServiceMap.Service<PrismaTransactionClientService, PrismaNamespace.TransactionClient>()("PrismaTransactionClientService") {}
683
+
684
+ // Re-export the custom error type for convenience
685
+ export { ${customError.className} }
686
+
687
+ // Use the user-provided error mapper
688
+ const mapError = mapPrismaError
689
+
690
+ /**
691
+ * Helper to get the current client - either the transaction client if in a transaction,
692
+ * or the root client if not. Uses Effect.serviceOption to detect transaction context.
693
+ */
694
+ const clientOrTx = (client: BasePrismaClient) => Effect.map(
695
+ Effect.serviceOption(PrismaTransactionClientService),
696
+ Option.getOrElse(() => client),
697
+ );
698
+
699
+ /**
700
+ * Like Effect.acquireUseRelease, but allows the release function to fail.
701
+ * Release errors are surfaced in the error channel instead of becoming defects.
702
+ *
703
+ * Key properties:
704
+ * - The release function is always called, even if use fails
705
+ * - The release function is uninterruptible to ensure cleanup completes
706
+ * - Release errors are surfaced in the error channel, not as defects
707
+ */
708
+ const acquireUseReleaseWithErrors = <A, E, R, A2, E2, R2, X, E3, R3>(
709
+ acquire: EffectType<A, E, R>,
710
+ use: (a: A) => EffectType<A2, E2, R2>,
711
+ release: (a: A, exit: Exit.Exit<A2, E2>) => EffectType<X, E3, R3>
712
+ ): EffectType<A2, E | E2 | E3, R | R2 | R3> =>
713
+ Effect.uninterruptibleMask((restore) =>
714
+ Effect.flatMap(acquire, (a) =>
715
+ Effect.flatMap(
716
+ Effect.exit(restore(use(a))),
717
+ (exit) =>
718
+ Effect.flatMap(
719
+ // Make release uninterruptible to ensure cleanup always completes
720
+ Effect.exit(release(a, exit)),
721
+ (releaseExit) => {
722
+ if (Exit.isFailure(releaseExit)) {
723
+ // Release failed - surface the release error
724
+ return releaseExit as any;
725
+ }
726
+ // Release succeeded - return the original use result
727
+ return exit as any;
728
+ }
729
+ )
730
+ )
731
+ )
732
+ );
733
+
734
+ ${prismaInterface}
735
+
736
+ /**
737
+ * Internal helper to begin a callback-free interactive transaction.
738
+ * Returns a transaction client with $commit and $rollback methods.
739
+ * This allows transactions to run in the same fiber as the parent effect.
740
+ */
741
+ const $begin = (
742
+ client: BasePrismaClient,
743
+ options?: {
744
+ maxWait?: number
745
+ timeout?: number
746
+ isolationLevel?: PrismaNamespace.TransactionIsolationLevel
747
+ }
748
+ ): EffectType<FlatTransactionClient, ${customError.className}> =>
749
+ Effect.callback<FlatTransactionClient, ${customError.className}>((resume) => {
750
+ let setTxClient: (txClient: PrismaNamespace.TransactionClient) => void
751
+ let commit: () => void
752
+ let rollback: () => void
753
+
754
+ // Promise that resolves when we get the transaction client
755
+ const txClientPromise = new Promise<PrismaNamespace.TransactionClient>((res) => {
756
+ setTxClient = res
757
+ })
758
+
759
+ // Promise that controls when the transaction commits/rolls back
760
+ const txPromise = new Promise<void>((_res, _rej) => {
761
+ commit = () => _res(undefined)
762
+ rollback = () => _rej(ROLLBACK)
763
+ })
764
+
765
+ // Start the transaction - Prisma will wait on txPromise before committing
766
+ const tx = client.$transaction((txClient) => {
767
+ setTxClient(txClient)
768
+ return txPromise
769
+ }, options).catch((e) => {
770
+ // Swallow intentional rollbacks, rethrow actual errors
771
+ if (e === ROLLBACK) return
772
+ throw e
773
+ })
774
+
775
+ // Once we have the transaction client, wrap it with commit/rollback methods
776
+ txClientPromise.then((innerTx) => {
777
+ const proxy = new Proxy(innerTx, {
778
+ get(target, prop) {
779
+ if (prop === "$commit") return () => { commit(); return tx }
780
+ if (prop === "$rollback") return () => { rollback(); return tx }
781
+ return target[prop as keyof typeof target]
782
+ },
783
+ }) as FlatTransactionClient
784
+ resume(Effect.succeed(proxy))
785
+ }).catch((error) => {
786
+ resume(Effect.fail(mapError(error, "$transaction", "Prisma")))
787
+ })
788
+ })
789
+
790
+ /**
791
+ * The main Prisma service with all database operations.
792
+ * Provides type-safe, effectful access to your Prisma models.
793
+ *
794
+ * @example
795
+ * const program = Effect.gen(function* () {
796
+ * const prisma = yield* Prisma
797
+ * const user = yield* prisma.user.create({ data: { name: "Alice" } })
798
+ * return user
799
+ * })
800
+ *
801
+ * // Run with layer
802
+ * Effect.runPromise(program.pipe(Effect.provide(Prisma.layer({ datasourceUrl: "..." }))))
803
+ */
804
+ const makePrismaService = Effect.gen(function* () {
805
+ const client = yield* PrismaClient;
806
+
807
+ const prismaService: IPrismaService = {
808
+ client,
809
+ /**
810
+ * Execute an effect within a database transaction.
811
+ * All operations within the effect will be atomic - they either all succeed or all fail.
812
+ *
813
+ * This implementation uses a callback-free transaction pattern that keeps the effect
814
+ * running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
815
+ *
816
+ * Uses default transaction options from PrismaClient constructor.
817
+ * For custom options, use \`$transactionWith\`.
818
+ *
819
+ * @example
820
+ * const result = yield* prisma.$transaction(
821
+ * Effect.gen(function* () {
822
+ * const user = yield* prisma.user.create({ data: { name: "Alice" } })
823
+ * yield* prisma.post.create({ data: { title: "Hello", authorId: user.id } })
824
+ * return user
825
+ * })
826
+ * )
827
+ */
828
+ $transaction: ${enableTelemetry ? 'Effect.fn("Prisma.$transaction")' : "Effect.fnUntraced"}(function* (effect) {
829
+ const currentTx = yield* Effect.serviceOption(PrismaTransactionClientService);
830
+
831
+ // If already in a transaction, just run the effect
832
+ if (Option.isSome(currentTx)) {
833
+ // have to get this to pass the Exclude type
834
+ return yield* (effect as EffectType<
835
+ EffectSuccess<typeof effect>,
836
+ EffectError<typeof effect>,
837
+ Exclude<
838
+ EffectServices<typeof effect>,
839
+ PrismaTransactionClientService
840
+ >
841
+ >);
842
+ }
843
+
844
+ // Otherwise, start a new transaction
845
+ return yield* acquireUseReleaseWithErrors(
846
+ // Acquire: begin a new transaction with default options
847
+ $begin(client),
848
+
849
+ // Use: run the effect with the transaction client injected
850
+ (txClient) =>
851
+ effect.pipe(
852
+ Effect.provideService(PrismaTransactionClientService, txClient)
853
+ ),
854
+
855
+ // Release: commit on success, rollback on failure/interruption
856
+ (txClient, exit) =>
857
+ Exit.isSuccess(exit)
858
+ ? Effect.tryPromise({
859
+ try: () => txClient.$commit(),
860
+ catch: (error) => mapError(error, "$commit", "Prisma")
861
+ }).pipe(Effect.withSpan("txClient.$commit"))
862
+ : Effect.tryPromise({
863
+ try: () => txClient.$rollback(),
864
+ catch: (error) => mapError(error, "$rollback", "Prisma")
865
+ }).pipe(Effect.withSpan("txClient.$rollback"))
866
+ );
867
+ }),
868
+
869
+ /**
870
+ * Execute an effect within a database transaction with custom options.
871
+ * All operations within the effect will be atomic - they either all succeed or all fail.
872
+ *
873
+ * This implementation uses a callback-free transaction pattern that keeps the effect
874
+ * running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
875
+ *
876
+ * Options passed here override any defaults set in PrismaClient constructor.
877
+ *
878
+ * @example
879
+ * // Override default isolation level for this transaction
880
+ * const result = yield* prisma.$transactionWith(
881
+ * Effect.gen(function* () {
882
+ * const user = yield* prisma.user.create({ data: { name: "Alice" } })
883
+ * yield* prisma.post.create({ data: { title: "Hello", authorId: user.id } })
884
+ * return user
885
+ * }),
886
+ * { isolationLevel: "ReadCommitted", timeout: 10000 }
887
+ * )
888
+ */
889
+ $transactionWith: ${enableTelemetry ? 'Effect.fn("Prisma.$transactionWith")' : "Effect.fnUntraced"}(function* (effect, options) {
890
+ const currentTx = yield* Effect.serviceOption(PrismaTransactionClientService);
891
+
892
+ // If already in a transaction, just run the effect
893
+ if (Option.isSome(currentTx)) {
894
+ return yield* effect;
895
+ }
896
+
897
+ // Otherwise, start a new transaction
898
+ return yield* acquireUseReleaseWithErrors(
899
+ // Acquire: begin a new transaction
900
+ // Prisma merges per-call options with constructor defaults internally
901
+ $begin(client, options),
902
+
903
+ // Use: run the effect with the transaction client injected
904
+ (txClient) =>
905
+ effect.pipe(
906
+ Effect.provideService(PrismaTransactionClientService, txClient)
907
+ ),
908
+
909
+ // Release: commit on success, rollback on failure/interruption
910
+ (txClient, exit) =>
911
+ Exit.isSuccess(exit)
912
+ ? Effect.tryPromise({
913
+ try: () => txClient.$commit(),
914
+ catch: (error) => mapError(error, "$commit", "Prisma")
915
+ }).pipe(Effect.withSpan("txClient.$rollback"))
916
+ : Effect.tryPromise({
917
+ try: () => txClient.$rollback(),
918
+ catch: (error) => mapError(error, "$rollback", "Prisma")
919
+ }).pipe(Effect.withSpan("txClient.$rollback"))
920
+ );
921
+ }),
922
+
923
+ /**
924
+ * Execute an effect in a NEW transaction, even if already inside a transaction.
925
+ * Unlike \`$transaction\`, this always creates a fresh, independent transaction.
926
+ *
927
+ * Use this for operations that should NOT be rolled back with the parent:
928
+ * - Audit logging that must persist even if main operation fails
929
+ * - Saga pattern where each step has independent commit/rollback
930
+ * - Background job queuing that should commit immediately
931
+ *
932
+ * ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
933
+ * or vice versa. Use carefully to avoid data inconsistencies.
934
+ *
935
+ * Uses default transaction options from PrismaClient constructor.
936
+ * For custom options, use \`$isolatedTransactionWith\`.
937
+ *
938
+ * @example
939
+ * yield* prisma.$transaction(
940
+ * Effect.gen(function* () {
941
+ * // This audit log commits independently - survives parent rollback
942
+ * yield* prisma.$isolatedTransaction(
943
+ * prisma.auditLog.create({ data: { action: "attempt", userId } })
944
+ * )
945
+ * // Main operation - if this fails, audit log is still committed
946
+ * yield* prisma.user.delete({ where: { id: userId } })
947
+ * })
948
+ * )
949
+ */
950
+ $isolatedTransaction: ${enableTelemetry ? 'Effect.fn("Prisma.$isolatedTransaction")' : "Effect.fnUntraced"}(function* (effect) {
951
+ // Always create a fresh transaction
952
+ return yield* acquireUseReleaseWithErrors(
953
+ $begin(client),
954
+ (txClient) =>
955
+ effect.pipe(
956
+ Effect.provideService(PrismaTransactionClientService, txClient)
957
+ ),
958
+ (txClient, exit) =>
959
+ Exit.isSuccess(exit)
960
+ ? Effect.tryPromise({
961
+ try: () => txClient.$commit(),
962
+ catch: (error) => mapError(error, "$commit", "Prisma")
963
+ }).pipe(Effect.withSpan("txClient.$rollback"))
964
+ : Effect.tryPromise({
965
+ try: () => txClient.$rollback(),
966
+ catch: (error) => mapError(error, "$rollback", "Prisma")
967
+ }).pipe(Effect.withSpan("txClient.$rollback"))
968
+ );
969
+ }),
970
+
971
+ /**
972
+ * Execute an effect in a NEW transaction with custom options, even if already inside a transaction.
973
+ * Unlike \`$transaction\`, this always creates a fresh, independent transaction.
974
+ *
975
+ * Use this for operations that should NOT be rolled back with the parent:
976
+ * - Audit logging that must persist even if main operation fails
977
+ * - Saga pattern where each step has independent commit/rollback
978
+ * - Background job queuing that should commit immediately
979
+ *
980
+ * ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
981
+ * or vice versa. Use carefully to avoid data inconsistencies.
982
+ *
983
+ * Options passed here override any defaults set in PrismaClient constructor.
984
+ *
985
+ * @example
986
+ * yield* prisma.$transaction(
987
+ * Effect.gen(function* () {
988
+ * // This audit log commits independently with custom isolation level
989
+ * yield* prisma.$isolatedTransactionWith(
990
+ * prisma.auditLog.create({ data: { action: "attempt", userId } }),
991
+ * { isolationLevel: "Serializable" }
992
+ * )
993
+ * // Main operation - if this fails, audit log is still committed
994
+ * yield* prisma.user.delete({ where: { id: userId } })
995
+ * })
996
+ * )
997
+ */
998
+ $isolatedTransactionWith: ${enableTelemetry ? 'Effect.fn("Prisma.$isolatedTransactionWith")' : "Effect.fnUntraced"}(function* (effect, options) {
999
+ // Always create a fresh transaction
1000
+ return yield* acquireUseReleaseWithErrors(
1001
+ $begin(client, options),
1002
+ (txClient) =>
1003
+ effect.pipe(
1004
+ Effect.provideService(PrismaTransactionClientService, txClient)
1005
+ ),
1006
+ (txClient, exit) =>
1007
+ Exit.isSuccess(exit)
1008
+ ? Effect.tryPromise({
1009
+ try: () => txClient.$commit(),
1010
+ catch: (error) => mapError(error, "$commit", "Prisma")
1011
+ }).pipe(Effect.withSpan("txClient.$rollback"))
1012
+ : Effect.tryPromise({
1013
+ try: () => txClient.$rollback(),
1014
+ catch: (error) => mapError(error, "$rollback", "Prisma")
1015
+ }).pipe(Effect.withSpan("txClient.$rollback"))
1016
+ );
1017
+ }),
1018
+ ${rawSqlOperations}
1019
+
1020
+ ${modelOperations}
1021
+ };
1022
+
1023
+ return prismaService;
1024
+ });
1025
+
1026
+ export class Prisma extends ServiceMap.Service<Prisma, IPrismaService>()("Prisma") {
1027
+ /**
1028
+ * Effect that constructs the Prisma service.
1029
+ * Used internally by layer constructors.
1030
+ */
1031
+ static make: EffectType<IPrismaService, never, PrismaClient> = makePrismaService;
1032
+ static Default: Layer.Layer<Prisma, never, PrismaClient> = Layer.effect(Prisma, this.make);
1033
+
1034
+ /**
1035
+ * Create a complete Prisma layer with the given PrismaClient options.
1036
+ * This is the recommended way to create a Prisma layer - it bundles both
1037
+ * PrismaClient and Prisma service together.
1038
+ *
1039
+ * Pass options directly - the signature matches PrismaClient's constructor.
1040
+ * Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
1041
+ *
1042
+ * @example
1043
+ * // Prisma 7 with adapter
1044
+ * const MainLayer = Prisma.layer({ adapter: myAdapter })
1045
+ *
1046
+ * // With transaction options
1047
+ * const MainLayer = Prisma.layer({
1048
+ * adapter: myAdapter,
1049
+ * transactionOptions: { isolationLevel: "Serializable" }
1050
+ * })
1051
+ *
1052
+ * // Use it
1053
+ * Effect.runPromise(program.pipe(Effect.provide(MainLayer)))
1054
+ */
1055
+ static layer: (
1056
+ ...args: ConstructorParameters<typeof BasePrismaClient>
1057
+ ) => Layer.Layer<Prisma | PrismaClient, never, never> = (...args) => this.Default.pipe(
1058
+ Layer.provideMerge(PrismaClient.layer(...args))
1059
+ );
1060
+
1061
+ /**
1062
+ * Create a complete Prisma layer where PrismaClient options are computed via an Effect.
1063
+ * This is useful when you need to fetch configuration or create adapters using Effect.
1064
+ *
1065
+ * @example
1066
+ * // Get config from a service
1067
+ * const MainLayer = Prisma.layerEffect(
1068
+ * Effect.gen(function* () {
1069
+ * const config = yield* ConfigService
1070
+ * return { adapter: createAdapter(config.databaseUrl) }
1071
+ * })
1072
+ * )
1073
+ *
1074
+ * // With transaction options
1075
+ * const MainLayer = Prisma.layerEffect(
1076
+ * Effect.gen(function* () {
1077
+ * return {
1078
+ * adapter: myAdapter,
1079
+ * transactionOptions: { isolationLevel: "Serializable" }
1080
+ * }
1081
+ * })
1082
+ * )
1083
+ */
1084
+ static layerEffect: <R, E>(
1085
+ optionsEffect: EffectType<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
1086
+ ) => Layer.Layer<Prisma | PrismaClient, E, Exclude<R, Scope.Scope>> = (optionsEffect) => this.Default.pipe(
1087
+ Layer.provideMerge(PrismaClient.layerEffect(optionsEffect))
1088
+ );
1089
+ }
1090
+
1091
+ `;
1092
+ }
1093
+ /**
1094
+ * Generate service with default tagged error classes.
1095
+ * Operations have per-operation error types for fine-grained error handling.
1096
+ */
1097
+ function generateDefaultErrorService(clientImportPath, rawSqlOperations, modelTypeAliases, prismaInterface, modelOperations, enableTelemetry) {
1098
+ const _errorType = "PrismaError";
1099
+ return `${header}
1100
+ import { Data, Effect, Exit, Layer, Option, Scope, ServiceMap } from "effect"
1101
+ import type { Effect as EffectType, Error as EffectError, Services as EffectServices, Success as EffectSuccess } from "effect/Effect"
1102
+ import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "${clientImportPath}"
1103
+
1104
+ // Create local reference to error class for proper type narrowing
1105
+ const PrismaClientKnownRequestError = PrismaNamespace.PrismaClientKnownRequestError
1106
+
1107
+ // Type guard function to help TypeScript narrow the error type
1108
+ // This is needed for Prisma 7 which re-exports error classes from external packages
1109
+ function isPrismaClientKnownRequestError(error: unknown): error is PrismaNamespace.PrismaClientKnownRequestError {
1110
+ return error instanceof PrismaClientKnownRequestError
1111
+ }
1112
+
1113
+ // ============================================================================
1114
+ // Type aliases for model operations (performance optimization)
1115
+ // These are computed once and reused, reducing TypeScript's type-checking workload
1116
+ // ============================================================================
1117
+
1118
+ ${modelTypeAliases}
1119
+
1120
+ // Symbol used to identify intentional rollbacks vs actual errors
1121
+ const ROLLBACK = Symbol.for("prisma.effect.rollback")
1122
+
1123
+ // Type for the flat transaction client with commit/rollback control
1124
+ type FlatTransactionClient = PrismaNamespace.TransactionClient & {
1125
+ $commit: () => Promise<void>
1126
+ $rollback: () => Promise<void>
1127
+ }
1128
+
1129
+ /** Transaction options for $transaction and $isolatedTransaction */
1130
+ type TransactionOptions = {
1131
+ maxWait?: number
1132
+ timeout?: number
1133
+ isolationLevel?: PrismaNamespace.TransactionIsolationLevel
1134
+ }
1135
+
1136
+ /**
1137
+ * Context tag for the Prisma client instance.
1138
+ *
1139
+ * Use \`PrismaClient.layer()\` or \`PrismaClient.layerEffect()\` to create a layer.
1140
+ *
1141
+ * @example
1142
+ * // Prisma 7 - adapter or accelerateUrl is required
1143
+ * const layer = PrismaClient.layer({ adapter: myAdapter })
1144
+ *
1145
+ * // With transaction options (Prisma uses these as defaults for $transaction)
1146
+ * const layer = PrismaClient.layer({
1147
+ * adapter: myAdapter,
1148
+ * transactionOptions: { isolationLevel: "Serializable", timeout: 10000 }
1149
+ * })
1150
+ */
1151
+ export class PrismaClient extends ServiceMap.Service<PrismaClient, BasePrismaClient>()("PrismaClient") {
1152
+ /**
1153
+ * Create a PrismaClient layer with the given options.
1154
+ * The client will be automatically disconnected when the layer scope ends.
1155
+ *
1156
+ * Pass options directly - the signature matches PrismaClient's constructor.
1157
+ * Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
1158
+ *
1159
+ * @example
1160
+ * // Prisma 7 with adapter
1161
+ * const layer = PrismaClient.layer({ adapter: myAdapter })
1162
+ *
1163
+ * // With transaction options
1164
+ * const layer = PrismaClient.layer({
1165
+ * adapter: myAdapter,
1166
+ * transactionOptions: { isolationLevel: "Serializable" }
1167
+ * })
1168
+ */
1169
+ static layer: (
1170
+ ...args: ConstructorParameters<typeof BasePrismaClient>
1171
+ ) => Layer.Layer<PrismaClient, never, never> = (...args) => Layer.effect(
1172
+ PrismaClient,
1173
+ Effect.gen(function* () {
1174
+ const prisma: BasePrismaClient = new BasePrismaClient(...args)
1175
+ yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
1176
+ return prisma
1177
+ })
1178
+ )
1179
+
1180
+ /**
1181
+ * Create a PrismaClient layer where options are computed via an Effect.
1182
+ * Useful when you need to fetch configuration from environment, config service, or create adapters.
1183
+ * The client will be automatically disconnected when the layer scope ends.
1184
+ *
1185
+ * @example
1186
+ * // Get config from a service
1187
+ * const layer = PrismaClient.layerEffect(
1188
+ * Effect.gen(function* () {
1189
+ * const config = yield* ConfigService
1190
+ * return { adapter: createAdapter(config.databaseUrl) }
1191
+ * })
1192
+ * )
1193
+ *
1194
+ * // With transaction options
1195
+ * const layer = PrismaClient.layerEffect(
1196
+ * Effect.gen(function* () {
1197
+ * return {
1198
+ * adapter: myAdapter,
1199
+ * transactionOptions: { isolationLevel: "Serializable" }
1200
+ * }
1201
+ * })
1202
+ * )
1203
+ */
1204
+ static layerEffect: <R, E>(
1205
+ optionsEffect: EffectType<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
1206
+ ) => Layer.Layer<PrismaClient, E, Exclude<R, Scope.Scope>> = (optionsEffect) => Layer.effect(
1207
+ PrismaClient,
1208
+ Effect.gen(function* () {
1209
+ const options: ConstructorParameters<typeof BasePrismaClient>[0] = yield* optionsEffect
1210
+ const prisma: BasePrismaClient = new BasePrismaClient(options)
1211
+ yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
1212
+ return prisma
1213
+ })
1214
+ )
1215
+ }
1216
+
1217
+ /**
1218
+ * Context tag for the transaction client within a transaction scope.
1219
+ * This service is only available inside \`$transaction\` calls.
1220
+ * Use \`Effect.serviceOption(PrismaTransactionClientService)\` to check if you're in a transaction.
1221
+ */
1222
+ export class PrismaTransactionClientService extends ServiceMap.Service<PrismaTransactionClientService, PrismaNamespace.TransactionClient>()("PrismaTransactionClientService") {}
1223
+
1224
+ export class PrismaUniqueConstraintError extends Data.TaggedError("PrismaUniqueConstraintError")<{
1225
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1226
+ operation: string
1227
+ model: string
1228
+ }> {}
1229
+
1230
+ export class PrismaForeignKeyConstraintError extends Data.TaggedError("PrismaForeignKeyConstraintError")<{
1231
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1232
+ operation: string
1233
+ model: string
1234
+ }> {}
1235
+
1236
+ export class PrismaRecordNotFoundError extends Data.TaggedError("PrismaRecordNotFoundError")<{
1237
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1238
+ operation: string
1239
+ model: string
1240
+ }> {}
1241
+
1242
+ export class PrismaRelationViolationError extends Data.TaggedError("PrismaRelationViolationError")<{
1243
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1244
+ operation: string
1245
+ model: string
1246
+ }> {}
1247
+
1248
+ export class PrismaRelatedRecordNotFoundError extends Data.TaggedError("PrismaRelatedRecordNotFoundError")<{
1249
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1250
+ operation: string
1251
+ model: string
1252
+ }> {}
1253
+
1254
+ export class PrismaTransactionConflictError extends Data.TaggedError("PrismaTransactionConflictError")<{
1255
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1256
+ operation: string
1257
+ model: string
1258
+ }> {}
1259
+
1260
+ export class PrismaValueTooLongError extends Data.TaggedError("PrismaValueTooLongError")<{
1261
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1262
+ operation: string
1263
+ model: string
1264
+ }> {}
1265
+
1266
+ export class PrismaValueOutOfRangeError extends Data.TaggedError("PrismaValueOutOfRangeError")<{
1267
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1268
+ operation: string
1269
+ model: string
1270
+ }> {}
1271
+
1272
+ export class PrismaDbConstraintError extends Data.TaggedError("PrismaDbConstraintError")<{
1273
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1274
+ operation: string
1275
+ model: string
1276
+ }> {}
1277
+
1278
+ export class PrismaConnectionError extends Data.TaggedError("PrismaConnectionError")<{
1279
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1280
+ operation: string
1281
+ model: string
1282
+ }> {}
1283
+
1284
+ export class PrismaMissingRequiredValueError extends Data.TaggedError("PrismaMissingRequiredValueError")<{
1285
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1286
+ operation: string
1287
+ model: string
1288
+ }> {}
1289
+
1290
+ export class PrismaInputValidationError extends Data.TaggedError("PrismaInputValidationError")<{
1291
+ cause: PrismaNamespace.PrismaClientKnownRequestError
1292
+ operation: string
1293
+ model: string
1294
+ }> {}
1295
+
1296
+ export type PrismaCreateError =
1297
+ | PrismaValueTooLongError
1298
+ | PrismaUniqueConstraintError
1299
+ | PrismaForeignKeyConstraintError
1300
+ | PrismaDbConstraintError
1301
+ | PrismaInputValidationError
1302
+ | PrismaMissingRequiredValueError
1303
+ | PrismaRelatedRecordNotFoundError
1304
+ | PrismaValueOutOfRangeError
1305
+ | PrismaConnectionError
1306
+ | PrismaTransactionConflictError
1307
+
1308
+ export type PrismaUpdateError =
1309
+ | PrismaValueTooLongError
1310
+ | PrismaUniqueConstraintError
1311
+ | PrismaForeignKeyConstraintError
1312
+ | PrismaDbConstraintError
1313
+ | PrismaInputValidationError
1314
+ | PrismaMissingRequiredValueError
1315
+ | PrismaRelationViolationError
1316
+ | PrismaRelatedRecordNotFoundError
1317
+ | PrismaValueOutOfRangeError
1318
+ | PrismaConnectionError
1319
+ | PrismaRecordNotFoundError
1320
+ | PrismaTransactionConflictError
1321
+
1322
+ export type PrismaDeleteError =
1323
+ | PrismaForeignKeyConstraintError
1324
+ | PrismaRelationViolationError
1325
+ | PrismaConnectionError
1326
+ | PrismaRecordNotFoundError
1327
+ | PrismaTransactionConflictError
1328
+
1329
+ export type PrismaFindOrThrowError =
1330
+ | PrismaConnectionError
1331
+ | PrismaRecordNotFoundError
1332
+
1333
+ export type PrismaFindError =
1334
+ | PrismaConnectionError
1335
+
1336
+ export type PrismaDeleteManyError =
1337
+ | PrismaForeignKeyConstraintError
1338
+ | PrismaRelationViolationError
1339
+ | PrismaConnectionError
1340
+ | PrismaTransactionConflictError
1341
+
1342
+ export type PrismaUpdateManyError =
1343
+ | PrismaValueTooLongError
1344
+ | PrismaUniqueConstraintError
1345
+ | PrismaForeignKeyConstraintError
1346
+ | PrismaDbConstraintError
1347
+ | PrismaInputValidationError
1348
+ | PrismaMissingRequiredValueError
1349
+ | PrismaValueOutOfRangeError
1350
+ | PrismaConnectionError
1351
+ | PrismaTransactionConflictError
1352
+
1353
+ export type PrismaError =
1354
+ | PrismaValueTooLongError
1355
+ | PrismaUniqueConstraintError
1356
+ | PrismaForeignKeyConstraintError
1357
+ | PrismaDbConstraintError
1358
+ | PrismaInputValidationError
1359
+ | PrismaMissingRequiredValueError
1360
+ | PrismaRelationViolationError
1361
+ | PrismaRelatedRecordNotFoundError
1362
+ | PrismaValueOutOfRangeError
1363
+ | PrismaConnectionError
1364
+ | PrismaRecordNotFoundError
1365
+ | PrismaTransactionConflictError
1366
+
1367
+ // Generic mapper for raw operations and fallback
1368
+ const mapError = (error: unknown, operation: string, model: string): PrismaError => {
1369
+ if (isPrismaClientKnownRequestError(error)) {
1370
+ const knownError = error;
1371
+ switch (knownError.code) {
1372
+ case "P2000":
1373
+ return new PrismaValueTooLongError({ cause: error, operation, model });
1374
+ case "P2002":
1375
+ return new PrismaUniqueConstraintError({ cause: error, operation, model });
1376
+ case "P2003":
1377
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1378
+ case "P2004":
1379
+ return new PrismaDbConstraintError({ cause: error, operation, model });
1380
+ case "P2005":
1381
+ case "P2006":
1382
+ case "P2019":
1383
+ return new PrismaInputValidationError({ cause: error, operation, model });
1384
+ case "P2011":
1385
+ case "P2012":
1386
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model });
1387
+ case "P2014":
1388
+ return new PrismaRelationViolationError({ cause: error, operation, model });
1389
+ case "P2015":
1390
+ case "P2018":
1391
+ return new PrismaRelatedRecordNotFoundError({ cause: error, operation, model });
1392
+ case "P2020":
1393
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model });
1394
+ case "P2024":
1395
+ return new PrismaConnectionError({ cause: error, operation, model });
1396
+ case "P2025":
1397
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
1398
+ case "P2034":
1399
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1400
+ }
1401
+ }
1402
+ // Unknown errors are not handled and will be treated as defects
1403
+ throw error;
1404
+ }
1405
+
1406
+ // Specific mappers to narrow error types per operation
1407
+
1408
+ // Create, Upsert
1409
+ const mapCreateError = (error: unknown, operation: string, model: string): PrismaCreateError => {
1410
+ if (isPrismaClientKnownRequestError(error)) {
1411
+ const knownError = error;
1412
+ switch (knownError.code) {
1413
+ case "P2000":
1414
+ return new PrismaValueTooLongError({ cause: error, operation, model });
1415
+ case "P2002":
1416
+ return new PrismaUniqueConstraintError({ cause: error, operation, model });
1417
+ case "P2003":
1418
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1419
+ case "P2004":
1420
+ return new PrismaDbConstraintError({ cause: error, operation, model });
1421
+ case "P2005":
1422
+ case "P2006":
1423
+ case "P2019":
1424
+ return new PrismaInputValidationError({ cause: error, operation, model });
1425
+ case "P2011":
1426
+ case "P2012":
1427
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model });
1428
+ case "P2015":
1429
+ case "P2018":
1430
+ return new PrismaRelatedRecordNotFoundError({ cause: error, operation, model });
1431
+ case "P2020":
1432
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model });
1433
+ case "P2024":
1434
+ return new PrismaConnectionError({ cause: error, operation, model });
1435
+ case "P2034":
1436
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1437
+ }
1438
+ }
1439
+ throw error;
1440
+ }
1441
+
1442
+ // Update
1443
+ const mapUpdateError = (error: unknown, operation: string, model: string): PrismaUpdateError => {
1444
+ if (isPrismaClientKnownRequestError(error)) {
1445
+ const knownError = error;
1446
+ switch (knownError.code) {
1447
+ case "P2000":
1448
+ return new PrismaValueTooLongError({ cause: error, operation, model });
1449
+ case "P2002":
1450
+ return new PrismaUniqueConstraintError({ cause: error, operation, model });
1451
+ case "P2003":
1452
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1453
+ case "P2004":
1454
+ return new PrismaDbConstraintError({ cause: error, operation, model });
1455
+ case "P2005":
1456
+ case "P2006":
1457
+ case "P2019":
1458
+ return new PrismaInputValidationError({ cause: error, operation, model });
1459
+ case "P2011":
1460
+ case "P2012":
1461
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model });
1462
+ case "P2014":
1463
+ return new PrismaRelationViolationError({ cause: error, operation, model });
1464
+ case "P2015":
1465
+ case "P2018":
1466
+ return new PrismaRelatedRecordNotFoundError({ cause: error, operation, model });
1467
+ case "P2020":
1468
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model });
1469
+ case "P2024":
1470
+ return new PrismaConnectionError({ cause: error, operation, model });
1471
+ case "P2025":
1472
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
1473
+ case "P2034":
1474
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1475
+ }
1476
+ }
1477
+ throw error;
1478
+ }
1479
+
1480
+ // Delete
1481
+ const mapDeleteError = (error: unknown, operation: string, model: string): PrismaDeleteError => {
1482
+ if (isPrismaClientKnownRequestError(error)) {
1483
+ const knownError = error;
1484
+ switch (knownError.code) {
1485
+ case "P2003":
1486
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1487
+ case "P2014":
1488
+ return new PrismaRelationViolationError({ cause: error, operation, model });
1489
+ case "P2024":
1490
+ return new PrismaConnectionError({ cause: error, operation, model });
1491
+ case "P2025":
1492
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
1493
+ case "P2034":
1494
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1495
+ }
1496
+ }
1497
+ throw error;
1498
+ }
1499
+
1500
+ // FindOrThrow
1501
+ const mapFindOrThrowError = (error: unknown, operation: string, model: string): PrismaFindOrThrowError => {
1502
+ if (isPrismaClientKnownRequestError(error)) {
1503
+ const knownError = error;
1504
+ switch (knownError.code) {
1505
+ case "P2024":
1506
+ return new PrismaConnectionError({ cause: error, operation, model });
1507
+ case "P2025":
1508
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
1509
+ }
1510
+ }
1511
+ throw error;
1512
+ }
1513
+
1514
+ // Find
1515
+ const mapFindError = (error: unknown, operation: string, model: string): PrismaFindError => {
1516
+ if (isPrismaClientKnownRequestError(error)) {
1517
+ const knownError = error;
1518
+ switch (knownError.code) {
1519
+ case "P2024":
1520
+ return new PrismaConnectionError({ cause: error, operation, model });
1521
+ }
1522
+ }
1523
+ throw error;
1524
+ }
1525
+
1526
+ // DeleteMany
1527
+ const mapDeleteManyError = (error: unknown, operation: string, model: string): PrismaDeleteManyError => {
1528
+ if (isPrismaClientKnownRequestError(error)) {
1529
+ const knownError = error;
1530
+ switch (knownError.code) {
1531
+ case "P2003":
1532
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1533
+ case "P2014":
1534
+ return new PrismaRelationViolationError({ cause: error, operation, model });
1535
+ case "P2024":
1536
+ return new PrismaConnectionError({ cause: error, operation, model });
1537
+ case "P2034":
1538
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1539
+ }
1540
+ }
1541
+ throw error;
1542
+ }
1543
+
1544
+ // UpdateMany
1545
+ const mapUpdateManyError = (error: unknown, operation: string, model: string): PrismaUpdateManyError => {
1546
+ if (isPrismaClientKnownRequestError(error)) {
1547
+ const knownError = error;
1548
+ switch (knownError.code) {
1549
+ case "P2000":
1550
+ return new PrismaValueTooLongError({ cause: error, operation, model });
1551
+ case "P2002":
1552
+ return new PrismaUniqueConstraintError({ cause: error, operation, model });
1553
+ case "P2003":
1554
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1555
+ case "P2004":
1556
+ return new PrismaDbConstraintError({ cause: error, operation, model });
1557
+ case "P2005":
1558
+ case "P2006":
1559
+ case "P2019":
1560
+ return new PrismaInputValidationError({ cause: error, operation, model });
1561
+ case "P2011":
1562
+ case "P2012":
1563
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model });
1564
+ case "P2020":
1565
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model });
1566
+ case "P2024":
1567
+ return new PrismaConnectionError({ cause: error, operation, model });
1568
+ case "P2034":
1569
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1570
+ }
1571
+ }
1572
+ throw error;
1573
+ }
1574
+
1575
+ /**
1576
+ * Helper to get the current client - either the transaction client if in a transaction,
1577
+ * or the root client if not. Uses Effect.serviceOption to detect transaction context.
1578
+ */
1579
+ const clientOrTx = (client: BasePrismaClient) => Effect.map(
1580
+ Effect.serviceOption(PrismaTransactionClientService),
1581
+ Option.getOrElse(() => client),
1582
+ );
1583
+
1584
+ /**
1585
+ * Like Effect.acquireUseRelease, but allows the release function to fail.
1586
+ * Release errors are surfaced in the error channel instead of becoming defects.
1587
+ *
1588
+ * Key properties:
1589
+ * - The release function is always called, even if use fails
1590
+ * - The release function is uninterruptible to ensure cleanup completes
1591
+ * - Release errors are surfaced in the error channel, not as defects
1592
+ */
1593
+ const acquireUseReleaseWithErrors = <A, E, R, A2, E2, R2, X, E3, R3>(
1594
+ acquire: EffectType<A, E, R>,
1595
+ use: (a: A) => EffectType<A2, E2, R2>,
1596
+ release: (a: A, exit: Exit.Exit<A2, E2>) => EffectType<X, E3, R3>
1597
+ ): EffectType<A2, E | E2 | E3, R | R2 | R3> =>
1598
+ Effect.uninterruptibleMask((restore) =>
1599
+ Effect.flatMap(acquire, (a) =>
1600
+ Effect.flatMap(
1601
+ Effect.exit(restore(use(a))),
1602
+ (exit) =>
1603
+ Effect.flatMap(
1604
+ // Make release uninterruptible to ensure cleanup always completes
1605
+ Effect.exit(Effect.uninterruptible(release(a, exit))),
1606
+ (releaseExit) => {
1607
+ if (Exit.isFailure(releaseExit)) {
1608
+ // Release failed - surface the release error
1609
+ return releaseExit as any;
1610
+ }
1611
+ // Release succeeded - return the original use result
1612
+ return exit as any;
1613
+ }
1614
+ )
1615
+ )
1616
+ )
1617
+ );
1618
+
1619
+ ${prismaInterface}
1620
+
1621
+ /**
1622
+ * Internal helper to begin a callback-free interactive transaction.
1623
+ * Returns a transaction client with $commit and $rollback methods.
1624
+ * This allows transactions to run in the same fiber as the parent effect.
1625
+ */
1626
+ const $begin = (
1627
+ client: BasePrismaClient,
1628
+ options?: {
1629
+ maxWait?: number
1630
+ timeout?: number
1631
+ isolationLevel?: PrismaNamespace.TransactionIsolationLevel
1632
+ }
1633
+ ): EffectType<FlatTransactionClient, PrismaError> =>
1634
+ Effect.callback<FlatTransactionClient, PrismaError>((resume) => {
1635
+ let setTxClient: (txClient: PrismaNamespace.TransactionClient) => void
1636
+ let commit: () => void
1637
+ let rollback: () => void
1638
+
1639
+ // Promise that resolves when we get the transaction client
1640
+ const txClientPromise = new Promise<PrismaNamespace.TransactionClient>((res) => {
1641
+ setTxClient = res
1642
+ })
1643
+
1644
+ // Promise that controls when the transaction commits/rolls back
1645
+ const txPromise = new Promise<void>((_res, _rej) => {
1646
+ commit = () => _res(undefined)
1647
+ rollback = () => _rej(ROLLBACK)
1648
+ })
1649
+
1650
+ // Start the transaction - Prisma will wait on txPromise before committing
1651
+ const tx = client.$transaction((txClient) => {
1652
+ setTxClient(txClient)
1653
+ return txPromise
1654
+ }, options).catch((e) => {
1655
+ // Swallow intentional rollbacks, rethrow actual errors
1656
+ if (e === ROLLBACK) return
1657
+ throw e
1658
+ })
1659
+
1660
+ // Once we have the transaction client, wrap it with commit/rollback methods
1661
+ txClientPromise.then((innerTx) => {
1662
+ const proxy = new Proxy(innerTx, {
1663
+ get(target, prop) {
1664
+ if (prop === "$commit") return () => { commit(); return tx }
1665
+ if (prop === "$rollback") return () => { rollback(); return tx }
1666
+ return target[prop as keyof typeof target]
1667
+ },
1668
+ }) as FlatTransactionClient
1669
+ resume(Effect.succeed(proxy))
1670
+ }).catch((error) => {
1671
+ resume(Effect.fail(mapError(error, "$transaction", "Prisma")))
1672
+ })
1673
+ })
1674
+
1675
+ /**
1676
+ * The main Prisma service with all database operations.
1677
+ * Provides type-safe, effectful access to your Prisma models.
1678
+ *
1679
+ * @example
1680
+ * const program = Effect.gen(function* () {
1681
+ * const prisma = yield* Prisma
1682
+ * const user = yield* prisma.user.create({ data: { name: "Alice" } })
1683
+ * return user
1684
+ * })
1685
+ *
1686
+ * // Run with layer
1687
+ * Effect.runPromise(program.pipe(Effect.provide(Prisma.layer({ datasourceUrl: "..." }))))
1688
+ */
1689
+ const makePrismaService = Effect.gen(function* () {
1690
+ const client = yield* PrismaClient;
1691
+
1692
+ const prismaService: IPrismaService = {
1693
+ client,
1694
+ /**
1695
+ * Execute an effect within a database transaction.
1696
+ * All operations within the effect will be atomic - they either all succeed or all fail.
1697
+ *
1698
+ * This implementation uses a callback-free transaction pattern that keeps the effect
1699
+ * running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
1700
+ *
1701
+ * Uses default transaction options from PrismaClient constructor.
1702
+ * For custom options, use \`$transactionWith\`.
1703
+ *
1704
+ * @example
1705
+ * const result = yield* prisma.$transaction(
1706
+ * Effect.gen(function* () {
1707
+ * const user = yield* prisma.user.create({ data: { name: "Alice" } })
1708
+ * yield* prisma.post.create({ data: { title: "Hello", authorId: user.id } })
1709
+ * return user
1710
+ * })
1711
+ * )
1712
+ */
1713
+ $transaction: ${enableTelemetry ? 'Effect.fn("Prisma.$transaction")' : "Effect.fnUntraced"}(function* (effect) {
1714
+ const currentTx = yield* Effect.serviceOption(PrismaTransactionClientService);
1715
+
1716
+ // If already in a transaction, just run the effect
1717
+ if (Option.isSome(currentTx)) {
1718
+ return yield* (effect as EffectType<
1719
+ EffectSuccess<typeof effect>,
1720
+ EffectError<typeof effect>,
1721
+ Exclude<
1722
+ EffectServices<typeof effect>,
1723
+ PrismaTransactionClientService
1724
+ >
1725
+ >);
1726
+ }
1727
+
1728
+ // Otherwise, start a new transaction
1729
+ return yield* acquireUseReleaseWithErrors(
1730
+ // Acquire: begin a new transaction with default options
1731
+ $begin(client),
1732
+
1733
+ // Use: run the effect with the transaction client injected
1734
+ (txClient) =>
1735
+ effect.pipe(
1736
+ Effect.provideService(PrismaTransactionClientService, txClient)
1737
+ ),
1738
+
1739
+ // Release: commit on success, rollback on failure/interruption
1740
+ (txClient, exit) =>
1741
+ Exit.isSuccess(exit)
1742
+ ? Effect.tryPromise({
1743
+ try: () => txClient.$commit(),
1744
+ catch: (error) => mapError(error, "$commit", "Prisma")
1745
+ }).pipe(Effect.withSpan("txClient.$rollback"))
1746
+ : Effect.tryPromise({
1747
+ try: () => txClient.$rollback(),
1748
+ catch: (error) => mapError(error, "$rollback", "Prisma")
1749
+ }).pipe(Effect.withSpan("txClient.$rollback"))
1750
+ );
1751
+ }),
1752
+
1753
+ /**
1754
+ * Execute an effect within a database transaction with custom options.
1755
+ * All operations within the effect will be atomic - they either all succeed or all fail.
1756
+ *
1757
+ * This implementation uses a callback-free transaction pattern that keeps the effect
1758
+ * running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
1759
+ *
1760
+ * Options passed here override any defaults set in PrismaClient constructor.
1761
+ *
1762
+ * @example
1763
+ * // Override default isolation level for this transaction
1764
+ * const result = yield* prisma.$transactionWith(
1765
+ * Effect.gen(function* () {
1766
+ * const user = yield* prisma.user.create({ data: { name: "Alice" } })
1767
+ * yield* prisma.post.create({ data: { title: "Hello", authorId: user.id } })
1768
+ * return user
1769
+ * }),
1770
+ * { isolationLevel: "ReadCommitted", timeout: 10000 }
1771
+ * )
1772
+ */
1773
+ $transactionWith: ${enableTelemetry ? 'Effect.fn("Prisma.$transactionWith")' : "Effect.fnUntraced"}(function* (effect, options) {
1774
+ const currentTx = yield* Effect.serviceOption(PrismaTransactionClientService);
1775
+
1776
+ // If already in a transaction, just run the effect
1777
+ if (Option.isSome(currentTx)) {
1778
+ return yield* effect;
1779
+ }
1780
+
1781
+ // Otherwise, start a new transaction
1782
+ return yield* acquireUseReleaseWithErrors(
1783
+ // Acquire: begin a new transaction
1784
+ // Prisma merges per-call options with constructor defaults internally
1785
+ $begin(client, options),
1786
+
1787
+ // Use: run the effect with the transaction client injected
1788
+ (txClient) =>
1789
+ effect.pipe(
1790
+ Effect.provideService(PrismaTransactionClientService, txClient)
1791
+ ),
1792
+
1793
+ // Release: commit on success, rollback on failure/interruption
1794
+ (txClient, exit) =>
1795
+ Exit.isSuccess(exit)
1796
+ ? Effect.tryPromise({
1797
+ try: () => txClient.$commit(),
1798
+ catch: (error) => mapError(error, "$commit", "Prisma")
1799
+ }).pipe(Effect.withSpan("txClient.$rollback"))
1800
+ : Effect.tryPromise({
1801
+ try: () => txClient.$rollback(),
1802
+ catch: (error) => mapError(error, "$rollback", "Prisma")
1803
+ }).pipe(Effect.withSpan("txClient.$rollback"))
1804
+ );
1805
+ }),
1806
+
1807
+ /**
1808
+ * Execute an effect in a NEW transaction, even if already inside a transaction.
1809
+ * Unlike \`$transaction\`, this always creates a fresh, independent transaction.
1810
+ *
1811
+ * Use this for operations that should NOT be rolled back with the parent:
1812
+ * - Audit logging that must persist even if main operation fails
1813
+ * - Saga pattern where each step has independent commit/rollback
1814
+ * - Background job queuing that should commit immediately
1815
+ *
1816
+ * ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
1817
+ * or vice versa. Use carefully to avoid data inconsistencies.
1818
+ *
1819
+ * Uses default transaction options from PrismaClient constructor.
1820
+ * For custom options, use \`$isolatedTransactionWith\`.
1821
+ *
1822
+ * @example
1823
+ * yield* prisma.$transaction(
1824
+ * Effect.gen(function* () {
1825
+ * // This audit log commits independently - survives parent rollback
1826
+ * yield* prisma.$isolatedTransaction(
1827
+ * prisma.auditLog.create({ data: { action: "attempt", userId } })
1828
+ * )
1829
+ * // Main operation - if this fails, audit log is still committed
1830
+ * yield* prisma.user.delete({ where: { id: userId } })
1831
+ * })
1832
+ * )
1833
+ */
1834
+ $isolatedTransaction: ${enableTelemetry ? 'Effect.fn("Prisma.$isolatedTransaction")' : "Effect.fnUntraced"}(function* (effect) {
1835
+ // Always create a fresh transaction
1836
+ return yield* acquireUseReleaseWithErrors(
1837
+ $begin(client),
1838
+ (txClient) =>
1839
+ effect.pipe(
1840
+ Effect.provideService(PrismaTransactionClientService, txClient)
1841
+ ),
1842
+ (txClient, exit) =>
1843
+ Exit.isSuccess(exit)
1844
+ ? Effect.tryPromise({
1845
+ try: () => txClient.$commit(),
1846
+ catch: (error) => mapError(error, "$commit", "Prisma")
1847
+ }).pipe(Effect.withSpan("txClient.$rollback"))
1848
+ : Effect.tryPromise({
1849
+ try: () => txClient.$rollback(),
1850
+ catch: (error) => mapError(error, "$rollback", "Prisma")
1851
+ }).pipe(Effect.withSpan("txClient.$rollback"))
1852
+ );
1853
+ }),
1854
+
1855
+ /**
1856
+ * Execute an effect in a NEW transaction with custom options, even if already inside a transaction.
1857
+ * Unlike \`$transaction\`, this always creates a fresh, independent transaction.
1858
+ *
1859
+ * Use this for operations that should NOT be rolled back with the parent:
1860
+ * - Audit logging that must persist even if main operation fails
1861
+ * - Saga pattern where each step has independent commit/rollback
1862
+ * - Background job queuing that should commit immediately
1863
+ *
1864
+ * ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
1865
+ * or vice versa. Use carefully to avoid data inconsistencies.
1866
+ *
1867
+ * Options passed here override any defaults set in PrismaClient constructor.
1868
+ *
1869
+ * @example
1870
+ * yield* prisma.$transaction(
1871
+ * Effect.gen(function* () {
1872
+ * // This audit log commits independently with custom isolation level
1873
+ * yield* prisma.$isolatedTransactionWith(
1874
+ * prisma.auditLog.create({ data: { action: "attempt", userId } }),
1875
+ * { isolationLevel: "Serializable" }
1876
+ * )
1877
+ * // Main operation - if this fails, audit log is still committed
1878
+ * yield* prisma.user.delete({ where: { id: userId } })
1879
+ * })
1880
+ * )
1881
+ */
1882
+ $isolatedTransactionWith: ${enableTelemetry ? 'Effect.fn("Prisma.$isolatedTransactionWith")' : "Effect.fnUntraced"}(function* (effect, options) {
1883
+ // Always create a fresh transaction
1884
+ return yield* acquireUseReleaseWithErrors(
1885
+ $begin(client, options),
1886
+ (txClient) =>
1887
+ effect.pipe(
1888
+ Effect.provideService(PrismaTransactionClientService, txClient)
1889
+ ),
1890
+ (txClient, exit) =>
1891
+ Exit.isSuccess(exit)
1892
+ ? Effect.tryPromise({
1893
+ try: () => txClient.$commit(),
1894
+ catch: (error) => mapError(error, "$commit", "Prisma")
1895
+ }).pipe(Effect.withSpan("txClient.$rollback"))
1896
+ : Effect.tryPromise({
1897
+ try: () => txClient.$rollback(),
1898
+ catch: (error) => mapError(error, "$rollback", "Prisma")
1899
+ }).pipe(Effect.withSpan("txClient.$rollback"))
1900
+ );
1901
+ }),
1902
+ ${rawSqlOperations}
1903
+
1904
+ ${modelOperations}
1905
+ };
1906
+
1907
+ return prismaService;
1908
+ });
1909
+
1910
+ export class Prisma extends ServiceMap.Service<Prisma, IPrismaService>()("Prisma") {
1911
+ /**
1912
+ * Effect that constructs the Prisma service.
1913
+ * Used internally by layer constructors.
1914
+ */
1915
+ static make: EffectType<IPrismaService, never, PrismaClient> = makePrismaService;
1916
+ static Default: Layer.Layer<Prisma, never, PrismaClient> = Layer.effect(Prisma, this.make);
1917
+
1918
+ /**
1919
+ * Create a complete Prisma layer with the given PrismaClient options.
1920
+ * This is the recommended way to create a Prisma layer - it bundles both
1921
+ * PrismaClient and Prisma service together.
1922
+ *
1923
+ * Pass options directly - the signature matches PrismaClient's constructor.
1924
+ * Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
1925
+ *
1926
+ * @example
1927
+ * // Prisma 7 with adapter
1928
+ * const MainLayer = Prisma.layer({ adapter: myAdapter })
1929
+ *
1930
+ * // With transaction options
1931
+ * const MainLayer = Prisma.layer({
1932
+ * adapter: myAdapter,
1933
+ * transactionOptions: { isolationLevel: "Serializable" }
1934
+ * })
1935
+ *
1936
+ * // Use it
1937
+ * Effect.runPromise(program.pipe(Effect.provide(MainLayer)))
1938
+ */
1939
+ static layer: (
1940
+ ...args: ConstructorParameters<typeof BasePrismaClient>
1941
+ ) => Layer.Layer<Prisma | PrismaClient, never, never> = (...args) => this.Default.pipe(
1942
+ Layer.provideMerge(PrismaClient.layer(...args))
1943
+ );
1944
+
1945
+ /**
1946
+ * Create a complete Prisma layer where PrismaClient options are computed via an Effect.
1947
+ * This is useful when you need to fetch configuration or create adapters using Effect.
1948
+ *
1949
+ * @example
1950
+ * // Get config from a service
1951
+ * const MainLayer = Prisma.layerEffect(
1952
+ * Effect.gen(function* () {
1953
+ * const config = yield* ConfigService
1954
+ * return { adapter: createAdapter(config.databaseUrl) }
1955
+ * })
1956
+ * )
1957
+ *
1958
+ * // With transaction options
1959
+ * const MainLayer = Prisma.layerEffect(
1960
+ * Effect.gen(function* () {
1961
+ * return {
1962
+ * adapter: myAdapter,
1963
+ * transactionOptions: { isolationLevel: "Serializable" }
1964
+ * }
1965
+ * })
1966
+ * )
1967
+ */
1968
+ static layerEffect: <R, E>(
1969
+ optionsEffect: EffectType<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
1970
+ ) => Layer.Layer<Prisma | PrismaClient, E, Exclude<R, Scope.Scope>> = (optionsEffect) => this.Default.pipe(
1971
+ Layer.provideMerge(PrismaClient.layerEffect(optionsEffect))
1972
+ );
1973
+ }
1974
+
1975
+ `;
1976
+ }