Skip to content

Instantly share code, notes, and snippets.

@riordanpawley
Last active May 9, 2025 16:11
Show Gist options
  • Save riordanpawley/1265ac8df7c373560bf42c71a731d2ff to your computer and use it in GitHub Desktop.
Save riordanpawley/1265ac8df7c373560bf42c71a731d2ff to your computer and use it in GitHub Desktop.
/** biome-ignore-all lint/suspicious/noExplicitAny: <explanation> */
import { neonConfig } from "@neondatabase/serverless";
import { PrismaNeon } from "@prisma/adapter-neon";
import type {
ITXClientDenyList,
Operation,
TypedSql,
} from "@prisma/client/runtime/library";
import {
type Cause,
Context,
Deferred,
Effect,
Exit,
Fiber,
Layer,
Metric,
Option,
Scope,
Schema
} from "effect";
import type { Replace } from "type-fest";
import ws from "ws";
import { PrismaClient as _PrismaClient, Prisma } from "./generated/prisma";
import { getMessage } from "@chefy-libs/utils";
export const PrismaErrorCodeSchema = Schema.Literal(
"not-found",
"unknown",
"invalid-state",
"db-not-found",
"timeout",
"access-denied",
"invalid-arguments",
"db-fn-unimplemented",
);
export type PrismaErrorCodes = typeof PrismaErrorCodeSchema.Type;
export const NativePrismaErrorShape = Schema.Struct({
clientVersion: Schema.optional(Schema.String),
code: Schema.optional(Schema.String),
errorCode: Schema.optional(Schema.String),
message: Schema.String,
meta: Schema.Record({ key: Schema.String, value: Schema.Unknown }).pipe(
Schema.annotations({ decodingFallback: () => Effect.succeed({}) }),
Schema.optional,
),
});
export class PrismaError extends Schema.TaggedError<PrismaError>()(
"PrismaError",
{
clientVersion: Schema.optional(Schema.String),
code: PrismaErrorCodeSchema,
message: Schema.String,
meta: Schema.Record({ key: Schema.String, value: Schema.Unknown }).pipe(
Schema.optional,
),
prismaCode: Schema.String.pipe(Schema.NullOr),
},
) {
static fromUnknown(err: unknown): PrismaError {
if (err instanceof PrismaError) {
return err;
}
return Schema.decodeUnknownOption(NativePrismaErrorShape)(err).pipe(
Option.match({
onNone: () =>
new PrismaError({
code: "unknown",
message: getMessage(err),
prismaCode: null,
}),
onSome: (parsed) =>
new PrismaError({
clientVersion: parsed.clientVersion,
code: prismaCodeMapper(parsed.code ?? parsed.errorCode ?? ""),
message: parsed.message,
meta: parsed.meta,
prismaCode: parsed.code ?? parsed.errorCode ?? null,
}),
}),
);
}
}
const prismaCodeMapper = (code: string): PrismaErrorCodes => {
switch (code) {
case "P1001":
case "P1013":
case "P1011":
return "db-not-found";
case "P2024":
case "P1008":
return "timeout";
case "P1010":
return "access-denied";
case "P2009":
case "P2012":
case "P2013":
case "P2000":
return "invalid-arguments";
case "P2002":
case "P2003":
case "P2015":
case "P2018":
case "P2025":
case "P2001":
return "not-found";
case "P2005":
return "invalid-state";
case "P2026":
return "db-fn-unimplemented";
default:
return "unknown";
}
};
type PrismaModelOp = Exclude<
// You can import operation types from the generated Prisma client
Operation,
| "$executeRaw"
| "$executeRawUnsafe"
| "$queryRaw"
| "$queryRawUnsafe"
| "$runCommandRaw"
| "aggregateRaw"
| "findFirst"
| "findFirstOrThrow"
| "findRaw"
| "findUnique"
| "findUniqueOrThrow"
>;
const PrismaModelOps = [
"findMany",
"create",
"createMany",
"createManyAndReturn",
"update",
"updateMany",
"updateManyAndReturn",
"upsert",
"delete",
"deleteMany",
"aggregate",
"count",
"groupBy",
] as const satisfies PrismaModelOp[];
export const prismaEffectExtension = {
client: {
// $executeRaw: (query: TemplateStringsArray | Sql, ...values: any[]) => PrismaPromise<number>
$executeRawEffect: function <T>(
this: T,
// This type will work after generating the Prisma client
query: Prisma.Sql | TemplateStringsArray,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...values: any[]
): Effect.Effect<number, PrismaError, never> {
return Effect.tryPromise<number, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient = Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.$executeRaw(query, ...values);
},
});
},
// $executeRawUnsafe: (query: string, ...values: any[]) => PrismaPromise<number>
$executeRawUnsafeEffect: function <T>(
this: T,
query: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...values: any[]
): Effect.Effect<number, PrismaError, never> {
return Effect.tryPromise<number, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient = Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.$executeRawUnsafe(
query,
...values,
);
},
});
},
// $queryRaw: <T = unknown>(query: TemplateStringsArray | Sql, ...values: any[]) => PrismaPromise<T>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
$queryRawEffect: function <A = unknown, T = any>(
this: T,
query: Prisma.Sql | TemplateStringsArray,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...values: any[]
): Effect.Effect<A, PrismaError, never> {
return Effect.tryPromise<A, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient = Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.$queryRaw(query, ...values);
},
});
},
// $queryRawTyped: <T>(query: TypedSql<unknown[], T>) => PrismaPromise<T[]>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
$queryRawTypedEffect: function <A = unknown, T = any>(
this: T,
query: TypedSql<unknown[], T>,
): Effect.Effect<A, PrismaError, never> {
return Effect.tryPromise<A, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient = Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.$queryRawTyped(query);
},
});
},
// $queryRawUnsafe: <T = unknown>(query: string, ...values: any[]) => PrismaPromise<T>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
$queryRawUnsafeEffect: function <A = unknown, T = any>(
this: T,
query: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...values: any[]
): Effect.Effect<A, PrismaError, never> {
return Effect.tryPromise<A, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient = Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.$queryRawUnsafe(query, ...values);
},
});
},
},
model: {
$allModels: {
...(Object.fromEntries(
PrismaModelOps.map((method) => [
`${method}Effect`,
function <T, A, O extends typeof method>(
// `this` is the current type (for example
// it might be `prisma.user` at runtime).
this: T,
x?: Prisma.Exact<
A,
// For `customCall`, use the arguments from model `T` and the
// operation `findFirst`. Add `customProperty` to the operation.
Prisma.Args<T, O>
>,
// Get the correct result types for the model of type `T`,
// and the arguments of type `A` for `findFirst`.
// `Prisma.Result` computes the result for a given operation
// such as `select {id: true}` in function `main` below.
//,
): Effect.Effect<Prisma.Result<T, A, O>, PrismaError, never> {
return Effect.tryPromise<Prisma.Result<T, A, O>, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
// eslint-disable-next-line no-invalid-this
const prismaExtensionContextClient =
Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return prismaExtensionContextClient[method](x) as any;
},
});
},
]),
) as {
[K in `${(typeof PrismaModelOps)[number]}Effect`]: <
T,
A,
O extends Replace<K, "Effect", "">,
>(
this: T,
x?: Prisma.Exact<A, Prisma.Args<T, O>>,
) => Effect.Effect<Prisma.Result<T, A, O>, PrismaError, never>;
}),
findFirstEffect<T, A, O extends "findFirst">(
this: T,
x?: Prisma.Exact<A, Prisma.Args<T, O>>,
): Effect.Effect<Prisma.Result<T, A, O>, PrismaError, never> {
return Effect.tryPromise<Prisma.Result<T, A, O>, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient =
Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.findFirst(x);
},
});
},
findFirstOption<T, A, O extends "findFirst">(
this: T,
x?: Prisma.Exact<A, Prisma.Args<T, O>>,
): Effect.Effect<
Option.Option<NonNullable<Prisma.Result<T, A, O>>>,
PrismaError,
never
> {
return Effect.tryPromise<Prisma.Result<T, A, O>, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient =
Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.findFirst(x);
},
}).pipe(Effect.map((result) => Option.fromNullable(result)));
},
findFirstOrThrowEffect<T, A, O extends "findFirstOrThrow">(
this: T,
x?: Prisma.Exact<A, Prisma.Args<T, O>>,
): Effect.Effect<Prisma.Result<T, A, O>, PrismaError, never> {
return Effect.tryPromise<Prisma.Result<T, A, O>, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient =
Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.findFirstOrThrow(x);
},
});
},
findUniqueEffect<T, A, O extends "findUnique">(
this: T,
x?: Prisma.Exact<A, Prisma.Args<T, O>>,
): Effect.Effect<Prisma.Result<T, A, O>, PrismaError, never> {
return Effect.tryPromise<Prisma.Result<T, A, O>, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient =
Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.findUnique(x);
},
});
},
findUniqueOption<T, A, O extends "findUnique">(
this: T,
x?: Prisma.Exact<A, Prisma.Args<T, O>>,
): Effect.Effect<
Option.Option<NonNullable<Prisma.Result<T, A, O>>>,
PrismaError,
never
> {
return Effect.tryPromise<Prisma.Result<T, A, O>, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient =
Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.findUnique(x);
},
}).pipe(Effect.map((result) => Option.fromNullable(result)));
},
findUniqueOrThrowEffect<T, A, O extends "findUniqueOrThrow">(
this: T,
x?: Prisma.Exact<A, Prisma.Args<T, O>>,
): Effect.Effect<Prisma.Result<T, A, O>, PrismaError, never> {
return Effect.tryPromise<Prisma.Result<T, A, O>, PrismaError>({
catch: PrismaError.fromUnknown,
try: () => {
const prismaExtensionContextClient =
Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.findUniqueOrThrow(x);
},
});
},
},
},
};
export const makePrismaClient = (arg: { adapter: PrismaNeon | null }) =>
new _PrismaClient(arg).$extends(prismaEffectExtension);
export interface PrismaClient extends ReturnType<typeof makePrismaClient> {}
export const PrismaClient = Context.GenericTag<PrismaClient>("PrismaClient");
export interface TransactionClient
extends Omit<PrismaClient, ITXClientDenyList> {}
export const TransactionClient =
Context.GenericTag<TransactionClient>("TransactionClient");
export const EitherClient = Effect.serviceOptional(TransactionClient).pipe(
Effect.catchAll(() => PrismaClient),
);
export type EitherClient = Effect.Effect.Context<typeof EitherClient>;
export { PrismaError };
export const skip = Prisma.skip;
export const PrismaQueriesMetric = Metric.counter("prisma_queries");
export const layer = Layer.sync(PrismaClient, () => {
neonConfig.webSocketConstructor = ws;
const connectionString = `${process.env.DATABASE_URL}`;
// const pool = new Pool({ connectionString });
const adapter = new PrismaNeon({ connectionString });
const prisma = makePrismaClient({ adapter });
return prisma;
});
export const safeLayer = Layer.scoped(
PrismaClient,
Effect.acquireRelease(PrismaClient, (client) =>
Effect.zipRight(
Effect.log("disconnecting prisma client"),
Effect.promise(() => client.$disconnect()),
),
).pipe(Effect.provide(layer)),
);
// BEWARE: LOGGING IN HERE MAY BREAK PRISMA https://github.com/prisma/prisma/issues/25501
export const withTransaction = (prisma: PrismaClient) =>
Effect.provideServiceEffect(
TransactionClient,
Effect.gen(function* () {
const scope = yield* Effect.scope;
const txClientDeferred = yield* Deferred.make<TransactionClient>();
const txCommitDeferred = yield* Deferred.make<
null | PrismaError | Cause.Cause<unknown>
>();
const fiber = yield* Effect.fork(
Effect.tryPromise({
catch: (_) => PrismaError.fromUnknown(_),
try: () =>
prisma.$transaction(
(txClient) =>
Effect.gen(function* () {
yield* Deferred.succeed(txClientDeferred, txClient);
const res = yield* Deferred.await(txCommitDeferred);
if (res instanceof PrismaError) {
return yield* res;
}
}).pipe(Effect.runPromise),
{
isolationLevel: "Serializable",
maxWait: 10000,
timeout: 40000,
},
),
}).pipe(Effect.withSpan("$transaction")),
);
// ensure the transaction is committed before the scope is closed
yield* Scope.addFinalizer(
scope,
Fiber.await(fiber).pipe(Effect.ignoreLogged, Effect.uninterruptible),
);
// add finalizer to commit the transaction - THIS MUST BE SET LAST
// due to how finalizers are executed in reverse order
yield* Scope.addFinalizerExit(scope, (exit) =>
Exit.match(exit, {
onFailure: (err) => {
if (err._tag === "Fail") {
return Deferred.succeed(
txCommitDeferred,
PrismaError.fromUnknown(err.error),
);
}
return Deferred.succeed(txCommitDeferred, err);
},
onSuccess: () => {
return Deferred.succeed(txCommitDeferred, null);
},
}).pipe(Effect.uninterruptible),
);
const result = yield* Deferred.await(txClientDeferred);
// ensure the transaction is committed before returning the client
return result;
}),
);
export * from "./generated/prisma/client";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment