Adapting Prisma's Transaction API to Effect TypeScript
Hi! I'm using Prisma ORM in my project, and trying to adapt its callback/promise-y transaction API:
I've currently come up with this, but I'm wondering if there's a better way to do it
Working solution in the playground, with some more comments and context: https://effect.website/play#ff4d35749a68
The main parts here:
const result = await prisma.transaction(async (tx) => {
// do something with the transaction
const result = await tx.someTable.select(/* blah */);
// do anything else
return result;
});const result = await prisma.transaction(async (tx) => {
// do something with the transaction
const result = await tx.someTable.select(/* blah */);
// do anything else
return result;
});I've currently come up with this, but I'm wondering if there's a better way to do it
Working solution in the playground, with some more comments and context: https://effect.website/play#ff4d35749a68
The main parts here:
// Some example callback-y API. Could use promises, but doesn't in this example for mild simplicity
const sampleCallbackApi = <A>(input: number, callback: (evenNumber: number) => A): A => {
if (input % 2 !== 0) {
throw new Error(`${input} is not even`)
}
return callback(input)
}
const effectApi02 = <A, E>(
input: number,
handleEvenNumber: (evenNumber: number) => Effect.Effect<A, E, never>
): Effect.Effect<A, E | Error, never> =>
Effect.gen(function*() {
const exit = yield* Effect.promise(() =>
sampleCallbackApi(input, async (evenNumber) => Effect.runPromiseExit(handleEvenNumber(evenNumber)))
)
return yield* exitToEffect(exit)
})
function exitToEffect<A, E>(
exit: Exit.Exit<A, E>
): Effect.Effect<A, E, never> {
return Effect.gen(function*() {
return yield* exit.pipe(
Exit.match({
onSuccess: (value) => Effect.succeed(value),
onFailure: (cause) => {
// Surface our "expected" errors
if (Cause.isFailType(cause)) {
return Effect.fail(cause.error)
}
return Effect.die(cause)
}
})
)
})
}// Some example callback-y API. Could use promises, but doesn't in this example for mild simplicity
const sampleCallbackApi = <A>(input: number, callback: (evenNumber: number) => A): A => {
if (input % 2 !== 0) {
throw new Error(`${input} is not even`)
}
return callback(input)
}
const effectApi02 = <A, E>(
input: number,
handleEvenNumber: (evenNumber: number) => Effect.Effect<A, E, never>
): Effect.Effect<A, E | Error, never> =>
Effect.gen(function*() {
const exit = yield* Effect.promise(() =>
sampleCallbackApi(input, async (evenNumber) => Effect.runPromiseExit(handleEvenNumber(evenNumber)))
)
return yield* exitToEffect(exit)
})
function exitToEffect<A, E>(
exit: Exit.Exit<A, E>
): Effect.Effect<A, E, never> {
return Effect.gen(function*() {
return yield* exit.pipe(
Exit.match({
onSuccess: (value) => Effect.succeed(value),
onFailure: (cause) => {
// Surface our "expected" errors
if (Cause.isFailType(cause)) {
return Effect.fail(cause.error)
}
return Effect.die(cause)
}
})
)
})
}