prisma-generator-effect 1.0.0 → 1.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +998 -463
- package/package.json +11 -8
package/dist/index.js
CHANGED
|
@@ -4,9 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
8
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const generator_helper_1 = require("@prisma/generator-helper");
|
|
10
11
|
const header = `// This file was generated by prisma-generator-effect, do not edit manually.\n`;
|
|
11
12
|
// Utility function to convert PascalCase to camelCase
|
|
12
13
|
function toCamelCase(str) {
|
|
@@ -28,10 +29,7 @@ function toCamelCase(str) {
|
|
|
28
29
|
const configPath = options.generator.config.clientImportPath;
|
|
29
30
|
const clientImportPath = Array.isArray(configPath)
|
|
30
31
|
? configPath[0]
|
|
31
|
-
: configPath ?? "@prisma/client";
|
|
32
|
-
// Get datasource provider (e.g., "sqlite", "postgresql", "mysql", etc.)
|
|
33
|
-
const datasources = options.datasources;
|
|
34
|
-
const provider = datasources[0]?.provider; // Usually there's one datasource, but could be multiple
|
|
32
|
+
: (configPath ?? "@prisma/client");
|
|
35
33
|
// Custom error configuration: "path/to/module#ErrorClassName"
|
|
36
34
|
// Path is relative to schema.prisma, e.g., "./errors#PrismaError"
|
|
37
35
|
// The module must export:
|
|
@@ -47,10 +45,26 @@ function toCamelCase(str) {
|
|
|
47
45
|
const importExtConfigRaw = options.generator.config.importFileExtension;
|
|
48
46
|
const importFileExtension = Array.isArray(importExtConfigRaw)
|
|
49
47
|
? importExtConfigRaw[0]
|
|
50
|
-
: importExtConfigRaw ?? "";
|
|
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";
|
|
51
56
|
if (!outputDir) {
|
|
52
57
|
throw new Error("No output directory specified");
|
|
53
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";
|
|
54
68
|
// Helper to add file extension to a path if configured
|
|
55
69
|
const addExtension = (filePath) => {
|
|
56
70
|
if (!importFileExtension)
|
|
@@ -89,254 +103,425 @@ function toCamelCase(str) {
|
|
|
89
103
|
await promises_1.default.rm(outputDir, { recursive: true, force: true });
|
|
90
104
|
await promises_1.default.mkdir(outputDir, { recursive: true });
|
|
91
105
|
// Generate unified index file with PrismaService
|
|
92
|
-
await generateUnifiedService([...models], outputDir, clientImportPath, errorImportPath,
|
|
106
|
+
await generateUnifiedService([...models], outputDir, clientImportPath, errorImportPath, enableTelemetry, supportsManyAndReturn);
|
|
93
107
|
},
|
|
94
108
|
});
|
|
95
|
-
function generateRawSqlOperations(customError) {
|
|
109
|
+
function generateRawSqlOperations(customError, enableTelemetry) {
|
|
96
110
|
// With custom error, use mapError which maps to user's error type
|
|
97
111
|
// Without custom error, use mapError which maps to PrismaError union
|
|
98
112
|
const errorType = customError ? customError.className : "PrismaError";
|
|
113
|
+
const wrapTelemetry = (name, generatorFn) => enableTelemetry
|
|
114
|
+
? `Effect.fn("${name}")(${generatorFn})`
|
|
115
|
+
: `Effect.fnUntraced(${generatorFn})`;
|
|
99
116
|
return `
|
|
100
|
-
$executeRaw: (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
$executeRawUnsafe: (query
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
$queryRaw:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
$queryRawUnsafe:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
}`)},`;
|
|
131
148
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
+
];
|
|
139
174
|
return models
|
|
140
175
|
.map((model) => {
|
|
141
176
|
const modelName = model.name;
|
|
142
177
|
const modelNameCamel = toCamelCase(modelName);
|
|
143
|
-
|
|
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);
|
|
144
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`;
|
|
145
364
|
// Cast Promise results to ensure consistent typing across Prisma versions
|
|
146
|
-
// This handles Prisma 7's GlobalOmitConfig
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return ` as Promise<${fullType}>`;
|
|
365
|
+
// This handles Prisma 7's GlobalOmitConfig
|
|
366
|
+
// @deprecated just cast as any now
|
|
367
|
+
const promiseCast = () => {
|
|
368
|
+
return " as any";
|
|
151
369
|
};
|
|
152
370
|
// Aggregate/groupBy use complex internal types that need stronger casts
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return
|
|
156
|
-
};
|
|
157
|
-
const resultType = (op, nullable = false) => {
|
|
158
|
-
const baseType = `PrismaNamespace.Result<${delegate}, A, '${op}'>`;
|
|
159
|
-
return nullable ? `${baseType} | null` : baseType;
|
|
371
|
+
// @deprecated just cast as any now
|
|
372
|
+
const strongPromiseCast = () => {
|
|
373
|
+
return " as any";
|
|
160
374
|
};
|
|
161
375
|
// With custom error: all operations use single error type and mapError
|
|
162
376
|
// Without custom error: use per-operation error types and mappers
|
|
163
377
|
const errorType = (opErrorType) => customError ? customError.className : opErrorType;
|
|
164
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
|
|
165
384
|
return ` ${modelNameCamel}: {
|
|
166
|
-
findUnique:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
try: () => client.${modelNameCamel}.create(args as any)${promiseCast("create")},
|
|
222
|
-
catch: (error) => ${mapperFn("mapCreateError")}(error, "create", "${modelName}")
|
|
223
|
-
})
|
|
224
|
-
),
|
|
225
|
-
|
|
226
|
-
createMany: (
|
|
227
|
-
args?: PrismaNamespace.Args<${delegate}, 'createMany'>
|
|
228
|
-
): Effect.Effect<PrismaNamespace.BatchPayload, ${errorType("PrismaCreateError")}, PrismaClient> =>
|
|
229
|
-
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
230
|
-
Effect.tryPromise({
|
|
231
|
-
try: () => client.${modelNameCamel}.createMany(args as any),
|
|
232
|
-
catch: (error) => ${mapperFn("mapCreateError")}(error, "createMany", "${modelName}")
|
|
233
|
-
})
|
|
234
|
-
),${supportsManyAndReturn
|
|
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
|
|
235
440
|
? `
|
|
236
441
|
|
|
237
|
-
createManyAndReturn:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
})
|
|
245
|
-
),`
|
|
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
|
+
}`)},`
|
|
246
449
|
: ""}
|
|
247
450
|
|
|
248
|
-
delete:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
args: PrismaNamespace.Args<${delegate}, 'updateMany'>
|
|
280
|
-
): Effect.Effect<PrismaNamespace.BatchPayload, ${errorType("PrismaUpdateManyError")}, PrismaClient> =>
|
|
281
|
-
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
282
|
-
Effect.tryPromise({
|
|
283
|
-
try: () => client.${modelNameCamel}.updateMany(args as any),
|
|
284
|
-
catch: (error) => ${mapperFn("mapUpdateManyError")}(error, "updateMany", "${modelName}")
|
|
285
|
-
})
|
|
286
|
-
),${supportsManyAndReturn
|
|
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
|
|
287
482
|
? `
|
|
288
483
|
|
|
289
|
-
updateManyAndReturn:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
})
|
|
297
|
-
),`
|
|
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
|
+
}`)},`
|
|
298
491
|
: ""}
|
|
299
492
|
|
|
300
|
-
upsert:
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
})
|
|
308
|
-
),
|
|
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
|
+
}`)},
|
|
309
500
|
|
|
310
501
|
// Aggregation operations
|
|
311
|
-
count:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
335
|
-
Effect.tryPromise({
|
|
336
|
-
try: () => client.${modelNameCamel}.groupBy(args as any)${strongPromiseCast("groupBy")},
|
|
337
|
-
catch: (error) => ${mapperFn("mapFindError")}(error, "groupBy", "${modelName}")
|
|
338
|
-
})
|
|
339
|
-
)
|
|
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
|
+
}`)}
|
|
340
525
|
}`;
|
|
341
526
|
})
|
|
342
527
|
.join(",\n\n");
|
|
@@ -351,27 +536,48 @@ function parseErrorImportPath(errorImportPath) {
|
|
|
351
536
|
}
|
|
352
537
|
return { path, className };
|
|
353
538
|
}
|
|
354
|
-
async function generateUnifiedService(models, outputDir, clientImportPath, errorImportPath,
|
|
539
|
+
async function generateUnifiedService(models, outputDir, clientImportPath, errorImportPath, enableTelemetry, supportsManyAndReturn) {
|
|
355
540
|
const customError = parseErrorImportPath(errorImportPath);
|
|
356
|
-
const rawSqlOperations = generateRawSqlOperations(customError);
|
|
357
|
-
const
|
|
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);
|
|
358
545
|
// Generate different content based on whether custom error is configured
|
|
359
546
|
const serviceContent = customError
|
|
360
|
-
? generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelOperations)
|
|
361
|
-
: generateDefaultErrorService(clientImportPath, rawSqlOperations, modelOperations);
|
|
362
|
-
|
|
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
|
+
}
|
|
363
561
|
}
|
|
364
562
|
/**
|
|
365
563
|
* Generate service with custom user-provided error class.
|
|
366
564
|
* All operations use a single error type and a simple mapError function.
|
|
367
565
|
*/
|
|
368
|
-
function generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelOperations) {
|
|
566
|
+
function generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelTypeAliases, prismaInterface, modelOperations, enableTelemetry) {
|
|
567
|
+
const _errorType = customError.className;
|
|
369
568
|
return `${header}
|
|
370
|
-
import {
|
|
371
|
-
import {
|
|
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"
|
|
372
571
|
import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "${clientImportPath}"
|
|
373
572
|
import { ${customError.className}, mapPrismaError } from "${customError.path}"
|
|
374
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
|
+
|
|
375
581
|
// Symbol used to identify intentional rollbacks vs actual errors
|
|
376
582
|
const ROLLBACK = Symbol.for("prisma.effect.rollback")
|
|
377
583
|
|
|
@@ -390,59 +596,44 @@ type TransactionOptions = {
|
|
|
390
596
|
|
|
391
597
|
/**
|
|
392
598
|
* Context tag for the Prisma client instance.
|
|
393
|
-
* Holds the transaction client (tx) and root client.
|
|
394
599
|
*
|
|
395
600
|
* Use \`PrismaClient.layer()\` or \`PrismaClient.layerEffect()\` to create a layer.
|
|
396
601
|
*
|
|
397
602
|
* @example
|
|
398
|
-
* // Prisma 6 - all options are optional
|
|
399
|
-
* const layer = PrismaClient.layer({ datasourceUrl: "..." })
|
|
400
|
-
*
|
|
401
603
|
* // Prisma 7 - adapter or accelerateUrl is required
|
|
402
604
|
* const layer = PrismaClient.layer({ adapter: myAdapter })
|
|
403
605
|
*
|
|
404
606
|
* // With transaction options (Prisma uses these as defaults for $transaction)
|
|
405
607
|
* const layer = PrismaClient.layer({
|
|
406
|
-
* adapter: myAdapter,
|
|
608
|
+
* adapter: myAdapter,
|
|
407
609
|
* transactionOptions: { isolationLevel: "Serializable", timeout: 10000 }
|
|
408
610
|
* })
|
|
409
611
|
*/
|
|
410
|
-
export class PrismaClient extends
|
|
411
|
-
PrismaClient,
|
|
412
|
-
{
|
|
413
|
-
tx: BasePrismaClient | PrismaNamespace.TransactionClient
|
|
414
|
-
client: BasePrismaClient
|
|
415
|
-
}
|
|
416
|
-
>() {
|
|
612
|
+
export class PrismaClient extends ServiceMap.Service<PrismaClient, BasePrismaClient>()("PrismaClient") {
|
|
417
613
|
/**
|
|
418
614
|
* Create a PrismaClient layer with the given options.
|
|
419
615
|
* The client will be automatically disconnected when the layer scope ends.
|
|
420
616
|
*
|
|
421
617
|
* Pass options directly - the signature matches PrismaClient's constructor.
|
|
422
|
-
* Prisma
|
|
423
|
-
* Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
|
|
618
|
+
* Prisma 7: requires either \`adapter\`
|
|
424
619
|
*
|
|
425
620
|
* @example
|
|
426
|
-
* // Prisma 6
|
|
427
|
-
* const layer = PrismaClient.layer({ datasourceUrl: process.env.DATABASE_URL })
|
|
428
|
-
*
|
|
429
621
|
* // Prisma 7 with adapter
|
|
430
622
|
* const layer = PrismaClient.layer({ adapter: myAdapter })
|
|
431
|
-
*
|
|
432
623
|
* // With transaction options
|
|
433
624
|
* const layer = PrismaClient.layer({
|
|
434
625
|
* adapter: myAdapter,
|
|
435
626
|
* transactionOptions: { isolationLevel: "Serializable" }
|
|
436
627
|
* })
|
|
437
628
|
*/
|
|
438
|
-
static layer
|
|
629
|
+
static layer: (
|
|
439
630
|
...args: ConstructorParameters<typeof BasePrismaClient>
|
|
440
|
-
) => Layer.
|
|
631
|
+
) => Layer.Layer<PrismaClient, never, never> = (...args) => Layer.effect(
|
|
441
632
|
PrismaClient,
|
|
442
633
|
Effect.gen(function* () {
|
|
443
|
-
const prisma = new BasePrismaClient(...args)
|
|
634
|
+
const prisma: BasePrismaClient = new BasePrismaClient(...args)
|
|
444
635
|
yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
|
|
445
|
-
return
|
|
636
|
+
return prisma
|
|
446
637
|
})
|
|
447
638
|
)
|
|
448
639
|
|
|
@@ -470,25 +661,78 @@ export class PrismaClient extends Context.Tag("PrismaClient")<
|
|
|
470
661
|
* })
|
|
471
662
|
* )
|
|
472
663
|
*/
|
|
473
|
-
static layerEffect
|
|
474
|
-
optionsEffect:
|
|
475
|
-
) => Layer.
|
|
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(
|
|
476
667
|
PrismaClient,
|
|
477
668
|
Effect.gen(function* () {
|
|
478
|
-
const options = yield* optionsEffect
|
|
479
|
-
const prisma = new BasePrismaClient(options)
|
|
669
|
+
const options: ConstructorParameters<typeof BasePrismaClient>[0] = yield* optionsEffect
|
|
670
|
+
const prisma: BasePrismaClient = new BasePrismaClient(options)
|
|
480
671
|
yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
|
|
481
|
-
return
|
|
672
|
+
return prisma
|
|
482
673
|
})
|
|
483
674
|
)
|
|
484
675
|
}
|
|
485
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
|
+
|
|
486
684
|
// Re-export the custom error type for convenience
|
|
487
685
|
export { ${customError.className} }
|
|
488
686
|
|
|
489
687
|
// Use the user-provided error mapper
|
|
490
688
|
const mapError = mapPrismaError
|
|
491
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
|
+
|
|
492
736
|
/**
|
|
493
737
|
* Internal helper to begin a callback-free interactive transaction.
|
|
494
738
|
* Returns a transaction client with $commit and $rollback methods.
|
|
@@ -501,8 +745,8 @@ const $begin = (
|
|
|
501
745
|
timeout?: number
|
|
502
746
|
isolationLevel?: PrismaNamespace.TransactionIsolationLevel
|
|
503
747
|
}
|
|
504
|
-
):
|
|
505
|
-
Effect.
|
|
748
|
+
): EffectType<FlatTransactionClient, ${customError.className}> =>
|
|
749
|
+
Effect.callback<FlatTransactionClient, ${customError.className}>((resume) => {
|
|
506
750
|
let setTxClient: (txClient: PrismaNamespace.TransactionClient) => void
|
|
507
751
|
let commit: () => void
|
|
508
752
|
let rollback: () => void
|
|
@@ -554,15 +798,14 @@ const $begin = (
|
|
|
554
798
|
* return user
|
|
555
799
|
* })
|
|
556
800
|
*
|
|
557
|
-
* // Run with
|
|
558
|
-
* Effect.runPromise(program.pipe(Effect.provide(Prisma.Live)))
|
|
559
|
-
*
|
|
560
|
-
* // Or with custom options
|
|
801
|
+
* // Run with layer
|
|
561
802
|
* Effect.runPromise(program.pipe(Effect.provide(Prisma.layer({ datasourceUrl: "..." }))))
|
|
562
803
|
*/
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
804
|
+
const makePrismaService = Effect.gen(function* () {
|
|
805
|
+
const client = yield* PrismaClient;
|
|
806
|
+
|
|
807
|
+
const prismaService: IPrismaService = {
|
|
808
|
+
client,
|
|
566
809
|
/**
|
|
567
810
|
* Execute an effect within a database transaction.
|
|
568
811
|
* All operations within the effect will be atomic - they either all succeed or all fail.
|
|
@@ -570,7 +813,8 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
570
813
|
* This implementation uses a callback-free transaction pattern that keeps the effect
|
|
571
814
|
* running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
|
|
572
815
|
*
|
|
573
|
-
*
|
|
816
|
+
* Uses default transaction options from PrismaClient constructor.
|
|
817
|
+
* For custom options, use \`$transactionWith\`.
|
|
574
818
|
*
|
|
575
819
|
* @example
|
|
576
820
|
* const result = yield* prisma.$transaction(
|
|
@@ -580,47 +824,101 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
580
824
|
* return user
|
|
581
825
|
* })
|
|
582
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.
|
|
583
877
|
*
|
|
584
878
|
* @example
|
|
585
879
|
* // Override default isolation level for this transaction
|
|
586
|
-
* const result = yield* prisma.$
|
|
587
|
-
*
|
|
588
|
-
* })
|
|
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
|
+
* )
|
|
589
888
|
*/
|
|
590
|
-
$
|
|
591
|
-
|
|
592
|
-
options?: TransactionOptions
|
|
593
|
-
) =>
|
|
594
|
-
Effect.flatMap(
|
|
595
|
-
PrismaClient,
|
|
596
|
-
({ client, tx }): Effect.Effect<A, E | ${customError.className}, R> => {
|
|
597
|
-
// If we're already in a transaction, just run the effect directly (no nesting)
|
|
598
|
-
const isRootClient = "$transaction" in tx
|
|
599
|
-
if (!isRootClient) {
|
|
600
|
-
return effect
|
|
601
|
-
}
|
|
889
|
+
$transactionWith: ${enableTelemetry ? 'Effect.fn("Prisma.$transactionWith")' : "Effect.fnUntraced"}(function* (effect, options) {
|
|
890
|
+
const currentTx = yield* Effect.serviceOption(PrismaTransactionClientService);
|
|
602
891
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
return
|
|
606
|
-
// Acquire: begin a new transaction
|
|
607
|
-
// Prisma merges per-call options with constructor defaults internally
|
|
608
|
-
$begin(client, options),
|
|
609
|
-
|
|
610
|
-
// Use: run the effect with the transaction client injected
|
|
611
|
-
(txClient) =>
|
|
612
|
-
effect.pipe(
|
|
613
|
-
Effect.provideService(PrismaClient, { tx: txClient, client })
|
|
614
|
-
),
|
|
615
|
-
|
|
616
|
-
// Release: commit on success, rollback on failure/interruption
|
|
617
|
-
(txClient, exit) =>
|
|
618
|
-
Exit.isSuccess(exit)
|
|
619
|
-
? Effect.promise(() => txClient.$commit())
|
|
620
|
-
: Effect.promise(() => txClient.$rollback())
|
|
621
|
-
)
|
|
892
|
+
// If already in a transaction, just run the effect
|
|
893
|
+
if (Option.isSome(currentTx)) {
|
|
894
|
+
return yield* effect;
|
|
622
895
|
}
|
|
623
|
-
|
|
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
|
+
}),
|
|
624
922
|
|
|
625
923
|
/**
|
|
626
924
|
* Execute an effect in a NEW transaction, even if already inside a transaction.
|
|
@@ -634,6 +932,9 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
634
932
|
* ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
|
|
635
933
|
* or vice versa. Use carefully to avoid data inconsistencies.
|
|
636
934
|
*
|
|
935
|
+
* Uses default transaction options from PrismaClient constructor.
|
|
936
|
+
* For custom options, use \`$isolatedTransactionWith\`.
|
|
937
|
+
*
|
|
637
938
|
* @example
|
|
638
939
|
* yield* prisma.$transaction(
|
|
639
940
|
* Effect.gen(function* () {
|
|
@@ -646,46 +947,99 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
646
947
|
* })
|
|
647
948
|
* )
|
|
648
949
|
*/
|
|
649
|
-
$isolatedTransaction:
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
+
}),
|
|
670
1018
|
${rawSqlOperations}
|
|
671
1019
|
|
|
672
1020
|
${modelOperations}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
+
|
|
676
1034
|
/**
|
|
677
1035
|
* Create a complete Prisma layer with the given PrismaClient options.
|
|
678
1036
|
* This is the recommended way to create a Prisma layer - it bundles both
|
|
679
1037
|
* PrismaClient and Prisma service together.
|
|
680
1038
|
*
|
|
681
1039
|
* Pass options directly - the signature matches PrismaClient's constructor.
|
|
682
|
-
* Prisma 6: all options are optional
|
|
683
1040
|
* Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
|
|
684
1041
|
*
|
|
685
1042
|
* @example
|
|
686
|
-
* // Prisma 6
|
|
687
|
-
* const MainLayer = Prisma.layer({ datasourceUrl: process.env.DATABASE_URL })
|
|
688
|
-
*
|
|
689
1043
|
* // Prisma 7 with adapter
|
|
690
1044
|
* const MainLayer = Prisma.layer({ adapter: myAdapter })
|
|
691
1045
|
*
|
|
@@ -698,9 +1052,11 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
698
1052
|
* // Use it
|
|
699
1053
|
* Effect.runPromise(program.pipe(Effect.provide(MainLayer)))
|
|
700
1054
|
*/
|
|
701
|
-
static layer
|
|
1055
|
+
static layer: (
|
|
702
1056
|
...args: ConstructorParameters<typeof BasePrismaClient>
|
|
703
|
-
) => Layer.
|
|
1057
|
+
) => Layer.Layer<Prisma | PrismaClient, never, never> = (...args) => this.Default.pipe(
|
|
1058
|
+
Layer.provideMerge(PrismaClient.layer(...args))
|
|
1059
|
+
);
|
|
704
1060
|
|
|
705
1061
|
/**
|
|
706
1062
|
* Create a complete Prisma layer where PrismaClient options are computed via an Effect.
|
|
@@ -725,23 +1081,42 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
725
1081
|
* })
|
|
726
1082
|
* )
|
|
727
1083
|
*/
|
|
728
|
-
static layerEffect
|
|
729
|
-
optionsEffect:
|
|
730
|
-
) => Layer.
|
|
731
|
-
|
|
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
|
+
);
|
|
732
1089
|
}
|
|
1090
|
+
|
|
733
1091
|
`;
|
|
734
1092
|
}
|
|
735
1093
|
/**
|
|
736
1094
|
* Generate service with default tagged error classes.
|
|
737
1095
|
* Operations have per-operation error types for fine-grained error handling.
|
|
738
1096
|
*/
|
|
739
|
-
function generateDefaultErrorService(clientImportPath, rawSqlOperations, modelOperations) {
|
|
1097
|
+
function generateDefaultErrorService(clientImportPath, rawSqlOperations, modelTypeAliases, prismaInterface, modelOperations, enableTelemetry) {
|
|
1098
|
+
const _errorType = "PrismaError";
|
|
740
1099
|
return `${header}
|
|
741
|
-
import {
|
|
742
|
-
import {
|
|
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"
|
|
743
1102
|
import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "${clientImportPath}"
|
|
744
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
|
+
|
|
745
1120
|
// Symbol used to identify intentional rollbacks vs actual errors
|
|
746
1121
|
const ROLLBACK = Symbol.for("prisma.effect.rollback")
|
|
747
1122
|
|
|
@@ -760,42 +1135,28 @@ type TransactionOptions = {
|
|
|
760
1135
|
|
|
761
1136
|
/**
|
|
762
1137
|
* Context tag for the Prisma client instance.
|
|
763
|
-
* Holds the transaction client (tx) and root client.
|
|
764
1138
|
*
|
|
765
1139
|
* Use \`PrismaClient.layer()\` or \`PrismaClient.layerEffect()\` to create a layer.
|
|
766
1140
|
*
|
|
767
1141
|
* @example
|
|
768
|
-
* // Prisma 6 - all options are optional
|
|
769
|
-
* const layer = PrismaClient.layer({ datasourceUrl: "..." })
|
|
770
|
-
*
|
|
771
1142
|
* // Prisma 7 - adapter or accelerateUrl is required
|
|
772
1143
|
* const layer = PrismaClient.layer({ adapter: myAdapter })
|
|
773
1144
|
*
|
|
774
1145
|
* // With transaction options (Prisma uses these as defaults for $transaction)
|
|
775
1146
|
* const layer = PrismaClient.layer({
|
|
776
|
-
* adapter: myAdapter,
|
|
1147
|
+
* adapter: myAdapter,
|
|
777
1148
|
* transactionOptions: { isolationLevel: "Serializable", timeout: 10000 }
|
|
778
1149
|
* })
|
|
779
1150
|
*/
|
|
780
|
-
export class PrismaClient extends
|
|
781
|
-
PrismaClient,
|
|
782
|
-
{
|
|
783
|
-
tx: BasePrismaClient | PrismaNamespace.TransactionClient
|
|
784
|
-
client: BasePrismaClient
|
|
785
|
-
}
|
|
786
|
-
>() {
|
|
1151
|
+
export class PrismaClient extends ServiceMap.Service<PrismaClient, BasePrismaClient>()("PrismaClient") {
|
|
787
1152
|
/**
|
|
788
1153
|
* Create a PrismaClient layer with the given options.
|
|
789
1154
|
* The client will be automatically disconnected when the layer scope ends.
|
|
790
1155
|
*
|
|
791
1156
|
* Pass options directly - the signature matches PrismaClient's constructor.
|
|
792
|
-
* Prisma 6: all options are optional
|
|
793
1157
|
* Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
|
|
794
1158
|
*
|
|
795
1159
|
* @example
|
|
796
|
-
* // Prisma 6
|
|
797
|
-
* const layer = PrismaClient.layer({ datasourceUrl: process.env.DATABASE_URL })
|
|
798
|
-
*
|
|
799
1160
|
* // Prisma 7 with adapter
|
|
800
1161
|
* const layer = PrismaClient.layer({ adapter: myAdapter })
|
|
801
1162
|
*
|
|
@@ -805,14 +1166,14 @@ export class PrismaClient extends Context.Tag("PrismaClient")<
|
|
|
805
1166
|
* transactionOptions: { isolationLevel: "Serializable" }
|
|
806
1167
|
* })
|
|
807
1168
|
*/
|
|
808
|
-
static layer
|
|
1169
|
+
static layer: (
|
|
809
1170
|
...args: ConstructorParameters<typeof BasePrismaClient>
|
|
810
|
-
) => Layer.
|
|
1171
|
+
) => Layer.Layer<PrismaClient, never, never> = (...args) => Layer.effect(
|
|
811
1172
|
PrismaClient,
|
|
812
1173
|
Effect.gen(function* () {
|
|
813
|
-
const prisma = new BasePrismaClient(...args)
|
|
1174
|
+
const prisma: BasePrismaClient = new BasePrismaClient(...args)
|
|
814
1175
|
yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
|
|
815
|
-
return
|
|
1176
|
+
return prisma
|
|
816
1177
|
})
|
|
817
1178
|
)
|
|
818
1179
|
|
|
@@ -840,19 +1201,26 @@ export class PrismaClient extends Context.Tag("PrismaClient")<
|
|
|
840
1201
|
* })
|
|
841
1202
|
* )
|
|
842
1203
|
*/
|
|
843
|
-
static layerEffect
|
|
844
|
-
optionsEffect:
|
|
845
|
-
) => Layer.
|
|
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(
|
|
846
1207
|
PrismaClient,
|
|
847
1208
|
Effect.gen(function* () {
|
|
848
|
-
const options = yield* optionsEffect
|
|
849
|
-
const prisma = new BasePrismaClient(options)
|
|
1209
|
+
const options: ConstructorParameters<typeof BasePrismaClient>[0] = yield* optionsEffect
|
|
1210
|
+
const prisma: BasePrismaClient = new BasePrismaClient(options)
|
|
850
1211
|
yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
|
|
851
|
-
return
|
|
1212
|
+
return prisma
|
|
852
1213
|
})
|
|
853
1214
|
)
|
|
854
1215
|
}
|
|
855
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
|
+
|
|
856
1224
|
export class PrismaUniqueConstraintError extends Data.TaggedError("PrismaUniqueConstraintError")<{
|
|
857
1225
|
cause: PrismaNamespace.PrismaClientKnownRequestError
|
|
858
1226
|
operation: string
|
|
@@ -998,8 +1366,9 @@ export type PrismaError =
|
|
|
998
1366
|
|
|
999
1367
|
// Generic mapper for raw operations and fallback
|
|
1000
1368
|
const mapError = (error: unknown, operation: string, model: string): PrismaError => {
|
|
1001
|
-
if (error
|
|
1002
|
-
|
|
1369
|
+
if (isPrismaClientKnownRequestError(error)) {
|
|
1370
|
+
const knownError = error;
|
|
1371
|
+
switch (knownError.code) {
|
|
1003
1372
|
case "P2000":
|
|
1004
1373
|
return new PrismaValueTooLongError({ cause: error, operation, model });
|
|
1005
1374
|
case "P2002":
|
|
@@ -1038,8 +1407,9 @@ const mapError = (error: unknown, operation: string, model: string): PrismaError
|
|
|
1038
1407
|
|
|
1039
1408
|
// Create, Upsert
|
|
1040
1409
|
const mapCreateError = (error: unknown, operation: string, model: string): PrismaCreateError => {
|
|
1041
|
-
if (error
|
|
1042
|
-
|
|
1410
|
+
if (isPrismaClientKnownRequestError(error)) {
|
|
1411
|
+
const knownError = error;
|
|
1412
|
+
switch (knownError.code) {
|
|
1043
1413
|
case "P2000":
|
|
1044
1414
|
return new PrismaValueTooLongError({ cause: error, operation, model });
|
|
1045
1415
|
case "P2002":
|
|
@@ -1071,8 +1441,9 @@ const mapCreateError = (error: unknown, operation: string, model: string): Prism
|
|
|
1071
1441
|
|
|
1072
1442
|
// Update
|
|
1073
1443
|
const mapUpdateError = (error: unknown, operation: string, model: string): PrismaUpdateError => {
|
|
1074
|
-
if (error
|
|
1075
|
-
|
|
1444
|
+
if (isPrismaClientKnownRequestError(error)) {
|
|
1445
|
+
const knownError = error;
|
|
1446
|
+
switch (knownError.code) {
|
|
1076
1447
|
case "P2000":
|
|
1077
1448
|
return new PrismaValueTooLongError({ cause: error, operation, model });
|
|
1078
1449
|
case "P2002":
|
|
@@ -1108,8 +1479,9 @@ const mapUpdateError = (error: unknown, operation: string, model: string): Prism
|
|
|
1108
1479
|
|
|
1109
1480
|
// Delete
|
|
1110
1481
|
const mapDeleteError = (error: unknown, operation: string, model: string): PrismaDeleteError => {
|
|
1111
|
-
if (error
|
|
1112
|
-
|
|
1482
|
+
if (isPrismaClientKnownRequestError(error)) {
|
|
1483
|
+
const knownError = error;
|
|
1484
|
+
switch (knownError.code) {
|
|
1113
1485
|
case "P2003":
|
|
1114
1486
|
return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
|
|
1115
1487
|
case "P2014":
|
|
@@ -1127,8 +1499,9 @@ const mapDeleteError = (error: unknown, operation: string, model: string): Prism
|
|
|
1127
1499
|
|
|
1128
1500
|
// FindOrThrow
|
|
1129
1501
|
const mapFindOrThrowError = (error: unknown, operation: string, model: string): PrismaFindOrThrowError => {
|
|
1130
|
-
if (error
|
|
1131
|
-
|
|
1502
|
+
if (isPrismaClientKnownRequestError(error)) {
|
|
1503
|
+
const knownError = error;
|
|
1504
|
+
switch (knownError.code) {
|
|
1132
1505
|
case "P2024":
|
|
1133
1506
|
return new PrismaConnectionError({ cause: error, operation, model });
|
|
1134
1507
|
case "P2025":
|
|
@@ -1140,8 +1513,9 @@ const mapFindOrThrowError = (error: unknown, operation: string, model: string):
|
|
|
1140
1513
|
|
|
1141
1514
|
// Find
|
|
1142
1515
|
const mapFindError = (error: unknown, operation: string, model: string): PrismaFindError => {
|
|
1143
|
-
if (error
|
|
1144
|
-
|
|
1516
|
+
if (isPrismaClientKnownRequestError(error)) {
|
|
1517
|
+
const knownError = error;
|
|
1518
|
+
switch (knownError.code) {
|
|
1145
1519
|
case "P2024":
|
|
1146
1520
|
return new PrismaConnectionError({ cause: error, operation, model });
|
|
1147
1521
|
}
|
|
@@ -1151,8 +1525,9 @@ const mapFindError = (error: unknown, operation: string, model: string): PrismaF
|
|
|
1151
1525
|
|
|
1152
1526
|
// DeleteMany
|
|
1153
1527
|
const mapDeleteManyError = (error: unknown, operation: string, model: string): PrismaDeleteManyError => {
|
|
1154
|
-
if (error
|
|
1155
|
-
|
|
1528
|
+
if (isPrismaClientKnownRequestError(error)) {
|
|
1529
|
+
const knownError = error;
|
|
1530
|
+
switch (knownError.code) {
|
|
1156
1531
|
case "P2003":
|
|
1157
1532
|
return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
|
|
1158
1533
|
case "P2014":
|
|
@@ -1168,8 +1543,9 @@ const mapDeleteManyError = (error: unknown, operation: string, model: string): P
|
|
|
1168
1543
|
|
|
1169
1544
|
// UpdateMany
|
|
1170
1545
|
const mapUpdateManyError = (error: unknown, operation: string, model: string): PrismaUpdateManyError => {
|
|
1171
|
-
if (error
|
|
1172
|
-
|
|
1546
|
+
if (isPrismaClientKnownRequestError(error)) {
|
|
1547
|
+
const knownError = error;
|
|
1548
|
+
switch (knownError.code) {
|
|
1173
1549
|
case "P2000":
|
|
1174
1550
|
return new PrismaValueTooLongError({ cause: error, operation, model });
|
|
1175
1551
|
case "P2002":
|
|
@@ -1196,6 +1572,52 @@ const mapUpdateManyError = (error: unknown, operation: string, model: string): P
|
|
|
1196
1572
|
throw error;
|
|
1197
1573
|
}
|
|
1198
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
|
+
|
|
1199
1621
|
/**
|
|
1200
1622
|
* Internal helper to begin a callback-free interactive transaction.
|
|
1201
1623
|
* Returns a transaction client with $commit and $rollback methods.
|
|
@@ -1208,8 +1630,8 @@ const $begin = (
|
|
|
1208
1630
|
timeout?: number
|
|
1209
1631
|
isolationLevel?: PrismaNamespace.TransactionIsolationLevel
|
|
1210
1632
|
}
|
|
1211
|
-
):
|
|
1212
|
-
Effect.
|
|
1633
|
+
): EffectType<FlatTransactionClient, PrismaError> =>
|
|
1634
|
+
Effect.callback<FlatTransactionClient, PrismaError>((resume) => {
|
|
1213
1635
|
let setTxClient: (txClient: PrismaNamespace.TransactionClient) => void
|
|
1214
1636
|
let commit: () => void
|
|
1215
1637
|
let rollback: () => void
|
|
@@ -1261,15 +1683,14 @@ const $begin = (
|
|
|
1261
1683
|
* return user
|
|
1262
1684
|
* })
|
|
1263
1685
|
*
|
|
1264
|
-
* // Run with
|
|
1265
|
-
* Effect.runPromise(program.pipe(Effect.provide(Prisma.Live)))
|
|
1266
|
-
*
|
|
1267
|
-
* // Or with custom options
|
|
1686
|
+
* // Run with layer
|
|
1268
1687
|
* Effect.runPromise(program.pipe(Effect.provide(Prisma.layer({ datasourceUrl: "..." }))))
|
|
1269
1688
|
*/
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1689
|
+
const makePrismaService = Effect.gen(function* () {
|
|
1690
|
+
const client = yield* PrismaClient;
|
|
1691
|
+
|
|
1692
|
+
const prismaService: IPrismaService = {
|
|
1693
|
+
client,
|
|
1273
1694
|
/**
|
|
1274
1695
|
* Execute an effect within a database transaction.
|
|
1275
1696
|
* All operations within the effect will be atomic - they either all succeed or all fail.
|
|
@@ -1277,7 +1698,8 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
1277
1698
|
* This implementation uses a callback-free transaction pattern that keeps the effect
|
|
1278
1699
|
* running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
|
|
1279
1700
|
*
|
|
1280
|
-
*
|
|
1701
|
+
* Uses default transaction options from PrismaClient constructor.
|
|
1702
|
+
* For custom options, use \`$transactionWith\`.
|
|
1281
1703
|
*
|
|
1282
1704
|
* @example
|
|
1283
1705
|
* const result = yield* prisma.$transaction(
|
|
@@ -1287,47 +1709,100 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
1287
1709
|
* return user
|
|
1288
1710
|
* })
|
|
1289
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.
|
|
1290
1761
|
*
|
|
1291
1762
|
* @example
|
|
1292
1763
|
* // Override default isolation level for this transaction
|
|
1293
|
-
* const result = yield* prisma.$
|
|
1294
|
-
*
|
|
1295
|
-
* })
|
|
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
|
+
* )
|
|
1296
1772
|
*/
|
|
1297
|
-
$
|
|
1298
|
-
|
|
1299
|
-
options?: TransactionOptions
|
|
1300
|
-
) =>
|
|
1301
|
-
Effect.flatMap(
|
|
1302
|
-
PrismaClient,
|
|
1303
|
-
({ client, tx }): Effect.Effect<A, E | PrismaError, R> => {
|
|
1304
|
-
// If we're already in a transaction, just run the effect directly (no nesting)
|
|
1305
|
-
const isRootClient = "$transaction" in tx
|
|
1306
|
-
if (!isRootClient) {
|
|
1307
|
-
return effect
|
|
1308
|
-
}
|
|
1773
|
+
$transactionWith: ${enableTelemetry ? 'Effect.fn("Prisma.$transactionWith")' : "Effect.fnUntraced"}(function* (effect, options) {
|
|
1774
|
+
const currentTx = yield* Effect.serviceOption(PrismaTransactionClientService);
|
|
1309
1775
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
return
|
|
1313
|
-
// Acquire: begin a new transaction
|
|
1314
|
-
// Prisma merges per-call options with constructor defaults internally
|
|
1315
|
-
$begin(client, options),
|
|
1316
|
-
|
|
1317
|
-
// Use: run the effect with the transaction client injected
|
|
1318
|
-
(txClient) =>
|
|
1319
|
-
effect.pipe(
|
|
1320
|
-
Effect.provideService(PrismaClient, { tx: txClient, client })
|
|
1321
|
-
),
|
|
1322
|
-
|
|
1323
|
-
// Release: commit on success, rollback on failure/interruption
|
|
1324
|
-
(txClient, exit) =>
|
|
1325
|
-
Exit.isSuccess(exit)
|
|
1326
|
-
? Effect.promise(() => txClient.$commit())
|
|
1327
|
-
: Effect.promise(() => txClient.$rollback())
|
|
1328
|
-
)
|
|
1776
|
+
// If already in a transaction, just run the effect
|
|
1777
|
+
if (Option.isSome(currentTx)) {
|
|
1778
|
+
return yield* effect;
|
|
1329
1779
|
}
|
|
1330
|
-
|
|
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
|
+
}),
|
|
1331
1806
|
|
|
1332
1807
|
/**
|
|
1333
1808
|
* Execute an effect in a NEW transaction, even if already inside a transaction.
|
|
@@ -1341,6 +1816,9 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
1341
1816
|
* ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
|
|
1342
1817
|
* or vice versa. Use carefully to avoid data inconsistencies.
|
|
1343
1818
|
*
|
|
1819
|
+
* Uses default transaction options from PrismaClient constructor.
|
|
1820
|
+
* For custom options, use \`$isolatedTransactionWith\`.
|
|
1821
|
+
*
|
|
1344
1822
|
* @example
|
|
1345
1823
|
* yield* prisma.$transaction(
|
|
1346
1824
|
* Effect.gen(function* () {
|
|
@@ -1353,46 +1831,99 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
1353
1831
|
* })
|
|
1354
1832
|
* )
|
|
1355
1833
|
*/
|
|
1356
|
-
$isolatedTransaction:
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
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
|
+
}),
|
|
1377
1902
|
${rawSqlOperations}
|
|
1378
1903
|
|
|
1379
1904
|
${modelOperations}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
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
|
+
|
|
1383
1918
|
/**
|
|
1384
1919
|
* Create a complete Prisma layer with the given PrismaClient options.
|
|
1385
1920
|
* This is the recommended way to create a Prisma layer - it bundles both
|
|
1386
1921
|
* PrismaClient and Prisma service together.
|
|
1387
1922
|
*
|
|
1388
1923
|
* Pass options directly - the signature matches PrismaClient's constructor.
|
|
1389
|
-
* Prisma 6: all options are optional
|
|
1390
1924
|
* Prisma 7: requires either \`adapter\` or \`accelerateUrl\`
|
|
1391
1925
|
*
|
|
1392
1926
|
* @example
|
|
1393
|
-
* // Prisma 6
|
|
1394
|
-
* const MainLayer = Prisma.layer({ datasourceUrl: process.env.DATABASE_URL })
|
|
1395
|
-
*
|
|
1396
1927
|
* // Prisma 7 with adapter
|
|
1397
1928
|
* const MainLayer = Prisma.layer({ adapter: myAdapter })
|
|
1398
1929
|
*
|
|
@@ -1405,9 +1936,11 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
1405
1936
|
* // Use it
|
|
1406
1937
|
* Effect.runPromise(program.pipe(Effect.provide(MainLayer)))
|
|
1407
1938
|
*/
|
|
1408
|
-
static layer
|
|
1939
|
+
static layer: (
|
|
1409
1940
|
...args: ConstructorParameters<typeof BasePrismaClient>
|
|
1410
|
-
) => Layer.
|
|
1941
|
+
) => Layer.Layer<Prisma | PrismaClient, never, never> = (...args) => this.Default.pipe(
|
|
1942
|
+
Layer.provideMerge(PrismaClient.layer(...args))
|
|
1943
|
+
);
|
|
1411
1944
|
|
|
1412
1945
|
/**
|
|
1413
1946
|
* Create a complete Prisma layer where PrismaClient options are computed via an Effect.
|
|
@@ -1432,10 +1965,12 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
1432
1965
|
* })
|
|
1433
1966
|
* )
|
|
1434
1967
|
*/
|
|
1435
|
-
static layerEffect
|
|
1436
|
-
optionsEffect:
|
|
1437
|
-
) => Layer.
|
|
1438
|
-
|
|
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
|
+
);
|
|
1439
1973
|
}
|
|
1974
|
+
|
|
1440
1975
|
`;
|
|
1441
1976
|
}
|