prisma-generator-effect 1.1.2-beta.1 → 1.1.3-beta.2
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.
- package/README.md +102 -0
- package/dist/index.js +202 -43
- package/package.json +1 -1
- package/dist/src/index.js +0 -1976
package/dist/src/index.js
DELETED
|
@@ -1,1976 +0,0 @@
|
|
|
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
|
-
}
|