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;
  },
}) {}

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