Last active
May 9, 2025 16:11
-
-
Save riordanpawley/1265ac8df7c373560bf42c71a731d2ff to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** 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