Skip to content

Instantly share code, notes, and snippets.

@arekmaz
Created October 27, 2023 14:59
Show Gist options
  • Save arekmaz/6fdcf9e84e7f7a46172ce256e81a403b to your computer and use it in GitHub Desktop.
Save arekmaz/6fdcf9e84e7f7a46172ce256e81a403b to your computer and use it in GitHub Desktop.
prisma + effect-ts integration - client which returns effects instead of promises
import { db } from "../services/db.server";
import { Data, Effect } from "effect";
import type { PrismaClient } from "@prisma/client";
import type {
PrismaClientKnownRequestError,
PrismaClientUnknownRequestError,
PrismaClientRustPanicError,
PrismaClientInitializationError,
PrismaClientValidationError,
} from "@prisma/client/runtime/library";
export class PrismaError extends Data.TaggedError("PrismaError")<{
details:
| PrismaClientKnownRequestError
| PrismaClientUnknownRequestError
| PrismaClientRustPanicError
| PrismaClientInitializationError
| PrismaClientValidationError;
}> {}
type FilterNotContaining<
Set,
Needle extends string
// eslint-disable-next-line @typescript-eslint/no-unused-vars
> = Set extends `${infer _A}${Needle}${infer _B}` ? never : Set;
type ExcludeFromUnionOtherTypes<From, E> = From extends E ? From : never;
type ExcludeNonStringKeys<Obj> = {
[k in ExcludeFromUnionOtherTypes<keyof Obj, string>]: k extends string
? Obj[k]
: never;
};
type ExcludeKeysContaining<
Obj extends Record<string, any>,
Key extends string
> = {
[key in FilterNotContaining<keyof Obj, Key>]: Obj[key];
};
export type Client = ExcludeKeysContaining<
ExcludeNonStringKeys<PrismaClient>,
"$" | "_"
> & {};
type LazyPromiseToLazyEffect<Fn extends (...a: any[]) => any> = Fn extends (
...a: infer Args
) => Promise<infer Result>
? <R, E>(...a: Args) => Effect.Effect<R, E, Result>
: never;
type EffectifyObject<
Obj extends Record<string, F>,
F extends (...a: any[]) => any = any
> = {
[op in keyof Obj]: LazyPromiseToLazyEffect<Obj[op]>;
};
type EffectPrisma = {
[model in keyof Client]: EffectifyObject<Client[model]>;
};
const createEffectClient = () => {
return new Proxy(
{},
{
get(_target, model) {
return new Proxy(
{},
{
get(_target, method) {
return (...args: any[]) =>
Effect.tryPromise(() => (db as any)[model][method](...args));
},
}
);
},
}
) as EffectPrisma;
};
// example usage:
// standard prisma:
const a = await db.log.findMany();
// a: {
// id: number;
// date: Date;
// login: string | null;
// info: string | null;
// }[]
// standard prisma:
const e = createEffectClient().log.findMany();
// Effect.Effect<unknown, unknown, {
// id: number;
// date: Date;
// login: string | null;
// info: string | null;
// }[]>
const e1 = createEffectClient().log.findMany<never, PrismaError>();
// Effect.Effect<never, PrismaError, {
// id: number;
// date: Date;
// login: string | null;
// info: string | null;
// }[]>
@adamgoose
Copy link

Working in 2025:

import { Data, Effect } from "effect";
import { Prisma, PrismaClient } from "@prisma/client";
import type {
  PrismaClientKnownRequestError,
  PrismaClientUnknownRequestError,
  PrismaClientRustPanicError,
  PrismaClientInitializationError,
  PrismaClientValidationError,
} from "@prisma/client/runtime/library";

export class PrismaError extends Data.TaggedError("PrismaError")<{
  details:
    | PrismaClientKnownRequestError
    | PrismaClientUnknownRequestError
    | PrismaClientRustPanicError
    | PrismaClientInitializationError
    | PrismaClientValidationError;
}> {}

type FilterNotContaining<
  Set,
  Needle extends string,
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
> = Set extends `${infer _A}${Needle}${infer _B}` ? never : Set;

type ExcludeFromUnionOtherTypes<From, E> = From extends E ? From : never;

type ExcludeNonStringKeys<Obj> = {
  [k in ExcludeFromUnionOtherTypes<keyof Obj, string>]: k extends string
    ? Obj[k]
    : never;
};

type ExcludeKeysContaining<
  Obj extends Record<string, any>,
  Key extends string,
> = {
  [key in FilterNotContaining<keyof Obj, Key>]: Obj[key];
};

export type Client = ExcludeKeysContaining<
  ExcludeNonStringKeys<PrismaClient>,
  "$" | "_"
> & {};

type LazyPromiseToLazyEffect<Fn extends (...a: any[]) => any> = Fn extends (
  ...a: infer Args
) => Promise<infer Result>
  ? (...a: Args) => Effect.Effect<Result, PrismaError, never>
  : never;

type EffectifyObject<
  Obj extends Record<string, F>,
  F extends (...a: any[]) => any = any,
> = {
  [op in keyof Obj]: LazyPromiseToLazyEffect<Obj[op]>;
};

type EffectPrisma = {
  [model in keyof Client]: EffectifyObject<Client[model]>;
};

export class PrismaService extends Effect.Service<PrismaService>()("Prisma", {
  sync: () => {
    const prisma = new PrismaClient();

    return new Proxy(
      {},
      {
        getOwnPropertyDescriptor() {
          return {
            enumerable: true,
            configurable: true,
          };
        },
        ownKeys() {
          return Object.values(Prisma.ModelName);
        },
        get(_target, model) {
          return new Proxy(
            {},
            {
              get(_target, method) {
                return (...args: any[]) =>
                  Effect.tryPromise(() =>
                    (prisma as any)[model][method](...args),
                  );
              },
            },
          );
        },
      },
    ) as EffectPrisma;
  },
}) {}

@IRediTOTO
Copy link

Thank you

@jjhiggz
Copy link

jjhiggz commented Jul 22, 2025

Here's a gist where I made a prisma effect generator which for me sped up typescript compiler dramatically.

I'd also really like to get good primitives in there for working with transactions and doing integration tests but haven't got that far yet.

Side note I'm very new to effect so any criticisms/feedback would be extremely welcome

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