@riordanpawley/effect-prisma-generator 0.5.0 → 0.5.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 +40 -0
- package/dist/index.js +296 -50
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -343,6 +343,46 @@ const program = Effect.gen(function* () {
|
|
|
343
343
|
});
|
|
344
344
|
```
|
|
345
345
|
|
|
346
|
+
#### Custom Transaction Options
|
|
347
|
+
|
|
348
|
+
For transactions that need custom options (isolation level, timeout, etc.), use `$transactionWith`:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
// Custom isolation level
|
|
352
|
+
yield* prisma.$transactionWith(
|
|
353
|
+
Effect.gen(function* () {
|
|
354
|
+
const user = yield* prisma.user.create({ data: { name: "Alice" } });
|
|
355
|
+
return user;
|
|
356
|
+
}),
|
|
357
|
+
{ isolationLevel: "Serializable", timeout: 10000 }
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
// Point-free style for cleaner composition
|
|
361
|
+
import { pipe } from "effect";
|
|
362
|
+
|
|
363
|
+
const myEffect = Effect.gen(function* () {
|
|
364
|
+
const prisma = yield* Prisma;
|
|
365
|
+
return yield* prisma.user.findMany();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Clean, functional composition!
|
|
369
|
+
pipe(
|
|
370
|
+
myEffect,
|
|
371
|
+
prisma.$transaction // No options? Use $transaction directly!
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
// With options, use $transactionWith
|
|
375
|
+
const withSerializable = (eff: Effect.Effect<any, any, any>) =>
|
|
376
|
+
prisma.$transactionWith(eff, { isolationLevel: "Serializable" });
|
|
377
|
+
|
|
378
|
+
pipe(myEffect, withSerializable);
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Available transaction options:
|
|
382
|
+
- `isolationLevel`: `"ReadUncommitted" | "ReadCommitted" | "RepeatableRead" | "Serializable"`
|
|
383
|
+
- `maxWait`: Maximum time (ms) Prisma Client will wait to acquire a transaction
|
|
384
|
+
- `timeout`: Maximum time (ms) the transaction can run before being canceled
|
|
385
|
+
|
|
346
386
|
#### Transaction Rollback Behavior
|
|
347
387
|
|
|
348
388
|
**Any uncaught error in the Effect error channel triggers a rollback:**
|
package/dist/index.js
CHANGED
|
@@ -126,13 +126,42 @@ function generateRawSqlOperations(customError) {
|
|
|
126
126
|
})
|
|
127
127
|
),`;
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Generate type aliases for a model to reduce redundant type computation.
|
|
131
|
+
* TypeScript performance is significantly improved when complex types are
|
|
132
|
+
* computed once and reused via aliases rather than inline.
|
|
133
|
+
*/
|
|
134
|
+
function generateModelTypeAliases(models) {
|
|
135
|
+
return models
|
|
136
|
+
.map((model) => {
|
|
137
|
+
const modelName = model.name;
|
|
138
|
+
const modelNameCamel = toCamelCase(modelName);
|
|
139
|
+
// Operations that need Args/Result type aliases
|
|
140
|
+
const operations = [
|
|
141
|
+
'findUnique', 'findUniqueOrThrow', 'findFirst', 'findFirstOrThrow',
|
|
142
|
+
'findMany', 'create', 'createMany', 'createManyAndReturn',
|
|
143
|
+
'delete', 'update', 'deleteMany', 'updateMany', 'updateManyAndReturn',
|
|
144
|
+
'upsert', 'count', 'aggregate', 'groupBy'
|
|
145
|
+
];
|
|
146
|
+
const argsAliases = operations
|
|
147
|
+
.map(op => `type ${modelName}${capitalize(op)}Args = PrismaNamespace.Args<BasePrismaClient['${modelNameCamel}'], '${op}'>`)
|
|
148
|
+
.join('\n');
|
|
149
|
+
return argsAliases;
|
|
150
|
+
})
|
|
151
|
+
.join('\n\n');
|
|
152
|
+
}
|
|
153
|
+
function capitalize(str) {
|
|
154
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
155
|
+
}
|
|
129
156
|
function generateModelOperations(models, customError) {
|
|
130
157
|
return models
|
|
131
158
|
.map((model) => {
|
|
132
159
|
const modelName = model.name;
|
|
133
160
|
const modelNameCamel = toCamelCase(modelName);
|
|
134
|
-
//
|
|
161
|
+
// Use pre-computed type alias for delegate
|
|
135
162
|
const delegate = `BasePrismaClient['${modelNameCamel}']`;
|
|
163
|
+
// Helper to get pre-computed Args type alias
|
|
164
|
+
const argsType = (op) => `${modelName}${capitalize(op)}Args`;
|
|
136
165
|
// Cast Promise results to ensure consistent typing across Prisma versions
|
|
137
166
|
// This handles Prisma 7's GlobalOmitConfig and works fine with Prisma 6 too
|
|
138
167
|
const promiseCast = (op, nullable = false) => {
|
|
@@ -153,9 +182,13 @@ function generateModelOperations(models, customError) {
|
|
|
153
182
|
// Without custom error: use per-operation error types and mappers
|
|
154
183
|
const errorType = (opErrorType) => customError ? customError.className : opErrorType;
|
|
155
184
|
const mapperFn = (defaultMapper) => customError ? "mapError" : defaultMapper;
|
|
185
|
+
// Optimized signatures:
|
|
186
|
+
// - Use pre-computed Args type aliases instead of inline PrismaNamespace.Args
|
|
187
|
+
// - Remove Exact wrapper - the extends constraint already provides type safety
|
|
188
|
+
// - This reduces TypeScript's type computation workload significantly
|
|
156
189
|
return ` ${modelNameCamel}: {
|
|
157
|
-
findUnique: <A extends
|
|
158
|
-
args:
|
|
190
|
+
findUnique: <A extends ${argsType('findUnique')}>(
|
|
191
|
+
args: A
|
|
159
192
|
): Effect.Effect<${resultType('findUnique', true)}, ${errorType('PrismaFindError')}, PrismaClient> =>
|
|
160
193
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
161
194
|
Effect.tryPromise({
|
|
@@ -164,8 +197,8 @@ function generateModelOperations(models, customError) {
|
|
|
164
197
|
})
|
|
165
198
|
),
|
|
166
199
|
|
|
167
|
-
findUniqueOrThrow: <A extends
|
|
168
|
-
args:
|
|
200
|
+
findUniqueOrThrow: <A extends ${argsType('findUniqueOrThrow')}>(
|
|
201
|
+
args: A
|
|
169
202
|
): Effect.Effect<${resultType('findUniqueOrThrow')}, ${errorType('PrismaFindOrThrowError')}, PrismaClient> =>
|
|
170
203
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
171
204
|
Effect.tryPromise({
|
|
@@ -174,8 +207,8 @@ function generateModelOperations(models, customError) {
|
|
|
174
207
|
})
|
|
175
208
|
),
|
|
176
209
|
|
|
177
|
-
findFirst: <A extends
|
|
178
|
-
args?:
|
|
210
|
+
findFirst: <A extends ${argsType('findFirst')} = {}>(
|
|
211
|
+
args?: A
|
|
179
212
|
): Effect.Effect<${resultType('findFirst', true)}, ${errorType('PrismaFindError')}, PrismaClient> =>
|
|
180
213
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
181
214
|
Effect.tryPromise({
|
|
@@ -184,8 +217,8 @@ function generateModelOperations(models, customError) {
|
|
|
184
217
|
})
|
|
185
218
|
),
|
|
186
219
|
|
|
187
|
-
findFirstOrThrow: <A extends
|
|
188
|
-
args?:
|
|
220
|
+
findFirstOrThrow: <A extends ${argsType('findFirstOrThrow')} = {}>(
|
|
221
|
+
args?: A
|
|
189
222
|
): Effect.Effect<${resultType('findFirstOrThrow')}, ${errorType('PrismaFindOrThrowError')}, PrismaClient> =>
|
|
190
223
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
191
224
|
Effect.tryPromise({
|
|
@@ -194,8 +227,8 @@ function generateModelOperations(models, customError) {
|
|
|
194
227
|
})
|
|
195
228
|
),
|
|
196
229
|
|
|
197
|
-
findMany: <A extends
|
|
198
|
-
args?:
|
|
230
|
+
findMany: <A extends ${argsType('findMany')} = {}>(
|
|
231
|
+
args?: A
|
|
199
232
|
): Effect.Effect<${resultType('findMany')}, ${errorType('PrismaFindError')}, PrismaClient> =>
|
|
200
233
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
201
234
|
Effect.tryPromise({
|
|
@@ -204,8 +237,8 @@ function generateModelOperations(models, customError) {
|
|
|
204
237
|
})
|
|
205
238
|
),
|
|
206
239
|
|
|
207
|
-
create: <A extends
|
|
208
|
-
args:
|
|
240
|
+
create: <A extends ${argsType('create')}>(
|
|
241
|
+
args: A
|
|
209
242
|
): Effect.Effect<${resultType('create')}, ${errorType('PrismaCreateError')}, PrismaClient> =>
|
|
210
243
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
211
244
|
Effect.tryPromise({
|
|
@@ -215,7 +248,7 @@ function generateModelOperations(models, customError) {
|
|
|
215
248
|
),
|
|
216
249
|
|
|
217
250
|
createMany: (
|
|
218
|
-
args?:
|
|
251
|
+
args?: ${argsType('createMany')}
|
|
219
252
|
): Effect.Effect<PrismaNamespace.BatchPayload, ${errorType('PrismaCreateError')}, PrismaClient> =>
|
|
220
253
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
221
254
|
Effect.tryPromise({
|
|
@@ -224,8 +257,8 @@ function generateModelOperations(models, customError) {
|
|
|
224
257
|
})
|
|
225
258
|
),
|
|
226
259
|
|
|
227
|
-
createManyAndReturn: <A extends
|
|
228
|
-
args:
|
|
260
|
+
createManyAndReturn: <A extends ${argsType('createManyAndReturn')}>(
|
|
261
|
+
args: A
|
|
229
262
|
): Effect.Effect<${resultType('createManyAndReturn')}, ${errorType('PrismaCreateError')}, PrismaClient> =>
|
|
230
263
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
231
264
|
Effect.tryPromise({
|
|
@@ -234,8 +267,8 @@ function generateModelOperations(models, customError) {
|
|
|
234
267
|
})
|
|
235
268
|
),
|
|
236
269
|
|
|
237
|
-
delete: <A extends
|
|
238
|
-
args:
|
|
270
|
+
delete: <A extends ${argsType('delete')}>(
|
|
271
|
+
args: A
|
|
239
272
|
): Effect.Effect<${resultType('delete')}, ${errorType('PrismaDeleteError')}, PrismaClient> =>
|
|
240
273
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
241
274
|
Effect.tryPromise({
|
|
@@ -244,8 +277,8 @@ function generateModelOperations(models, customError) {
|
|
|
244
277
|
})
|
|
245
278
|
),
|
|
246
279
|
|
|
247
|
-
update: <A extends
|
|
248
|
-
args:
|
|
280
|
+
update: <A extends ${argsType('update')}>(
|
|
281
|
+
args: A
|
|
249
282
|
): Effect.Effect<${resultType('update')}, ${errorType('PrismaUpdateError')}, PrismaClient> =>
|
|
250
283
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
251
284
|
Effect.tryPromise({
|
|
@@ -255,7 +288,7 @@ function generateModelOperations(models, customError) {
|
|
|
255
288
|
),
|
|
256
289
|
|
|
257
290
|
deleteMany: (
|
|
258
|
-
args?:
|
|
291
|
+
args?: ${argsType('deleteMany')}
|
|
259
292
|
): Effect.Effect<PrismaNamespace.BatchPayload, ${errorType('PrismaDeleteManyError')}, PrismaClient> =>
|
|
260
293
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
261
294
|
Effect.tryPromise({
|
|
@@ -265,7 +298,7 @@ function generateModelOperations(models, customError) {
|
|
|
265
298
|
),
|
|
266
299
|
|
|
267
300
|
updateMany: (
|
|
268
|
-
args:
|
|
301
|
+
args: ${argsType('updateMany')}
|
|
269
302
|
): Effect.Effect<PrismaNamespace.BatchPayload, ${errorType('PrismaUpdateManyError')}, PrismaClient> =>
|
|
270
303
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
271
304
|
Effect.tryPromise({
|
|
@@ -274,8 +307,8 @@ function generateModelOperations(models, customError) {
|
|
|
274
307
|
})
|
|
275
308
|
),
|
|
276
309
|
|
|
277
|
-
updateManyAndReturn: <A extends
|
|
278
|
-
args:
|
|
310
|
+
updateManyAndReturn: <A extends ${argsType('updateManyAndReturn')}>(
|
|
311
|
+
args: A
|
|
279
312
|
): Effect.Effect<${resultType('updateManyAndReturn')}, ${errorType('PrismaUpdateManyError')}, PrismaClient> =>
|
|
280
313
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
281
314
|
Effect.tryPromise({
|
|
@@ -284,8 +317,8 @@ function generateModelOperations(models, customError) {
|
|
|
284
317
|
})
|
|
285
318
|
),
|
|
286
319
|
|
|
287
|
-
upsert: <A extends
|
|
288
|
-
args:
|
|
320
|
+
upsert: <A extends ${argsType('upsert')}>(
|
|
321
|
+
args: A
|
|
289
322
|
): Effect.Effect<${resultType('upsert')}, ${errorType('PrismaCreateError')}, PrismaClient> =>
|
|
290
323
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
291
324
|
Effect.tryPromise({
|
|
@@ -295,8 +328,8 @@ function generateModelOperations(models, customError) {
|
|
|
295
328
|
),
|
|
296
329
|
|
|
297
330
|
// Aggregation operations
|
|
298
|
-
count: <A extends
|
|
299
|
-
args?:
|
|
331
|
+
count: <A extends ${argsType('count')} = {}>(
|
|
332
|
+
args?: A
|
|
300
333
|
): Effect.Effect<${resultType('count')}, ${errorType('PrismaFindError')}, PrismaClient> =>
|
|
301
334
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
302
335
|
Effect.tryPromise({
|
|
@@ -305,8 +338,8 @@ function generateModelOperations(models, customError) {
|
|
|
305
338
|
})
|
|
306
339
|
),
|
|
307
340
|
|
|
308
|
-
aggregate: <A extends
|
|
309
|
-
args:
|
|
341
|
+
aggregate: <A extends ${argsType('aggregate')}>(
|
|
342
|
+
args: A
|
|
310
343
|
): Effect.Effect<${resultType('aggregate')}, ${errorType('PrismaFindError')}, PrismaClient> =>
|
|
311
344
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
312
345
|
Effect.tryPromise({
|
|
@@ -315,8 +348,8 @@ function generateModelOperations(models, customError) {
|
|
|
315
348
|
})
|
|
316
349
|
),
|
|
317
350
|
|
|
318
|
-
groupBy: <A extends
|
|
319
|
-
args:
|
|
351
|
+
groupBy: <A extends ${argsType('groupBy')}>(
|
|
352
|
+
args: A
|
|
320
353
|
): Effect.Effect<${resultType('groupBy')}, ${errorType('PrismaFindError')}, PrismaClient> =>
|
|
321
354
|
Effect.flatMap(PrismaClient, ({ tx: client }) =>
|
|
322
355
|
Effect.tryPromise({
|
|
@@ -341,24 +374,32 @@ function parseErrorImportPath(errorImportPath) {
|
|
|
341
374
|
async function generateUnifiedService(models, outputDir, clientImportPath, errorImportPath) {
|
|
342
375
|
const customError = parseErrorImportPath(errorImportPath);
|
|
343
376
|
const rawSqlOperations = generateRawSqlOperations(customError);
|
|
377
|
+
const modelTypeAliases = generateModelTypeAliases(models);
|
|
344
378
|
const modelOperations = generateModelOperations(models, customError);
|
|
345
379
|
// Generate different content based on whether custom error is configured
|
|
346
380
|
const serviceContent = customError
|
|
347
|
-
? generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelOperations)
|
|
348
|
-
: generateDefaultErrorService(clientImportPath, rawSqlOperations, modelOperations);
|
|
381
|
+
? generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelTypeAliases, modelOperations)
|
|
382
|
+
: generateDefaultErrorService(clientImportPath, rawSqlOperations, modelTypeAliases, modelOperations);
|
|
349
383
|
await promises_1.default.writeFile(node_path_1.default.join(outputDir, "index.ts"), serviceContent);
|
|
350
384
|
}
|
|
351
385
|
/**
|
|
352
386
|
* Generate service with custom user-provided error class.
|
|
353
387
|
* All operations use a single error type and a simple mapError function.
|
|
354
388
|
*/
|
|
355
|
-
function generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelOperations) {
|
|
389
|
+
function generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelTypeAliases, modelOperations) {
|
|
356
390
|
return `${header}
|
|
357
391
|
import { Context, Effect, Exit, Layer } from "effect"
|
|
358
392
|
import { Service } from "effect/Effect"
|
|
359
393
|
import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "${clientImportPath}"
|
|
360
394
|
import { ${customError.className}, mapPrismaError } from "${customError.path}"
|
|
361
395
|
|
|
396
|
+
// ============================================================================
|
|
397
|
+
// Type aliases for model operations (performance optimization)
|
|
398
|
+
// These are computed once and reused, reducing TypeScript's type-checking workload
|
|
399
|
+
// ============================================================================
|
|
400
|
+
|
|
401
|
+
${modelTypeAliases}
|
|
402
|
+
|
|
362
403
|
// Symbol used to identify intentional rollbacks vs actual errors
|
|
363
404
|
const ROLLBACK = Symbol.for("prisma.effect.rollback")
|
|
364
405
|
|
|
@@ -557,7 +598,8 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
557
598
|
* This implementation uses a callback-free transaction pattern that keeps the effect
|
|
558
599
|
* running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
|
|
559
600
|
*
|
|
560
|
-
*
|
|
601
|
+
* Uses default transaction options from PrismaClient constructor.
|
|
602
|
+
* For custom options, use \`$transactionWith\`.
|
|
561
603
|
*
|
|
562
604
|
* @example
|
|
563
605
|
* const result = yield* prisma.$transaction(
|
|
@@ -567,16 +609,63 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
567
609
|
* return user
|
|
568
610
|
* })
|
|
569
611
|
* )
|
|
612
|
+
*/
|
|
613
|
+
$transaction: <R, E, A>(
|
|
614
|
+
effect: Effect.Effect<A, E, R>
|
|
615
|
+
) =>
|
|
616
|
+
Effect.flatMap(
|
|
617
|
+
PrismaClient,
|
|
618
|
+
({ client, tx }): Effect.Effect<A, E | ${customError.className}, R> => {
|
|
619
|
+
// If we're already in a transaction, just run the effect directly (no nesting)
|
|
620
|
+
const isRootClient = "$transaction" in tx
|
|
621
|
+
if (!isRootClient) {
|
|
622
|
+
return effect
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Use acquireUseRelease to manage the transaction lifecycle
|
|
626
|
+
// This keeps everything in the same fiber, preserving Ref/FiberRef/Context
|
|
627
|
+
return Effect.acquireUseRelease(
|
|
628
|
+
// Acquire: begin a new transaction with default options
|
|
629
|
+
$begin(client),
|
|
630
|
+
|
|
631
|
+
// Use: run the effect with the transaction client injected
|
|
632
|
+
(txClient) =>
|
|
633
|
+
effect.pipe(
|
|
634
|
+
Effect.provideService(PrismaClient, { tx: txClient, client })
|
|
635
|
+
),
|
|
636
|
+
|
|
637
|
+
// Release: commit on success, rollback on failure/interruption
|
|
638
|
+
(txClient, exit) =>
|
|
639
|
+
Exit.isSuccess(exit)
|
|
640
|
+
? Effect.promise(() => txClient.$commit())
|
|
641
|
+
: Effect.promise(() => txClient.$rollback())
|
|
642
|
+
)
|
|
643
|
+
}
|
|
644
|
+
),
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Execute an effect within a database transaction with custom options.
|
|
648
|
+
* All operations within the effect will be atomic - they either all succeed or all fail.
|
|
649
|
+
*
|
|
650
|
+
* This implementation uses a callback-free transaction pattern that keeps the effect
|
|
651
|
+
* running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
|
|
652
|
+
*
|
|
653
|
+
* Options passed here override any defaults set in PrismaClient constructor.
|
|
570
654
|
*
|
|
571
655
|
* @example
|
|
572
656
|
* // Override default isolation level for this transaction
|
|
573
|
-
* const result = yield* prisma.$
|
|
574
|
-
*
|
|
575
|
-
* })
|
|
657
|
+
* const result = yield* prisma.$transactionWith(
|
|
658
|
+
* Effect.gen(function* () {
|
|
659
|
+
* const user = yield* prisma.user.create({ data: { name: "Alice" } })
|
|
660
|
+
* yield* prisma.post.create({ data: { title: "Hello", authorId: user.id } })
|
|
661
|
+
* return user
|
|
662
|
+
* }),
|
|
663
|
+
* { isolationLevel: "ReadCommitted", timeout: 10000 }
|
|
664
|
+
* )
|
|
576
665
|
*/
|
|
577
|
-
$
|
|
666
|
+
$transactionWith: <R, E, A>(
|
|
578
667
|
effect: Effect.Effect<A, E, R>,
|
|
579
|
-
options
|
|
668
|
+
options: TransactionOptions
|
|
580
669
|
) =>
|
|
581
670
|
Effect.flatMap(
|
|
582
671
|
PrismaClient,
|
|
@@ -621,6 +710,9 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
621
710
|
* ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
|
|
622
711
|
* or vice versa. Use carefully to avoid data inconsistencies.
|
|
623
712
|
*
|
|
713
|
+
* Uses default transaction options from PrismaClient constructor.
|
|
714
|
+
* For custom options, use \`$isolatedTransactionWith\`.
|
|
715
|
+
*
|
|
624
716
|
* @example
|
|
625
717
|
* yield* prisma.$transaction(
|
|
626
718
|
* Effect.gen(function* () {
|
|
@@ -634,8 +726,56 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
634
726
|
* )
|
|
635
727
|
*/
|
|
636
728
|
$isolatedTransaction: <R, E, A>(
|
|
729
|
+
effect: Effect.Effect<A, E, R>
|
|
730
|
+
) =>
|
|
731
|
+
Effect.flatMap(
|
|
732
|
+
PrismaClient,
|
|
733
|
+
({ client }): Effect.Effect<A, E | ${customError.className}, R> => {
|
|
734
|
+
// Always use the root client to create a fresh transaction
|
|
735
|
+
return Effect.acquireUseRelease(
|
|
736
|
+
$begin(client),
|
|
737
|
+
(txClient) =>
|
|
738
|
+
effect.pipe(
|
|
739
|
+
Effect.provideService(PrismaClient, { tx: txClient, client })
|
|
740
|
+
),
|
|
741
|
+
(txClient, exit) =>
|
|
742
|
+
Exit.isSuccess(exit)
|
|
743
|
+
? Effect.promise(() => txClient.$commit())
|
|
744
|
+
: Effect.promise(() => txClient.$rollback())
|
|
745
|
+
)
|
|
746
|
+
}
|
|
747
|
+
),
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Execute an effect in a NEW transaction with custom options, even if already inside a transaction.
|
|
751
|
+
* Unlike \`$transaction\`, this always creates a fresh, independent transaction.
|
|
752
|
+
*
|
|
753
|
+
* Use this for operations that should NOT be rolled back with the parent:
|
|
754
|
+
* - Audit logging that must persist even if main operation fails
|
|
755
|
+
* - Saga pattern where each step has independent commit/rollback
|
|
756
|
+
* - Background job queuing that should commit immediately
|
|
757
|
+
*
|
|
758
|
+
* ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
|
|
759
|
+
* or vice versa. Use carefully to avoid data inconsistencies.
|
|
760
|
+
*
|
|
761
|
+
* Options passed here override any defaults set in PrismaClient constructor.
|
|
762
|
+
*
|
|
763
|
+
* @example
|
|
764
|
+
* yield* prisma.$transaction(
|
|
765
|
+
* Effect.gen(function* () {
|
|
766
|
+
* // This audit log commits independently with custom isolation level
|
|
767
|
+
* yield* prisma.$isolatedTransactionWith(
|
|
768
|
+
* prisma.auditLog.create({ data: { action: "attempt", userId } }),
|
|
769
|
+
* { isolationLevel: "Serializable" }
|
|
770
|
+
* )
|
|
771
|
+
* // Main operation - if this fails, audit log is still committed
|
|
772
|
+
* yield* prisma.user.delete({ where: { id: userId } })
|
|
773
|
+
* })
|
|
774
|
+
* )
|
|
775
|
+
*/
|
|
776
|
+
$isolatedTransactionWith: <R, E, A>(
|
|
637
777
|
effect: Effect.Effect<A, E, R>,
|
|
638
|
-
options
|
|
778
|
+
options: TransactionOptions
|
|
639
779
|
) =>
|
|
640
780
|
Effect.flatMap(
|
|
641
781
|
PrismaClient,
|
|
@@ -749,12 +889,19 @@ export const makePrismaLayerEffect = PrismaClient.layerEffect
|
|
|
749
889
|
* Generate service with default tagged error classes.
|
|
750
890
|
* Operations have per-operation error types for fine-grained error handling.
|
|
751
891
|
*/
|
|
752
|
-
function generateDefaultErrorService(clientImportPath, rawSqlOperations, modelOperations) {
|
|
892
|
+
function generateDefaultErrorService(clientImportPath, rawSqlOperations, modelTypeAliases, modelOperations) {
|
|
753
893
|
return `${header}
|
|
754
894
|
import { Context, Data, Effect, Exit, Layer } from "effect"
|
|
755
895
|
import { Service } from "effect/Effect"
|
|
756
896
|
import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "${clientImportPath}"
|
|
757
897
|
|
|
898
|
+
// ============================================================================
|
|
899
|
+
// Type aliases for model operations (performance optimization)
|
|
900
|
+
// These are computed once and reused, reducing TypeScript's type-checking workload
|
|
901
|
+
// ============================================================================
|
|
902
|
+
|
|
903
|
+
${modelTypeAliases}
|
|
904
|
+
|
|
758
905
|
// Symbol used to identify intentional rollbacks vs actual errors
|
|
759
906
|
const ROLLBACK = Symbol.for("prisma.effect.rollback")
|
|
760
907
|
|
|
@@ -1290,7 +1437,8 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
1290
1437
|
* This implementation uses a callback-free transaction pattern that keeps the effect
|
|
1291
1438
|
* running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
|
|
1292
1439
|
*
|
|
1293
|
-
*
|
|
1440
|
+
* Uses default transaction options from PrismaClient constructor.
|
|
1441
|
+
* For custom options, use \`$transactionWith\`.
|
|
1294
1442
|
*
|
|
1295
1443
|
* @example
|
|
1296
1444
|
* const result = yield* prisma.$transaction(
|
|
@@ -1300,16 +1448,63 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
1300
1448
|
* return user
|
|
1301
1449
|
* })
|
|
1302
1450
|
* )
|
|
1451
|
+
*/
|
|
1452
|
+
$transaction: <R, E, A>(
|
|
1453
|
+
effect: Effect.Effect<A, E, R>
|
|
1454
|
+
) =>
|
|
1455
|
+
Effect.flatMap(
|
|
1456
|
+
PrismaClient,
|
|
1457
|
+
({ client, tx }): Effect.Effect<A, E | PrismaError, R> => {
|
|
1458
|
+
// If we're already in a transaction, just run the effect directly (no nesting)
|
|
1459
|
+
const isRootClient = "$transaction" in tx
|
|
1460
|
+
if (!isRootClient) {
|
|
1461
|
+
return effect
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
// Use acquireUseRelease to manage the transaction lifecycle
|
|
1465
|
+
// This keeps everything in the same fiber, preserving Ref/FiberRef/Context
|
|
1466
|
+
return Effect.acquireUseRelease(
|
|
1467
|
+
// Acquire: begin a new transaction with default options
|
|
1468
|
+
$begin(client),
|
|
1469
|
+
|
|
1470
|
+
// Use: run the effect with the transaction client injected
|
|
1471
|
+
(txClient) =>
|
|
1472
|
+
effect.pipe(
|
|
1473
|
+
Effect.provideService(PrismaClient, { tx: txClient, client })
|
|
1474
|
+
),
|
|
1475
|
+
|
|
1476
|
+
// Release: commit on success, rollback on failure/interruption
|
|
1477
|
+
(txClient, exit) =>
|
|
1478
|
+
Exit.isSuccess(exit)
|
|
1479
|
+
? Effect.promise(() => txClient.$commit())
|
|
1480
|
+
: Effect.promise(() => txClient.$rollback())
|
|
1481
|
+
)
|
|
1482
|
+
}
|
|
1483
|
+
),
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Execute an effect within a database transaction with custom options.
|
|
1487
|
+
* All operations within the effect will be atomic - they either all succeed or all fail.
|
|
1488
|
+
*
|
|
1489
|
+
* This implementation uses a callback-free transaction pattern that keeps the effect
|
|
1490
|
+
* running in the same fiber as the parent, preserving Ref, FiberRef, and Context access.
|
|
1491
|
+
*
|
|
1492
|
+
* Options passed here override any defaults set in PrismaClient constructor.
|
|
1303
1493
|
*
|
|
1304
1494
|
* @example
|
|
1305
1495
|
* // Override default isolation level for this transaction
|
|
1306
|
-
* const result = yield* prisma.$
|
|
1307
|
-
*
|
|
1308
|
-
* })
|
|
1496
|
+
* const result = yield* prisma.$transactionWith(
|
|
1497
|
+
* Effect.gen(function* () {
|
|
1498
|
+
* const user = yield* prisma.user.create({ data: { name: "Alice" } })
|
|
1499
|
+
* yield* prisma.post.create({ data: { title: "Hello", authorId: user.id } })
|
|
1500
|
+
* return user
|
|
1501
|
+
* }),
|
|
1502
|
+
* { isolationLevel: "ReadCommitted", timeout: 10000 }
|
|
1503
|
+
* )
|
|
1309
1504
|
*/
|
|
1310
|
-
$
|
|
1505
|
+
$transactionWith: <R, E, A>(
|
|
1311
1506
|
effect: Effect.Effect<A, E, R>,
|
|
1312
|
-
options
|
|
1507
|
+
options: TransactionOptions
|
|
1313
1508
|
) =>
|
|
1314
1509
|
Effect.flatMap(
|
|
1315
1510
|
PrismaClient,
|
|
@@ -1354,6 +1549,9 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
1354
1549
|
* ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
|
|
1355
1550
|
* or vice versa. Use carefully to avoid data inconsistencies.
|
|
1356
1551
|
*
|
|
1552
|
+
* Uses default transaction options from PrismaClient constructor.
|
|
1553
|
+
* For custom options, use \`$isolatedTransactionWith\`.
|
|
1554
|
+
*
|
|
1357
1555
|
* @example
|
|
1358
1556
|
* yield* prisma.$transaction(
|
|
1359
1557
|
* Effect.gen(function* () {
|
|
@@ -1367,8 +1565,56 @@ export class Prisma extends Service<Prisma>()("Prisma", {
|
|
|
1367
1565
|
* )
|
|
1368
1566
|
*/
|
|
1369
1567
|
$isolatedTransaction: <R, E, A>(
|
|
1568
|
+
effect: Effect.Effect<A, E, R>
|
|
1569
|
+
) =>
|
|
1570
|
+
Effect.flatMap(
|
|
1571
|
+
PrismaClient,
|
|
1572
|
+
({ client }): Effect.Effect<A, E | PrismaError, R> => {
|
|
1573
|
+
// Always use the root client to create a fresh transaction
|
|
1574
|
+
return Effect.acquireUseRelease(
|
|
1575
|
+
$begin(client),
|
|
1576
|
+
(txClient) =>
|
|
1577
|
+
effect.pipe(
|
|
1578
|
+
Effect.provideService(PrismaClient, { tx: txClient, client })
|
|
1579
|
+
),
|
|
1580
|
+
(txClient, exit) =>
|
|
1581
|
+
Exit.isSuccess(exit)
|
|
1582
|
+
? Effect.promise(() => txClient.$commit())
|
|
1583
|
+
: Effect.promise(() => txClient.$rollback())
|
|
1584
|
+
)
|
|
1585
|
+
}
|
|
1586
|
+
),
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* Execute an effect in a NEW transaction with custom options, even if already inside a transaction.
|
|
1590
|
+
* Unlike \`$transaction\`, this always creates a fresh, independent transaction.
|
|
1591
|
+
*
|
|
1592
|
+
* Use this for operations that should NOT be rolled back with the parent:
|
|
1593
|
+
* - Audit logging that must persist even if main operation fails
|
|
1594
|
+
* - Saga pattern where each step has independent commit/rollback
|
|
1595
|
+
* - Background job queuing that should commit immediately
|
|
1596
|
+
*
|
|
1597
|
+
* ⚠️ WARNING: The isolated transaction can commit while the parent rolls back,
|
|
1598
|
+
* or vice versa. Use carefully to avoid data inconsistencies.
|
|
1599
|
+
*
|
|
1600
|
+
* Options passed here override any defaults set in PrismaClient constructor.
|
|
1601
|
+
*
|
|
1602
|
+
* @example
|
|
1603
|
+
* yield* prisma.$transaction(
|
|
1604
|
+
* Effect.gen(function* () {
|
|
1605
|
+
* // This audit log commits independently with custom isolation level
|
|
1606
|
+
* yield* prisma.$isolatedTransactionWith(
|
|
1607
|
+
* prisma.auditLog.create({ data: { action: "attempt", userId } }),
|
|
1608
|
+
* { isolationLevel: "Serializable" }
|
|
1609
|
+
* )
|
|
1610
|
+
* // Main operation - if this fails, audit log is still committed
|
|
1611
|
+
* yield* prisma.user.delete({ where: { id: userId } })
|
|
1612
|
+
* })
|
|
1613
|
+
* )
|
|
1614
|
+
*/
|
|
1615
|
+
$isolatedTransactionWith: <R, E, A>(
|
|
1370
1616
|
effect: Effect.Effect<A, E, R>,
|
|
1371
|
-
options
|
|
1617
|
+
options: TransactionOptions
|
|
1372
1618
|
) =>
|
|
1373
1619
|
Effect.flatMap(
|
|
1374
1620
|
PrismaClient,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riordanpawley/effect-prisma-generator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Prisma generator for Effect (fork with Prisma 7 support)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/riordanpawley/effect-prisma-generator.git"
|
|
20
|
+
"url": "git+https://github.com/riordanpawley/effect-prisma-generator.git"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"prisma",
|