Skip to content

Instantly share code, notes, and snippets.

@aniravi24
Last active March 6, 2025 17:54
Show Gist options
  • Save aniravi24/b1f97ab2d7e6ec54b8309ed0452745a0 to your computer and use it in GitHub Desktop.
Save aniravi24/b1f97ab2d7e6ec54b8309ed0452745a0 to your computer and use it in GitHub Desktop.
import { Prisma } from "@prisma/client";
import type { Replace, UnionToTuple } from "type-fest";
import { Effect, Option } from "effect";
import { UnknownException } from "effect/Cause";
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: UnionToTuple<PrismaModelOp> = [
"findMany",
"create",
"createMany",
"createManyAndReturn",
"update",
"updateMany",
"updateManyAndReturn",
"upsert",
"delete",
"deleteMany",
"aggregate",
"count",
"groupBy",
] as const;
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, UnknownException, never> {
return Effect.tryPromise<number>(() => {
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, UnknownException, never> {
return Effect.tryPromise<number>(() => {
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, UnknownException, never> {
return Effect.tryPromise<A>(() => {
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, UnknownException, never> {
return Effect.tryPromise<A>(() => {
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, UnknownException, never> {
return Effect.tryPromise<A>(() => {
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>, UnknownException, never> {
return Effect.tryPromise<Prisma.Result<T, A, O>>(() => {
// 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>, UnknownException, never>;
}),
findFirstEffect<T, A, O extends "findFirstOrThrow">(
this: T,
x?: Prisma.Exact<A, Prisma.Args<T, O>>
): Effect.Effect<Prisma.Result<T, A, O>, UnknownException, never> {
return Effect.tryPromise<Prisma.Result<T, A, O>>(() => {
const prismaExtensionContextClient = Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.findFirstOrThrow(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>>>,
UnknownException,
never
> {
return Effect.tryPromise<Prisma.Result<T, A, O>>(() => {
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)));
},
findUniqueEffect<T, A, O extends "findUniqueOrThrow">(
this: T,
x?: Prisma.Exact<A, Prisma.Args<T, O>>
): Effect.Effect<Prisma.Result<T, A, O>, UnknownException, never> {
return Effect.tryPromise<Prisma.Result<T, A, O>>(() => {
const prismaExtensionContextClient = Prisma.getExtensionContext(this);
// @ts-expect-error Can't predict these in advance
return prismaExtensionContextClient.findUniqueOrThrow(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>>>,
UnknownException,
never
> {
return Effect.tryPromise<Prisma.Result<T, A, O>>(() => {
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)));
},
},
},
};
@aniravi24
Copy link
Author

Replace comes from https://github.com/sindresorhus/type-fest

This is an older version of Effect so some changes may need to be made. The functions might have different arguments and the Effect.Effect type should have the arguments in reverse order (instead of R, E, A, it should be A, E, R)

@aniravi24
Copy link
Author

This adds the Prisma methods suffixed with the word Effect, so you can call prisma.someTable.findManyEffect({ testing: true })

@aniravi24
Copy link
Author

Updated to Effect 3.12

@aniravi24
Copy link
Author

Updated to fix the client operations, need to explicitly list them as they are all different

@afonsomatos
Copy link

Nice!

@aniravi24
Copy link
Author

aniravi24 commented Mar 6, 2025

Updated to make the findFirst and findUnique more ergonomic with Effect by using Option when it's nullable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment