prisma-generator-effect 0.0.4

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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +607 -0
  3. package/dist/index.js +1493 -0
  4. package/package.json +42 -0
package/dist/index.js ADDED
@@ -0,0 +1,1493 @@
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 generator_helper_1 = require("@prisma/generator-helper");
8
+ const promises_1 = __importDefault(require("node:fs/promises"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const header = `// This file was generated by prisma-generator-effect, do not edit manually.\n`;
11
+ // Utility function to convert PascalCase to camelCase
12
+ function toCamelCase(str) {
13
+ return str.charAt(0).toLowerCase() + str.slice(1);
14
+ }
15
+ (0, generator_helper_1.generatorHandler)({
16
+ onManifest() {
17
+ return {
18
+ defaultOutput: "../generated/effect",
19
+ prettyName: "Prisma Effect Generator",
20
+ // No engines required - we only read the DMMF schema
21
+ requiresEngines: [],
22
+ };
23
+ },
24
+ async onGenerate(options) {
25
+ const models = options.dmmf.datamodel.models;
26
+ const outputDir = options.generator.output?.value;
27
+ const schemaDir = node_path_1.default.dirname(options.schemaPath);
28
+ const configPath = options.generator.config.clientImportPath;
29
+ const clientImportPath = Array.isArray(configPath)
30
+ ? configPath[0]
31
+ : configPath ?? "@prisma/client";
32
+ // Get datasource provider (e.g., "sqlite", "postgresql", "mysql", etc.)
33
+ const datasources = options.datasources;
34
+ const provider = datasources[0]?.provider; // Usually there's one datasource, but could be multiple
35
+ // Custom error configuration: "path/to/module#ErrorClassName"
36
+ // Path is relative to schema.prisma, e.g., "./errors#PrismaError"
37
+ // The module must export:
38
+ // - The error class (e.g., `export class PrismaError extends ...`)
39
+ // - A mapper function named `mapPrismaError` with signature:
40
+ // `(error: unknown, operation: string, model: string) => YourErrorType`
41
+ const errorConfigRaw = options.generator.config.errorImportPath;
42
+ const errorImportPathRaw = Array.isArray(errorConfigRaw)
43
+ ? errorConfigRaw[0]
44
+ : errorConfigRaw;
45
+ // Import file extension for generated imports (e.g., "js", "ts", or "" for no extension)
46
+ // Useful for ESM compatibility where imports need explicit extensions
47
+ const importExtConfigRaw = options.generator.config.importFileExtension;
48
+ const importFileExtension = Array.isArray(importExtConfigRaw)
49
+ ? importExtConfigRaw[0]
50
+ : importExtConfigRaw ?? "";
51
+ if (!outputDir) {
52
+ throw new Error("No output directory specified");
53
+ }
54
+ // Helper to add file extension to a path if configured
55
+ const addExtension = (filePath) => {
56
+ if (!importFileExtension)
57
+ return filePath;
58
+ // Don't add extension if path already has one
59
+ const ext = node_path_1.default.extname(filePath);
60
+ if (ext)
61
+ return filePath;
62
+ return `${filePath}.${importFileExtension}`;
63
+ };
64
+ // Convert errorImportPath from schema-relative to output-relative
65
+ let errorImportPath;
66
+ if (errorImportPathRaw) {
67
+ const [modulePath, className] = errorImportPathRaw.split("#");
68
+ if (!modulePath || !className) {
69
+ throw new Error(`Invalid errorImportPath format: "${errorImportPathRaw}". Expected "path/to/module#ErrorClassName"`);
70
+ }
71
+ // If it's a relative path, convert from schema-relative to output-relative
72
+ if (modulePath.startsWith(".")) {
73
+ const absoluteErrorPath = node_path_1.default.resolve(schemaDir, modulePath);
74
+ const relativeToOutput = node_path_1.default.relative(outputDir, absoluteErrorPath);
75
+ // Ensure it starts with ./ or ../
76
+ const normalizedPath = relativeToOutput.startsWith(".")
77
+ ? relativeToOutput
78
+ : `./${relativeToOutput}`;
79
+ // Add file extension if configured
80
+ const pathWithExtension = addExtension(normalizedPath);
81
+ errorImportPath = `${pathWithExtension}#${className}`;
82
+ }
83
+ else {
84
+ // Package import (e.g., "@myorg/errors#PrismaError"), use as-is
85
+ errorImportPath = errorImportPathRaw;
86
+ }
87
+ }
88
+ // Clean output directory
89
+ await promises_1.default.rm(outputDir, { recursive: true, force: true });
90
+ await promises_1.default.mkdir(outputDir, { recursive: true });
91
+ // Generate unified index file with PrismaService
92
+ await generateUnifiedService([...models], outputDir, clientImportPath, errorImportPath, provider);
93
+ },
94
+ });
95
+ function generateRawSqlOperations(customError) {
96
+ // With custom error, use mapError which maps to user's error type
97
+ // Without custom error, use mapError which maps to PrismaError union
98
+ const errorType = customError ? customError.className : "PrismaError";
99
+ return `
100
+ $executeRaw: (args: PrismaNamespace.Sql | [PrismaNamespace.Sql, ...any[]]): Effect.Effect<number, ${errorType}, PrismaClient> =>
101
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
102
+ Effect.tryPromise({
103
+ try: () => (Array.isArray(args) ? client.$executeRaw(args[0], ...args.slice(1)) : client.$executeRaw(args)),
104
+ catch: (error) => mapError(error, "$executeRaw", "Prisma")
105
+ })
106
+ ),
107
+
108
+ $executeRawUnsafe: (query: string, ...values: any[]): Effect.Effect<number, ${errorType}, PrismaClient> =>
109
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
110
+ Effect.tryPromise({
111
+ try: () => client.$executeRawUnsafe(query, ...values),
112
+ catch: (error) => mapError(error, "$executeRawUnsafe", "Prisma")
113
+ })
114
+ ),
115
+
116
+ $queryRaw: <T = unknown>(args: PrismaNamespace.Sql | [PrismaNamespace.Sql, ...any[]]): Effect.Effect<T, ${errorType}, PrismaClient> =>
117
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
118
+ Effect.tryPromise({
119
+ try: () => (Array.isArray(args) ? client.$queryRaw(args[0], ...args.slice(1)) : client.$queryRaw(args)) as Promise<T>,
120
+ catch: (error) => mapError(error, "$queryRaw", "Prisma")
121
+ })
122
+ ),
123
+
124
+ $queryRawUnsafe: <T = unknown>(query: string, ...values: any[]): Effect.Effect<T, ${errorType}, PrismaClient> =>
125
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
126
+ Effect.tryPromise({
127
+ try: () => client.$queryRawUnsafe(query, ...values) as Promise<T>,
128
+ catch: (error) => mapError(error, "$queryRawUnsafe", "Prisma")
129
+ })
130
+ ),`;
131
+ }
132
+ function generateModelOperations(models, customError, provider) {
133
+ // Check if provider supports createManyAndReturn and updateManyAndReturn
134
+ const supportsManyAndReturn = provider === "postgresql" ||
135
+ provider === "postgres" ||
136
+ provider === "prisma+postgres" ||
137
+ provider === "cockroachdb" ||
138
+ provider === "sqlite";
139
+ return models
140
+ .map((model) => {
141
+ const modelName = model.name;
142
+ const modelNameCamel = toCamelCase(modelName);
143
+ // Type alias for the model delegate (e.g., BasePrismaClient['user'])
144
+ const delegate = `BasePrismaClient['${modelNameCamel}']`;
145
+ // Cast Promise results to ensure consistent typing across Prisma versions
146
+ // This handles Prisma 7's GlobalOmitConfig and works fine with Prisma 6 too
147
+ const promiseCast = (op, nullable = false) => {
148
+ const resultType = `PrismaNamespace.Result<${delegate}, A, '${op}'>`;
149
+ const fullType = nullable ? `${resultType} | null` : resultType;
150
+ return ` as Promise<${fullType}>`;
151
+ };
152
+ // Aggregate/groupBy use complex internal types that need stronger casts
153
+ const strongPromiseCast = (op) => {
154
+ const resultType = `PrismaNamespace.Result<${delegate}, A, '${op}'>`;
155
+ return ` as unknown as Promise<${resultType}>`;
156
+ };
157
+ const resultType = (op, nullable = false) => {
158
+ const baseType = `PrismaNamespace.Result<${delegate}, A, '${op}'>`;
159
+ return nullable ? `${baseType} | null` : baseType;
160
+ };
161
+ // With custom error: all operations use single error type and mapError
162
+ // Without custom error: use per-operation error types and mappers
163
+ const errorType = (opErrorType) => customError ? customError.className : opErrorType;
164
+ const mapperFn = (defaultMapper) => customError ? "mapError" : defaultMapper;
165
+ return ` ${modelNameCamel}: {
166
+ findUnique: <A extends PrismaNamespace.Args<${delegate}, 'findUnique'>>(
167
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'findUnique'>>
168
+ ): Effect.Effect<${resultType("findUnique", true)}, ${errorType("PrismaFindError")}, PrismaClient> =>
169
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
170
+ Effect.tryPromise({
171
+ try: () => client.${modelNameCamel}.findUnique(args as any)${promiseCast("findUnique", true)},
172
+ catch: (error) => ${mapperFn("mapFindError")}(error, "findUnique", "${modelName}")
173
+ })
174
+ ),
175
+
176
+ findUniqueOrThrow: <A extends PrismaNamespace.Args<${delegate}, 'findUniqueOrThrow'>>(
177
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'findUniqueOrThrow'>>
178
+ ): Effect.Effect<${resultType("findUniqueOrThrow")}, ${errorType("PrismaFindOrThrowError")}, PrismaClient> =>
179
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
180
+ Effect.tryPromise({
181
+ try: () => client.${modelNameCamel}.findUniqueOrThrow(args as any)${promiseCast("findUniqueOrThrow")},
182
+ catch: (error) => ${mapperFn("mapFindOrThrowError")}(error, "findUniqueOrThrow", "${modelName}")
183
+ })
184
+ ),
185
+
186
+ findFirst: <A extends PrismaNamespace.Args<${delegate}, 'findFirst'> = {}>(
187
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'findFirst'>>
188
+ ): Effect.Effect<${resultType("findFirst", true)}, ${errorType("PrismaFindError")}, PrismaClient> =>
189
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
190
+ Effect.tryPromise({
191
+ try: () => client.${modelNameCamel}.findFirst(args as any)${promiseCast("findFirst", true)},
192
+ catch: (error) => ${mapperFn("mapFindError")}(error, "findFirst", "${modelName}")
193
+ })
194
+ ),
195
+
196
+ findFirstOrThrow: <A extends PrismaNamespace.Args<${delegate}, 'findFirstOrThrow'> = {}>(
197
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'findFirstOrThrow'>>
198
+ ): Effect.Effect<${resultType("findFirstOrThrow")}, ${errorType("PrismaFindOrThrowError")}, PrismaClient> =>
199
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
200
+ Effect.tryPromise({
201
+ try: () => client.${modelNameCamel}.findFirstOrThrow(args as any)${promiseCast("findFirstOrThrow")},
202
+ catch: (error) => ${mapperFn("mapFindOrThrowError")}(error, "findFirstOrThrow", "${modelName}")
203
+ })
204
+ ),
205
+
206
+ findMany: <A extends PrismaNamespace.Args<${delegate}, 'findMany'> = {}>(
207
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'findMany'>>
208
+ ): Effect.Effect<${resultType("findMany")}, ${errorType("PrismaFindError")}, PrismaClient> =>
209
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
210
+ Effect.tryPromise({
211
+ try: () => client.${modelNameCamel}.findMany(args as any)${promiseCast("findMany")},
212
+ catch: (error) => ${mapperFn("mapFindError")}(error, "findMany", "${modelName}")
213
+ })
214
+ ),
215
+
216
+ create: <A extends PrismaNamespace.Args<${delegate}, 'create'>>(
217
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'create'>>
218
+ ): Effect.Effect<${resultType("create")}, ${errorType("PrismaCreateError")}, PrismaClient> =>
219
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
220
+ Effect.tryPromise({
221
+ try: () => client.${modelNameCamel}.create(args as any)${promiseCast("create")},
222
+ catch: (error) => ${mapperFn("mapCreateError")}(error, "create", "${modelName}")
223
+ })
224
+ ),
225
+
226
+ createMany: (
227
+ args?: PrismaNamespace.Args<${delegate}, 'createMany'>
228
+ ): Effect.Effect<PrismaNamespace.BatchPayload, ${errorType("PrismaCreateError")}, PrismaClient> =>
229
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
230
+ Effect.tryPromise({
231
+ try: () => client.${modelNameCamel}.createMany(args as any),
232
+ catch: (error) => ${mapperFn("mapCreateError")}(error, "createMany", "${modelName}")
233
+ })
234
+ ),${supportsManyAndReturn
235
+ ? `
236
+
237
+ createManyAndReturn: <A extends PrismaNamespace.Args<${delegate}, 'createManyAndReturn'>>(
238
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'createManyAndReturn'>>
239
+ ): Effect.Effect<${resultType("createManyAndReturn")}, ${errorType("PrismaCreateError")}, PrismaClient> =>
240
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
241
+ Effect.tryPromise({
242
+ try: () => client.${modelNameCamel}.createManyAndReturn(args as any)${promiseCast("createManyAndReturn")},
243
+ catch: (error) => ${mapperFn("mapCreateError")}(error, "createManyAndReturn", "${modelName}")
244
+ })
245
+ ),`
246
+ : ""}
247
+
248
+ delete: <A extends PrismaNamespace.Args<${delegate}, 'delete'>>(
249
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'delete'>>
250
+ ): Effect.Effect<${resultType("delete")}, ${errorType("PrismaDeleteError")}, PrismaClient> =>
251
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
252
+ Effect.tryPromise({
253
+ try: () => client.${modelNameCamel}.delete(args as any)${promiseCast("delete")},
254
+ catch: (error) => ${mapperFn("mapDeleteError")}(error, "delete", "${modelName}")
255
+ })
256
+ ),
257
+
258
+ update: <A extends PrismaNamespace.Args<${delegate}, 'update'>>(
259
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'update'>>
260
+ ): Effect.Effect<${resultType("update")}, ${errorType("PrismaUpdateError")}, PrismaClient> =>
261
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
262
+ Effect.tryPromise({
263
+ try: () => client.${modelNameCamel}.update(args as any)${promiseCast("update")},
264
+ catch: (error) => ${mapperFn("mapUpdateError")}(error, "update", "${modelName}")
265
+ })
266
+ ),
267
+
268
+ deleteMany: (
269
+ args?: PrismaNamespace.Args<${delegate}, 'deleteMany'>
270
+ ): Effect.Effect<PrismaNamespace.BatchPayload, ${errorType("PrismaDeleteManyError")}, PrismaClient> =>
271
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
272
+ Effect.tryPromise({
273
+ try: () => client.${modelNameCamel}.deleteMany(args as any),
274
+ catch: (error) => ${mapperFn("mapDeleteManyError")}(error, "deleteMany", "${modelName}")
275
+ })
276
+ ),
277
+
278
+ updateMany: (
279
+ args: PrismaNamespace.Args<${delegate}, 'updateMany'>
280
+ ): Effect.Effect<PrismaNamespace.BatchPayload, ${errorType("PrismaUpdateManyError")}, PrismaClient> =>
281
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
282
+ Effect.tryPromise({
283
+ try: () => client.${modelNameCamel}.updateMany(args as any),
284
+ catch: (error) => ${mapperFn("mapUpdateManyError")}(error, "updateMany", "${modelName}")
285
+ })
286
+ ),${supportsManyAndReturn
287
+ ? `
288
+
289
+ updateManyAndReturn: <A extends PrismaNamespace.Args<${delegate}, 'updateManyAndReturn'>>(
290
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'updateManyAndReturn'>>
291
+ ): Effect.Effect<${resultType("updateManyAndReturn")}, ${errorType("PrismaUpdateManyError")}, PrismaClient> =>
292
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
293
+ Effect.tryPromise({
294
+ try: () => client.${modelNameCamel}.updateManyAndReturn(args as any)${promiseCast("updateManyAndReturn")},
295
+ catch: (error) => ${mapperFn("mapUpdateManyError")}(error, "updateManyAndReturn", "${modelName}")
296
+ })
297
+ ),`
298
+ : ""}
299
+
300
+ upsert: <A extends PrismaNamespace.Args<${delegate}, 'upsert'>>(
301
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'upsert'>>
302
+ ): Effect.Effect<${resultType("upsert")}, ${errorType("PrismaCreateError")}, PrismaClient> =>
303
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
304
+ Effect.tryPromise({
305
+ try: () => client.${modelNameCamel}.upsert(args as any)${promiseCast("upsert")},
306
+ catch: (error) => ${mapperFn("mapCreateError")}(error, "upsert", "${modelName}")
307
+ })
308
+ ),
309
+
310
+ // Aggregation operations
311
+ count: <A extends PrismaNamespace.Args<${delegate}, 'count'> = {}>(
312
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'count'>>
313
+ ): Effect.Effect<${resultType("count")}, ${errorType("PrismaFindError")}, PrismaClient> =>
314
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
315
+ Effect.tryPromise({
316
+ try: () => client.${modelNameCamel}.count(args as any)${promiseCast("count")},
317
+ catch: (error) => ${mapperFn("mapFindError")}(error, "count", "${modelName}")
318
+ })
319
+ ),
320
+
321
+ aggregate: <A extends PrismaNamespace.Args<${delegate}, 'aggregate'>>(
322
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'aggregate'>>
323
+ ): Effect.Effect<${resultType("aggregate")}, ${errorType("PrismaFindError")}, PrismaClient> =>
324
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
325
+ Effect.tryPromise({
326
+ try: () => client.${modelNameCamel}.aggregate(args as any)${strongPromiseCast("aggregate")},
327
+ catch: (error) => ${mapperFn("mapFindError")}(error, "aggregate", "${modelName}")
328
+ })
329
+ ),
330
+
331
+ groupBy: <A extends PrismaNamespace.Args<${delegate}, 'groupBy'>>(
332
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<${delegate}, 'groupBy'>>
333
+ ): Effect.Effect<${resultType("groupBy")}, ${errorType("PrismaFindError")}, PrismaClient> =>
334
+ Effect.flatMap(PrismaClient, ({ tx: client }) =>
335
+ Effect.tryPromise({
336
+ try: () => client.${modelNameCamel}.groupBy(args as any)${strongPromiseCast("groupBy")},
337
+ catch: (error) => ${mapperFn("mapFindError")}(error, "groupBy", "${modelName}")
338
+ })
339
+ )
340
+ }`;
341
+ })
342
+ .join(",\n\n");
343
+ }
344
+ // Parse error import path like "./errors#PrismaError" into { path, className }
345
+ function parseErrorImportPath(errorImportPath) {
346
+ if (!errorImportPath)
347
+ return null;
348
+ const [path, className] = errorImportPath.split("#");
349
+ if (!path || !className) {
350
+ throw new Error(`Invalid errorImportPath format: "${errorImportPath}". Expected "path/to/module#ErrorClassName"`);
351
+ }
352
+ return { path, className };
353
+ }
354
+ async function generateUnifiedService(models, outputDir, clientImportPath, errorImportPath, provider) {
355
+ const customError = parseErrorImportPath(errorImportPath);
356
+ const rawSqlOperations = generateRawSqlOperations(customError);
357
+ const modelOperations = generateModelOperations(models, customError, provider);
358
+ // Generate different content based on whether custom error is configured
359
+ const serviceContent = customError
360
+ ? generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelOperations)
361
+ : generateDefaultErrorService(clientImportPath, rawSqlOperations, modelOperations);
362
+ await promises_1.default.writeFile(node_path_1.default.join(outputDir, "index.ts"), serviceContent);
363
+ }
364
+ /**
365
+ * Generate service with custom user-provided error class.
366
+ * All operations use a single error type and a simple mapError function.
367
+ */
368
+ function generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelOperations) {
369
+ return `${header}
370
+ import { Context, Effect, Exit, Layer } from "effect"
371
+ import { Service } from "effect/Effect"
372
+ import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "${clientImportPath}"
373
+ import { ${customError.className}, mapPrismaError } from "${customError.path}"
374
+
375
+ // Symbol used to identify intentional rollbacks vs actual errors
376
+ const ROLLBACK = Symbol.for("prisma.effect.rollback")
377
+
378
+ // Type for the flat transaction client with commit/rollback control
379
+ type FlatTransactionClient = PrismaNamespace.TransactionClient & {
380
+ $commit: () => Promise<void>
381
+ $rollback: () => Promise<void>
382
+ }
383
+
384
+ /** Transaction options for $transaction and $isolatedTransaction */
385
+ type TransactionOptions = {
386
+ maxWait?: number
387
+ timeout?: number
388
+ isolationLevel?: PrismaNamespace.TransactionIsolationLevel
389
+ }
390
+
391
+ /**
392
+ * Context tag for the Prisma client instance.
393
+ * Holds the transaction client (tx) and root client.
394
+ *
395
+ * Use \`PrismaClient.layer()\` or \`PrismaClient.layerEffect()\` to create a layer.
396
+ *
397
+ * @example
398
+ * // Prisma 6 - all options are optional
399
+ * const layer = PrismaClient.layer({ datasourceUrl: "..." })
400
+ *
401
+ * // Prisma 7 - adapter or accelerateUrl is required
402
+ * const layer = PrismaClient.layer({ adapter: myAdapter })
403
+ *
404
+ * // With transaction options (Prisma uses these as defaults for $transaction)
405
+ * const layer = PrismaClient.layer({
406
+ * adapter: myAdapter, // or datasourceUrl for Prisma 6
407
+ * transactionOptions: { isolationLevel: "Serializable", timeout: 10000 }
408
+ * })
409
+ */
410
+ export class PrismaClient extends Context.Tag("PrismaClient")<
411
+ PrismaClient,
412
+ {
413
+ tx: BasePrismaClient | PrismaNamespace.TransactionClient
414
+ client: BasePrismaClient
415
+ }
416
+ >() {
417
+ /**
418
+ * Create a PrismaClient layer with the given options.
419
+ * The client will be automatically disconnected when the layer scope ends.
420
+ *
421
+ * Pass options directly - the signature matches PrismaClient's constructor.
422
+ * Prisma 6: all options are optional
423
+ * Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
424
+ *
425
+ * @example
426
+ * // Prisma 6
427
+ * const layer = PrismaClient.layer({ datasourceUrl: process.env.DATABASE_URL })
428
+ *
429
+ * // Prisma 7 with adapter
430
+ * const layer = PrismaClient.layer({ adapter: myAdapter })
431
+ *
432
+ * // With transaction options
433
+ * const layer = PrismaClient.layer({
434
+ * adapter: myAdapter,
435
+ * transactionOptions: { isolationLevel: "Serializable" }
436
+ * })
437
+ */
438
+ static layer = (
439
+ ...args: ConstructorParameters<typeof BasePrismaClient>
440
+ ) => Layer.scoped(
441
+ PrismaClient,
442
+ Effect.gen(function* () {
443
+ const prisma = new BasePrismaClient(...args)
444
+ yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
445
+ return { tx: prisma, client: prisma }
446
+ })
447
+ )
448
+
449
+ /**
450
+ * Create a PrismaClient layer where options are computed via an Effect.
451
+ * Useful when you need to fetch configuration from environment, config service, or create adapters.
452
+ * The client will be automatically disconnected when the layer scope ends.
453
+ *
454
+ * @example
455
+ * // Get config from a service
456
+ * const layer = PrismaClient.layerEffect(
457
+ * Effect.gen(function* () {
458
+ * const config = yield* ConfigService
459
+ * return { adapter: createAdapter(config.databaseUrl) }
460
+ * })
461
+ * )
462
+ *
463
+ * // With transaction options
464
+ * const layer = PrismaClient.layerEffect(
465
+ * Effect.gen(function* () {
466
+ * return {
467
+ * adapter: myAdapter,
468
+ * transactionOptions: { isolationLevel: "Serializable" }
469
+ * }
470
+ * })
471
+ * )
472
+ */
473
+ static layerEffect = <R, E>(
474
+ optionsEffect: Effect.Effect<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
475
+ ) => Layer.scoped(
476
+ PrismaClient,
477
+ Effect.gen(function* () {
478
+ const options = yield* optionsEffect
479
+ const prisma = new BasePrismaClient(options)
480
+ yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
481
+ return { tx: prisma, client: prisma }
482
+ })
483
+ )
484
+ }
485
+
486
+ // Re-export the custom error type for convenience
487
+ export { ${customError.className} }
488
+
489
+ // Use the user-provided error mapper
490
+ const mapError = mapPrismaError
491
+
492
+ /**
493
+ * Internal helper to begin a callback-free interactive transaction.
494
+ * Returns a transaction client with $commit and $rollback methods.
495
+ * This allows transactions to run in the same fiber as the parent effect.
496
+ */
497
+ const $begin = (
498
+ client: BasePrismaClient,
499
+ options?: {
500
+ maxWait?: number
501
+ timeout?: number
502
+ isolationLevel?: PrismaNamespace.TransactionIsolationLevel
503
+ }
504
+ ): Effect.Effect<FlatTransactionClient, ${customError.className}> =>
505
+ Effect.async<FlatTransactionClient, ${customError.className}>((resume) => {
506
+ let setTxClient: (txClient: PrismaNamespace.TransactionClient) => void
507
+ let commit: () => void
508
+ let rollback: () => void
509
+
510
+ // Promise that resolves when we get the transaction client
511
+ const txClientPromise = new Promise<PrismaNamespace.TransactionClient>((res) => {
512
+ setTxClient = res
513
+ })
514
+
515
+ // Promise that controls when the transaction commits/rolls back
516
+ const txPromise = new Promise<void>((_res, _rej) => {
517
+ commit = () => _res(undefined)
518
+ rollback = () => _rej(ROLLBACK)
519
+ })
520
+
521
+ // Start the transaction - Prisma will wait on txPromise before committing
522
+ const tx = client.$transaction((txClient) => {
523
+ setTxClient(txClient)
524
+ return txPromise
525
+ }, options).catch((e) => {
526
+ // Swallow intentional rollbacks, rethrow actual errors
527
+ if (e === ROLLBACK) return
528
+ throw e
529
+ })
530
+
531
+ // Once we have the transaction client, wrap it with commit/rollback methods
532
+ txClientPromise.then((innerTx) => {
533
+ const proxy = new Proxy(innerTx, {
534
+ get(target, prop) {
535
+ if (prop === "$commit") return () => { commit(); return tx }
536
+ if (prop === "$rollback") return () => { rollback(); return tx }
537
+ return target[prop as keyof typeof target]
538
+ },
539
+ }) as FlatTransactionClient
540
+ resume(Effect.succeed(proxy))
541
+ }).catch((error) => {
542
+ resume(Effect.fail(mapError(error, "$transaction", "Prisma")))
543
+ })
544
+ })
545
+
546
+ /**
547
+ * The main Prisma service with all database operations.
548
+ * Provides type-safe, effectful access to your Prisma models.
549
+ *
550
+ * @example
551
+ * const program = Effect.gen(function* () {
552
+ * const prisma = yield* Prisma
553
+ * const user = yield* prisma.user.create({ data: { name: "Alice" } })
554
+ * return user
555
+ * })
556
+ *
557
+ * // Run with default layer (Prisma 6)
558
+ * Effect.runPromise(program.pipe(Effect.provide(Prisma.Live)))
559
+ *
560
+ * // Or with custom options
561
+ * Effect.runPromise(program.pipe(Effect.provide(Prisma.layer({ datasourceUrl: "..." }))))
562
+ */
563
+ export class Prisma extends Service<Prisma>()("Prisma", {
564
+ effect: Effect.gen(function* () {
565
+ return {
566
+ /**
567
+ * Execute an effect within a database transaction.
568
+ * All operations within the effect will be atomic - they either all succeed or all fail.
569
+ *
570
+ * This implementation uses a callback-free transaction pattern that keeps the effect
571
+ * running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
572
+ *
573
+ * Options passed here override any defaults set via TransactionConfig layer.
574
+ *
575
+ * @example
576
+ * const result = yield* prisma.$transaction(
577
+ * Effect.gen(function* () {
578
+ * const user = yield* prisma.user.create({ data: { name: "Alice" } })
579
+ * yield* prisma.post.create({ data: { title: "Hello", authorId: user.id } })
580
+ * return user
581
+ * })
582
+ * )
583
+ *
584
+ * @example
585
+ * // Override default isolation level for this transaction
586
+ * const result = yield* prisma.$transaction(myEffect, {
587
+ * isolationLevel: "ReadCommitted"
588
+ * })
589
+ */
590
+ $transaction: <R, E, A>(
591
+ effect: Effect.Effect<A, E, R>,
592
+ options?: TransactionOptions
593
+ ) =>
594
+ Effect.flatMap(
595
+ PrismaClient,
596
+ ({ client, tx }): Effect.Effect<A, E | ${customError.className}, R> => {
597
+ // If we're already in a transaction, just run the effect directly (no nesting)
598
+ const isRootClient = "$transaction" in tx
599
+ if (!isRootClient) {
600
+ return effect
601
+ }
602
+
603
+ // Use acquireUseRelease to manage the transaction lifecycle
604
+ // This keeps everything in the same fiber, preserving Ref/FiberRef/Context
605
+ return Effect.acquireUseRelease(
606
+ // Acquire: begin a new transaction
607
+ // Prisma merges per-call options with constructor defaults internally
608
+ $begin(client, options),
609
+
610
+ // Use: run the effect with the transaction client injected
611
+ (txClient) =>
612
+ effect.pipe(
613
+ Effect.provideService(PrismaClient, { tx: txClient, client })
614
+ ),
615
+
616
+ // Release: commit on success, rollback on failure/interruption
617
+ (txClient, exit) =>
618
+ Exit.isSuccess(exit)
619
+ ? Effect.promise(() => txClient.$commit())
620
+ : Effect.promise(() => txClient.$rollback())
621
+ )
622
+ }
623
+ ),
624
+
625
+ /**
626
+ * Execute an effect in a NEW transaction, even if already inside a transaction.
627
+ * Unlike \`$transaction\`, this always creates a fresh, independent transaction.
628
+ *
629
+ * Use this for operations that should NOT be rolled back with the parent:
630
+ * - Audit logging that must persist even if main operation fails
631
+ * - Saga pattern where each step has independent commit/rollback
632
+ * - Background job queuing that should commit immediately
633
+ *
634
+ * ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
635
+ * or vice versa. Use carefully to avoid data inconsistencies.
636
+ *
637
+ * @example
638
+ * yield* prisma.$transaction(
639
+ * Effect.gen(function* () {
640
+ * // This audit log commits independently - survives parent rollback
641
+ * yield* prisma.$isolatedTransaction(
642
+ * prisma.auditLog.create({ data: { action: "attempt", userId } })
643
+ * )
644
+ * // Main operation - if this fails, audit log is still committed
645
+ * yield* prisma.user.delete({ where: { id: userId } })
646
+ * })
647
+ * )
648
+ */
649
+ $isolatedTransaction: <R, E, A>(
650
+ effect: Effect.Effect<A, E, R>,
651
+ options?: TransactionOptions
652
+ ) =>
653
+ Effect.flatMap(
654
+ PrismaClient,
655
+ ({ client }): Effect.Effect<A, E | ${customError.className}, R> => {
656
+ // Always use the root client to create a fresh transaction
657
+ return Effect.acquireUseRelease(
658
+ $begin(client, options),
659
+ (txClient) =>
660
+ effect.pipe(
661
+ Effect.provideService(PrismaClient, { tx: txClient, client })
662
+ ),
663
+ (txClient, exit) =>
664
+ Exit.isSuccess(exit)
665
+ ? Effect.promise(() => txClient.$commit())
666
+ : Effect.promise(() => txClient.$rollback())
667
+ )
668
+ }
669
+ ),
670
+ ${rawSqlOperations}
671
+
672
+ ${modelOperations}
673
+ }
674
+ })
675
+ }) {
676
+ /**
677
+ * Create a complete Prisma layer with the given PrismaClient options.
678
+ * This is the recommended way to create a Prisma layer - it bundles both
679
+ * PrismaClient and Prisma service together.
680
+ *
681
+ * Pass options directly - the signature matches PrismaClient's constructor.
682
+ * Prisma 6: all options are optional
683
+ * Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
684
+ *
685
+ * @example
686
+ * // Prisma 6
687
+ * const MainLayer = Prisma.layer({ datasourceUrl: process.env.DATABASE_URL })
688
+ *
689
+ * // Prisma 7 with adapter
690
+ * const MainLayer = Prisma.layer({ adapter: myAdapter })
691
+ *
692
+ * // With transaction options
693
+ * const MainLayer = Prisma.layer({
694
+ * adapter: myAdapter,
695
+ * transactionOptions: { isolationLevel: "Serializable" }
696
+ * })
697
+ *
698
+ * // Use it
699
+ * Effect.runPromise(program.pipe(Effect.provide(MainLayer)))
700
+ */
701
+ static layer = (
702
+ ...args: ConstructorParameters<typeof BasePrismaClient>
703
+ ) => Layer.merge(PrismaClient.layer(...args), Prisma.Default)
704
+
705
+ /**
706
+ * Create a complete Prisma layer where PrismaClient options are computed via an Effect.
707
+ * This is useful when you need to fetch configuration or create adapters using Effect.
708
+ *
709
+ * @example
710
+ * // Get config from a service
711
+ * const MainLayer = Prisma.layerEffect(
712
+ * Effect.gen(function* () {
713
+ * const config = yield* ConfigService
714
+ * return { adapter: createAdapter(config.databaseUrl) }
715
+ * })
716
+ * )
717
+ *
718
+ * // With transaction options
719
+ * const MainLayer = Prisma.layerEffect(
720
+ * Effect.gen(function* () {
721
+ * return {
722
+ * adapter: myAdapter,
723
+ * transactionOptions: { isolationLevel: "Serializable" }
724
+ * }
725
+ * })
726
+ * )
727
+ */
728
+ static layerEffect = <R, E>(
729
+ optionsEffect: Effect.Effect<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
730
+ ) => Layer.merge(PrismaClient.layerEffect(optionsEffect), Prisma.Default)
731
+
732
+ }
733
+
734
+ // ============================================================================
735
+ // Deprecated aliases for backward compatibility
736
+ // ============================================================================
737
+
738
+ /**
739
+ * @deprecated Use \`PrismaClient\` instead. Will be removed in next major version.
740
+ */
741
+ export const PrismaClientService = PrismaClient
742
+
743
+ /**
744
+ * @deprecated Use \`Prisma\` instead. Will be removed in next major version.
745
+ */
746
+ export const PrismaService = Prisma
747
+
748
+ /**
749
+ * @deprecated Use \`PrismaClient.layer()\` instead. Will be removed in next major version.
750
+ */
751
+ export const makePrismaLayer = PrismaClient.layer
752
+
753
+ /**
754
+ * @deprecated Use \`PrismaClient.layerEffect()\` instead. Will be removed in next major version.
755
+ */
756
+ export const makePrismaLayerEffect = PrismaClient.layerEffect
757
+
758
+
759
+ `;
760
+ }
761
+ /**
762
+ * Generate service with default tagged error classes.
763
+ * Operations have per-operation error types for fine-grained error handling.
764
+ */
765
+ function generateDefaultErrorService(clientImportPath, rawSqlOperations, modelOperations) {
766
+ return `${header}
767
+ import { Context, Data, Effect, Exit, Layer } from "effect"
768
+ import { Service } from "effect/Effect"
769
+ import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "${clientImportPath}"
770
+
771
+ // Symbol used to identify intentional rollbacks vs actual errors
772
+ const ROLLBACK = Symbol.for("prisma.effect.rollback")
773
+
774
+ // Type for the flat transaction client with commit/rollback control
775
+ type FlatTransactionClient = PrismaNamespace.TransactionClient & {
776
+ $commit: () => Promise<void>
777
+ $rollback: () => Promise<void>
778
+ }
779
+
780
+ /** Transaction options for $transaction and $isolatedTransaction */
781
+ type TransactionOptions = {
782
+ maxWait?: number
783
+ timeout?: number
784
+ isolationLevel?: PrismaNamespace.TransactionIsolationLevel
785
+ }
786
+
787
+ /**
788
+ * Context tag for the Prisma client instance.
789
+ * Holds the transaction client (tx) and root client.
790
+ *
791
+ * Use \`PrismaClient.layer()\` or \`PrismaClient.layerEffect()\` to create a layer.
792
+ *
793
+ * @example
794
+ * // Prisma 6 - all options are optional
795
+ * const layer = PrismaClient.layer({ datasourceUrl: "..." })
796
+ *
797
+ * // Prisma 7 - adapter or accelerateUrl is required
798
+ * const layer = PrismaClient.layer({ adapter: myAdapter })
799
+ *
800
+ * // With transaction options (Prisma uses these as defaults for $transaction)
801
+ * const layer = PrismaClient.layer({
802
+ * adapter: myAdapter, // or datasourceUrl for Prisma 6
803
+ * transactionOptions: { isolationLevel: "Serializable", timeout: 10000 }
804
+ * })
805
+ */
806
+ export class PrismaClient extends Context.Tag("PrismaClient")<
807
+ PrismaClient,
808
+ {
809
+ tx: BasePrismaClient | PrismaNamespace.TransactionClient
810
+ client: BasePrismaClient
811
+ }
812
+ >() {
813
+ /**
814
+ * Create a PrismaClient layer with the given options.
815
+ * The client will be automatically disconnected when the layer scope ends.
816
+ *
817
+ * Pass options directly - the signature matches PrismaClient's constructor.
818
+ * Prisma 6: all options are optional
819
+ * Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
820
+ *
821
+ * @example
822
+ * // Prisma 6
823
+ * const layer = PrismaClient.layer({ datasourceUrl: process.env.DATABASE_URL })
824
+ *
825
+ * // Prisma 7 with adapter
826
+ * const layer = PrismaClient.layer({ adapter: myAdapter })
827
+ *
828
+ * // With transaction options
829
+ * const layer = PrismaClient.layer({
830
+ * adapter: myAdapter,
831
+ * transactionOptions: { isolationLevel: "Serializable" }
832
+ * })
833
+ */
834
+ static layer = (
835
+ ...args: ConstructorParameters<typeof BasePrismaClient>
836
+ ) => Layer.scoped(
837
+ PrismaClient,
838
+ Effect.gen(function* () {
839
+ const prisma = new BasePrismaClient(...args)
840
+ yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
841
+ return { tx: prisma, client: prisma }
842
+ })
843
+ )
844
+
845
+ /**
846
+ * Create a PrismaClient layer where options are computed via an Effect.
847
+ * Useful when you need to fetch configuration from environment, config service, or create adapters.
848
+ * The client will be automatically disconnected when the layer scope ends.
849
+ *
850
+ * @example
851
+ * // Get config from a service
852
+ * const layer = PrismaClient.layerEffect(
853
+ * Effect.gen(function* () {
854
+ * const config = yield* ConfigService
855
+ * return { adapter: createAdapter(config.databaseUrl) }
856
+ * })
857
+ * )
858
+ *
859
+ * // With transaction options
860
+ * const layer = PrismaClient.layerEffect(
861
+ * Effect.gen(function* () {
862
+ * return {
863
+ * adapter: myAdapter,
864
+ * transactionOptions: { isolationLevel: "Serializable" }
865
+ * }
866
+ * })
867
+ * )
868
+ */
869
+ static layerEffect = <R, E>(
870
+ optionsEffect: Effect.Effect<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
871
+ ) => Layer.scoped(
872
+ PrismaClient,
873
+ Effect.gen(function* () {
874
+ const options = yield* optionsEffect
875
+ const prisma = new BasePrismaClient(options)
876
+ yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
877
+ return { tx: prisma, client: prisma }
878
+ })
879
+ )
880
+ }
881
+
882
+ export class PrismaUniqueConstraintError extends Data.TaggedError("PrismaUniqueConstraintError")<{
883
+ cause: PrismaNamespace.PrismaClientKnownRequestError
884
+ operation: string
885
+ model: string
886
+ }> {}
887
+
888
+ export class PrismaForeignKeyConstraintError extends Data.TaggedError("PrismaForeignKeyConstraintError")<{
889
+ cause: PrismaNamespace.PrismaClientKnownRequestError
890
+ operation: string
891
+ model: string
892
+ }> {}
893
+
894
+ export class PrismaRecordNotFoundError extends Data.TaggedError("PrismaRecordNotFoundError")<{
895
+ cause: PrismaNamespace.PrismaClientKnownRequestError
896
+ operation: string
897
+ model: string
898
+ }> {}
899
+
900
+ export class PrismaRelationViolationError extends Data.TaggedError("PrismaRelationViolationError")<{
901
+ cause: PrismaNamespace.PrismaClientKnownRequestError
902
+ operation: string
903
+ model: string
904
+ }> {}
905
+
906
+ export class PrismaRelatedRecordNotFoundError extends Data.TaggedError("PrismaRelatedRecordNotFoundError")<{
907
+ cause: PrismaNamespace.PrismaClientKnownRequestError
908
+ operation: string
909
+ model: string
910
+ }> {}
911
+
912
+ export class PrismaTransactionConflictError extends Data.TaggedError("PrismaTransactionConflictError")<{
913
+ cause: PrismaNamespace.PrismaClientKnownRequestError
914
+ operation: string
915
+ model: string
916
+ }> {}
917
+
918
+ export class PrismaValueTooLongError extends Data.TaggedError("PrismaValueTooLongError")<{
919
+ cause: PrismaNamespace.PrismaClientKnownRequestError
920
+ operation: string
921
+ model: string
922
+ }> {}
923
+
924
+ export class PrismaValueOutOfRangeError extends Data.TaggedError("PrismaValueOutOfRangeError")<{
925
+ cause: PrismaNamespace.PrismaClientKnownRequestError
926
+ operation: string
927
+ model: string
928
+ }> {}
929
+
930
+ export class PrismaDbConstraintError extends Data.TaggedError("PrismaDbConstraintError")<{
931
+ cause: PrismaNamespace.PrismaClientKnownRequestError
932
+ operation: string
933
+ model: string
934
+ }> {}
935
+
936
+ export class PrismaConnectionError extends Data.TaggedError("PrismaConnectionError")<{
937
+ cause: PrismaNamespace.PrismaClientKnownRequestError
938
+ operation: string
939
+ model: string
940
+ }> {}
941
+
942
+ export class PrismaMissingRequiredValueError extends Data.TaggedError("PrismaMissingRequiredValueError")<{
943
+ cause: PrismaNamespace.PrismaClientKnownRequestError
944
+ operation: string
945
+ model: string
946
+ }> {}
947
+
948
+ export class PrismaInputValidationError extends Data.TaggedError("PrismaInputValidationError")<{
949
+ cause: PrismaNamespace.PrismaClientKnownRequestError
950
+ operation: string
951
+ model: string
952
+ }> {}
953
+
954
+ export type PrismaCreateError =
955
+ | PrismaValueTooLongError
956
+ | PrismaUniqueConstraintError
957
+ | PrismaForeignKeyConstraintError
958
+ | PrismaDbConstraintError
959
+ | PrismaInputValidationError
960
+ | PrismaMissingRequiredValueError
961
+ | PrismaRelatedRecordNotFoundError
962
+ | PrismaValueOutOfRangeError
963
+ | PrismaConnectionError
964
+ | PrismaTransactionConflictError
965
+
966
+ export type PrismaUpdateError =
967
+ | PrismaValueTooLongError
968
+ | PrismaUniqueConstraintError
969
+ | PrismaForeignKeyConstraintError
970
+ | PrismaDbConstraintError
971
+ | PrismaInputValidationError
972
+ | PrismaMissingRequiredValueError
973
+ | PrismaRelationViolationError
974
+ | PrismaRelatedRecordNotFoundError
975
+ | PrismaValueOutOfRangeError
976
+ | PrismaConnectionError
977
+ | PrismaRecordNotFoundError
978
+ | PrismaTransactionConflictError
979
+
980
+ export type PrismaDeleteError =
981
+ | PrismaForeignKeyConstraintError
982
+ | PrismaRelationViolationError
983
+ | PrismaConnectionError
984
+ | PrismaRecordNotFoundError
985
+ | PrismaTransactionConflictError
986
+
987
+ export type PrismaFindOrThrowError =
988
+ | PrismaConnectionError
989
+ | PrismaRecordNotFoundError
990
+
991
+ export type PrismaFindError =
992
+ | PrismaConnectionError
993
+
994
+ export type PrismaDeleteManyError =
995
+ | PrismaForeignKeyConstraintError
996
+ | PrismaRelationViolationError
997
+ | PrismaConnectionError
998
+ | PrismaTransactionConflictError
999
+
1000
+ export type PrismaUpdateManyError =
1001
+ | PrismaValueTooLongError
1002
+ | PrismaUniqueConstraintError
1003
+ | PrismaForeignKeyConstraintError
1004
+ | PrismaDbConstraintError
1005
+ | PrismaInputValidationError
1006
+ | PrismaMissingRequiredValueError
1007
+ | PrismaValueOutOfRangeError
1008
+ | PrismaConnectionError
1009
+ | PrismaTransactionConflictError
1010
+
1011
+ export type PrismaError =
1012
+ | PrismaValueTooLongError
1013
+ | PrismaUniqueConstraintError
1014
+ | PrismaForeignKeyConstraintError
1015
+ | PrismaDbConstraintError
1016
+ | PrismaInputValidationError
1017
+ | PrismaMissingRequiredValueError
1018
+ | PrismaRelationViolationError
1019
+ | PrismaRelatedRecordNotFoundError
1020
+ | PrismaValueOutOfRangeError
1021
+ | PrismaConnectionError
1022
+ | PrismaRecordNotFoundError
1023
+ | PrismaTransactionConflictError
1024
+
1025
+ // Generic mapper for raw operations and fallback
1026
+ const mapError = (error: unknown, operation: string, model: string): PrismaError => {
1027
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
1028
+ switch (error.code) {
1029
+ case "P2000":
1030
+ return new PrismaValueTooLongError({ cause: error, operation, model });
1031
+ case "P2002":
1032
+ return new PrismaUniqueConstraintError({ cause: error, operation, model });
1033
+ case "P2003":
1034
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1035
+ case "P2004":
1036
+ return new PrismaDbConstraintError({ cause: error, operation, model });
1037
+ case "P2005":
1038
+ case "P2006":
1039
+ case "P2019":
1040
+ return new PrismaInputValidationError({ cause: error, operation, model });
1041
+ case "P2011":
1042
+ case "P2012":
1043
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model });
1044
+ case "P2014":
1045
+ return new PrismaRelationViolationError({ cause: error, operation, model });
1046
+ case "P2015":
1047
+ case "P2018":
1048
+ return new PrismaRelatedRecordNotFoundError({ cause: error, operation, model });
1049
+ case "P2020":
1050
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model });
1051
+ case "P2024":
1052
+ return new PrismaConnectionError({ cause: error, operation, model });
1053
+ case "P2025":
1054
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
1055
+ case "P2034":
1056
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1057
+ }
1058
+ }
1059
+ // Unknown errors are not handled and will be treated as defects
1060
+ throw error;
1061
+ }
1062
+
1063
+ // Specific mappers to narrow error types per operation
1064
+
1065
+ // Create, Upsert
1066
+ const mapCreateError = (error: unknown, operation: string, model: string): PrismaCreateError => {
1067
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
1068
+ switch (error.code) {
1069
+ case "P2000":
1070
+ return new PrismaValueTooLongError({ cause: error, operation, model });
1071
+ case "P2002":
1072
+ return new PrismaUniqueConstraintError({ cause: error, operation, model });
1073
+ case "P2003":
1074
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1075
+ case "P2004":
1076
+ return new PrismaDbConstraintError({ cause: error, operation, model });
1077
+ case "P2005":
1078
+ case "P2006":
1079
+ case "P2019":
1080
+ return new PrismaInputValidationError({ cause: error, operation, model });
1081
+ case "P2011":
1082
+ case "P2012":
1083
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model });
1084
+ case "P2015":
1085
+ case "P2018":
1086
+ return new PrismaRelatedRecordNotFoundError({ cause: error, operation, model });
1087
+ case "P2020":
1088
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model });
1089
+ case "P2024":
1090
+ return new PrismaConnectionError({ cause: error, operation, model });
1091
+ case "P2034":
1092
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1093
+ }
1094
+ }
1095
+ throw error;
1096
+ }
1097
+
1098
+ // Update
1099
+ const mapUpdateError = (error: unknown, operation: string, model: string): PrismaUpdateError => {
1100
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
1101
+ switch (error.code) {
1102
+ case "P2000":
1103
+ return new PrismaValueTooLongError({ cause: error, operation, model });
1104
+ case "P2002":
1105
+ return new PrismaUniqueConstraintError({ cause: error, operation, model });
1106
+ case "P2003":
1107
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1108
+ case "P2004":
1109
+ return new PrismaDbConstraintError({ cause: error, operation, model });
1110
+ case "P2005":
1111
+ case "P2006":
1112
+ case "P2019":
1113
+ return new PrismaInputValidationError({ cause: error, operation, model });
1114
+ case "P2011":
1115
+ case "P2012":
1116
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model });
1117
+ case "P2014":
1118
+ return new PrismaRelationViolationError({ cause: error, operation, model });
1119
+ case "P2015":
1120
+ case "P2018":
1121
+ return new PrismaRelatedRecordNotFoundError({ cause: error, operation, model });
1122
+ case "P2020":
1123
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model });
1124
+ case "P2024":
1125
+ return new PrismaConnectionError({ cause: error, operation, model });
1126
+ case "P2025":
1127
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
1128
+ case "P2034":
1129
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1130
+ }
1131
+ }
1132
+ throw error;
1133
+ }
1134
+
1135
+ // Delete
1136
+ const mapDeleteError = (error: unknown, operation: string, model: string): PrismaDeleteError => {
1137
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
1138
+ switch (error.code) {
1139
+ case "P2003":
1140
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1141
+ case "P2014":
1142
+ return new PrismaRelationViolationError({ cause: error, operation, model });
1143
+ case "P2024":
1144
+ return new PrismaConnectionError({ cause: error, operation, model });
1145
+ case "P2025":
1146
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
1147
+ case "P2034":
1148
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1149
+ }
1150
+ }
1151
+ throw error;
1152
+ }
1153
+
1154
+ // FindOrThrow
1155
+ const mapFindOrThrowError = (error: unknown, operation: string, model: string): PrismaFindOrThrowError => {
1156
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
1157
+ switch (error.code) {
1158
+ case "P2024":
1159
+ return new PrismaConnectionError({ cause: error, operation, model });
1160
+ case "P2025":
1161
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
1162
+ }
1163
+ }
1164
+ throw error;
1165
+ }
1166
+
1167
+ // Find
1168
+ const mapFindError = (error: unknown, operation: string, model: string): PrismaFindError => {
1169
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
1170
+ switch (error.code) {
1171
+ case "P2024":
1172
+ return new PrismaConnectionError({ cause: error, operation, model });
1173
+ }
1174
+ }
1175
+ throw error;
1176
+ }
1177
+
1178
+ // DeleteMany
1179
+ const mapDeleteManyError = (error: unknown, operation: string, model: string): PrismaDeleteManyError => {
1180
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
1181
+ switch (error.code) {
1182
+ case "P2003":
1183
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1184
+ case "P2014":
1185
+ return new PrismaRelationViolationError({ cause: error, operation, model });
1186
+ case "P2024":
1187
+ return new PrismaConnectionError({ cause: error, operation, model });
1188
+ case "P2034":
1189
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1190
+ }
1191
+ }
1192
+ throw error;
1193
+ }
1194
+
1195
+ // UpdateMany
1196
+ const mapUpdateManyError = (error: unknown, operation: string, model: string): PrismaUpdateManyError => {
1197
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
1198
+ switch (error.code) {
1199
+ case "P2000":
1200
+ return new PrismaValueTooLongError({ cause: error, operation, model });
1201
+ case "P2002":
1202
+ return new PrismaUniqueConstraintError({ cause: error, operation, model });
1203
+ case "P2003":
1204
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1205
+ case "P2004":
1206
+ return new PrismaDbConstraintError({ cause: error, operation, model });
1207
+ case "P2005":
1208
+ case "P2006":
1209
+ case "P2019":
1210
+ return new PrismaInputValidationError({ cause: error, operation, model });
1211
+ case "P2011":
1212
+ case "P2012":
1213
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model });
1214
+ case "P2020":
1215
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model });
1216
+ case "P2024":
1217
+ return new PrismaConnectionError({ cause: error, operation, model });
1218
+ case "P2034":
1219
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
1220
+ }
1221
+ }
1222
+ throw error;
1223
+ }
1224
+
1225
+ /**
1226
+ * Internal helper to begin a callback-free interactive transaction.
1227
+ * Returns a transaction client with $commit and $rollback methods.
1228
+ * This allows transactions to run in the same fiber as the parent effect.
1229
+ */
1230
+ const $begin = (
1231
+ client: BasePrismaClient,
1232
+ options?: {
1233
+ maxWait?: number
1234
+ timeout?: number
1235
+ isolationLevel?: PrismaNamespace.TransactionIsolationLevel
1236
+ }
1237
+ ): Effect.Effect<FlatTransactionClient, PrismaError> =>
1238
+ Effect.async<FlatTransactionClient, PrismaError>((resume) => {
1239
+ let setTxClient: (txClient: PrismaNamespace.TransactionClient) => void
1240
+ let commit: () => void
1241
+ let rollback: () => void
1242
+
1243
+ // Promise that resolves when we get the transaction client
1244
+ const txClientPromise = new Promise<PrismaNamespace.TransactionClient>((res) => {
1245
+ setTxClient = res
1246
+ })
1247
+
1248
+ // Promise that controls when the transaction commits/rolls back
1249
+ const txPromise = new Promise<void>((_res, _rej) => {
1250
+ commit = () => _res(undefined)
1251
+ rollback = () => _rej(ROLLBACK)
1252
+ })
1253
+
1254
+ // Start the transaction - Prisma will wait on txPromise before committing
1255
+ const tx = client.$transaction((txClient) => {
1256
+ setTxClient(txClient)
1257
+ return txPromise
1258
+ }, options).catch((e) => {
1259
+ // Swallow intentional rollbacks, rethrow actual errors
1260
+ if (e === ROLLBACK) return
1261
+ throw e
1262
+ })
1263
+
1264
+ // Once we have the transaction client, wrap it with commit/rollback methods
1265
+ txClientPromise.then((innerTx) => {
1266
+ const proxy = new Proxy(innerTx, {
1267
+ get(target, prop) {
1268
+ if (prop === "$commit") return () => { commit(); return tx }
1269
+ if (prop === "$rollback") return () => { rollback(); return tx }
1270
+ return target[prop as keyof typeof target]
1271
+ },
1272
+ }) as FlatTransactionClient
1273
+ resume(Effect.succeed(proxy))
1274
+ }).catch((error) => {
1275
+ resume(Effect.fail(mapError(error, "$transaction", "Prisma")))
1276
+ })
1277
+ })
1278
+
1279
+ /**
1280
+ * The main Prisma service with all database operations.
1281
+ * Provides type-safe, effectful access to your Prisma models.
1282
+ *
1283
+ * @example
1284
+ * const program = Effect.gen(function* () {
1285
+ * const prisma = yield* Prisma
1286
+ * const user = yield* prisma.user.create({ data: { name: "Alice" } })
1287
+ * return user
1288
+ * })
1289
+ *
1290
+ * // Run with default layer (Prisma 6)
1291
+ * Effect.runPromise(program.pipe(Effect.provide(Prisma.Live)))
1292
+ *
1293
+ * // Or with custom options
1294
+ * Effect.runPromise(program.pipe(Effect.provide(Prisma.layer({ datasourceUrl: "..." }))))
1295
+ */
1296
+ export class Prisma extends Service<Prisma>()("Prisma", {
1297
+ effect: Effect.gen(function* () {
1298
+ return {
1299
+ /**
1300
+ * Execute an effect within a database transaction.
1301
+ * All operations within the effect will be atomic - they either all succeed or all fail.
1302
+ *
1303
+ * This implementation uses a callback-free transaction pattern that keeps the effect
1304
+ * running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
1305
+ *
1306
+ * Options passed here override any defaults set via transactionOptions in the layer.
1307
+ *
1308
+ * @example
1309
+ * const result = yield* prisma.$transaction(
1310
+ * Effect.gen(function* () {
1311
+ * const user = yield* prisma.user.create({ data: { name: "Alice" } })
1312
+ * yield* prisma.post.create({ data: { title: "Hello", authorId: user.id } })
1313
+ * return user
1314
+ * })
1315
+ * )
1316
+ *
1317
+ * @example
1318
+ * // Override default isolation level for this transaction
1319
+ * const result = yield* prisma.$transaction(myEffect, {
1320
+ * isolationLevel: "ReadCommitted"
1321
+ * })
1322
+ */
1323
+ $transaction: <R, E, A>(
1324
+ effect: Effect.Effect<A, E, R>,
1325
+ options?: TransactionOptions
1326
+ ) =>
1327
+ Effect.flatMap(
1328
+ PrismaClient,
1329
+ ({ client, tx }): Effect.Effect<A, E | PrismaError, R> => {
1330
+ // If we're already in a transaction, just run the effect directly (no nesting)
1331
+ const isRootClient = "$transaction" in tx
1332
+ if (!isRootClient) {
1333
+ return effect
1334
+ }
1335
+
1336
+ // Use acquireUseRelease to manage the transaction lifecycle
1337
+ // This keeps everything in the same fiber, preserving Ref/FiberRef/Context
1338
+ return Effect.acquireUseRelease(
1339
+ // Acquire: begin a new transaction
1340
+ // Prisma merges per-call options with constructor defaults internally
1341
+ $begin(client, options),
1342
+
1343
+ // Use: run the effect with the transaction client injected
1344
+ (txClient) =>
1345
+ effect.pipe(
1346
+ Effect.provideService(PrismaClient, { tx: txClient, client })
1347
+ ),
1348
+
1349
+ // Release: commit on success, rollback on failure/interruption
1350
+ (txClient, exit) =>
1351
+ Exit.isSuccess(exit)
1352
+ ? Effect.promise(() => txClient.$commit())
1353
+ : Effect.promise(() => txClient.$rollback())
1354
+ )
1355
+ }
1356
+ ),
1357
+
1358
+ /**
1359
+ * Execute an effect in a NEW transaction, even if already inside a transaction.
1360
+ * Unlike \`$transaction\`, this always creates a fresh, independent transaction.
1361
+ *
1362
+ * Use this for operations that should NOT be rolled back with the parent:
1363
+ * - Audit logging that must persist even if main operation fails
1364
+ * - Saga pattern where each step has independent commit/rollback
1365
+ * - Background job queuing that should commit immediately
1366
+ *
1367
+ * ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
1368
+ * or vice versa. Use carefully to avoid data inconsistencies.
1369
+ *
1370
+ * @example
1371
+ * yield* prisma.$transaction(
1372
+ * Effect.gen(function* () {
1373
+ * // This audit log commits independently - survives parent rollback
1374
+ * yield* prisma.$isolatedTransaction(
1375
+ * prisma.auditLog.create({ data: { action: "attempt", userId } })
1376
+ * )
1377
+ * // Main operation - if this fails, audit log is still committed
1378
+ * yield* prisma.user.delete({ where: { id: userId } })
1379
+ * })
1380
+ * )
1381
+ */
1382
+ $isolatedTransaction: <R, E, A>(
1383
+ effect: Effect.Effect<A, E, R>,
1384
+ options?: TransactionOptions
1385
+ ) =>
1386
+ Effect.flatMap(
1387
+ PrismaClient,
1388
+ ({ client }): Effect.Effect<A, E | PrismaError, R> => {
1389
+ // Always use the root client to create a fresh transaction
1390
+ return Effect.acquireUseRelease(
1391
+ $begin(client, options),
1392
+ (txClient) =>
1393
+ effect.pipe(
1394
+ Effect.provideService(PrismaClient, { tx: txClient, client })
1395
+ ),
1396
+ (txClient, exit) =>
1397
+ Exit.isSuccess(exit)
1398
+ ? Effect.promise(() => txClient.$commit())
1399
+ : Effect.promise(() => txClient.$rollback())
1400
+ )
1401
+ }
1402
+ ),
1403
+ ${rawSqlOperations}
1404
+
1405
+ ${modelOperations}
1406
+ }
1407
+ })
1408
+ }) {
1409
+ /**
1410
+ * Create a complete Prisma layer with the given PrismaClient options.
1411
+ * This is the recommended way to create a Prisma layer - it bundles both
1412
+ * PrismaClient and Prisma service together.
1413
+ *
1414
+ * Pass options directly - the signature matches PrismaClient's constructor.
1415
+ * Prisma 6: all options are optional
1416
+ * Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
1417
+ *
1418
+ * @example
1419
+ * // Prisma 6
1420
+ * const MainLayer = Prisma.layer({ datasourceUrl: process.env.DATABASE_URL })
1421
+ *
1422
+ * // Prisma 7 with adapter
1423
+ * const MainLayer = Prisma.layer({ adapter: myAdapter })
1424
+ *
1425
+ * // With transaction options
1426
+ * const MainLayer = Prisma.layer({
1427
+ * adapter: myAdapter,
1428
+ * transactionOptions: { isolationLevel: "Serializable" }
1429
+ * })
1430
+ *
1431
+ * // Use it
1432
+ * Effect.runPromise(program.pipe(Effect.provide(MainLayer)))
1433
+ */
1434
+ static layer = (
1435
+ ...args: ConstructorParameters<typeof BasePrismaClient>
1436
+ ) => Layer.merge(PrismaClient.layer(...args), Prisma.Default)
1437
+
1438
+ /**
1439
+ * Create a complete Prisma layer where PrismaClient options are computed via an Effect.
1440
+ * This is useful when you need to fetch configuration or create adapters using Effect.
1441
+ *
1442
+ * @example
1443
+ * // Get config from a service
1444
+ * const MainLayer = Prisma.layerEffect(
1445
+ * Effect.gen(function* () {
1446
+ * const config = yield* ConfigService
1447
+ * return { adapter: createAdapter(config.databaseUrl) }
1448
+ * })
1449
+ * )
1450
+ *
1451
+ * // With transaction options
1452
+ * const MainLayer = Prisma.layerEffect(
1453
+ * Effect.gen(function* () {
1454
+ * return {
1455
+ * adapter: myAdapter,
1456
+ * transactionOptions: { isolationLevel: "Serializable" }
1457
+ * }
1458
+ * })
1459
+ * )
1460
+ */
1461
+ static layerEffect = <R, E>(
1462
+ optionsEffect: Effect.Effect<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
1463
+ ) => Layer.merge(PrismaClient.layerEffect(optionsEffect), Prisma.Default)
1464
+
1465
+ }
1466
+
1467
+ // ============================================================================
1468
+ // Deprecated aliases for backward compatibility
1469
+ // ============================================================================
1470
+
1471
+ /**
1472
+ * @deprecated Use \`PrismaClient\` instead. Will be removed in next major version.
1473
+ */
1474
+ export const PrismaClientService = PrismaClient
1475
+
1476
+ /**
1477
+ * @deprecated Use \`Prisma\` instead. Will be removed in next major version.
1478
+ */
1479
+ export const PrismaService = Prisma
1480
+
1481
+ /**
1482
+ * @deprecated Use \`PrismaClient.layer()\` instead. Will be removed in next major version.
1483
+ */
1484
+ export const makePrismaLayer = PrismaClient.layer
1485
+
1486
+ /**
1487
+ * @deprecated Use \`PrismaClient.layerEffect()\` instead. Will be removed in next major version.
1488
+ */
1489
+ export const makePrismaLayerEffect = PrismaClient.layerEffect
1490
+
1491
+
1492
+ `;
1493
+ }