Supabase service in Effect

Type-safe client and queries in Supabase using effect.

Type safety requires you to generate typescript types for your database. Running types.sh with your project-id will generate a database.ts file containing types for your supabase project.

Using these we create a Supabase service with effect that returns a client and a type-safe query function.

Languages

typescript

Libraries

effectSupabase
import { Database } from "./database/database"; // 👈 Generated types
import * as _Supabase from "@supabase/supabase-js";
import { Config, Context, Data, Effect, Layer, Redacted } from "effect";

interface SupabaseConfig {
  readonly url: string;
  readonly key: Redacted.Redacted;
}

class SupabaseError extends Data.TaggedError("SupabaseError")<
  Readonly<{
    error: _Supabase.PostgrestError;
  }>
> {}

const make = ({ key, url }: SupabaseConfig) =>
  Effect.gen(function* (_) {
    const client = yield* Effect.sync(() =>
      _Supabase.createClient<Database>(url, Redacted.value(key))
    );

    const query = <A>(
      body: (
        _: typeof client
      ) => PromiseLike<_Supabase.PostgrestSingleResponse<A>>
    ) =>
      Effect.async<A, SupabaseError, never>((cb) => {
        body(client).then(({ data, error }) => {
          if (error) {
            cb(Effect.fail(new SupabaseError({ error })));
          } else {
            cb(Effect.succeed(data));
          }
        });
      });

    return { query, client };
  });

export class Supabase extends Context.Tag("Supabase")<
  Supabase,
  Effect.Effect.Success<ReturnType<typeof make>>
>() {
  static readonly Live = (config: Config.Config.Wrap<SupabaseConfig>) =>
    Config.unwrap(config).pipe(
      Effect.flatMap(make),
      Layer.effect(this),
      Layer.orDie
    );
}