AZMARA PLATFORM
Packages

@azmr/db-supabase

A DbAdapter backed by Supabase's PostgREST query builder, the cloud counterpart to the SQLite adapter in @azmr/db.

@azmr/db-supabase implements the same DbAdapter interface as @azmr/db's SQLiteAdapter, but stores data in Supabase (Postgres). Code written against DbAdapter runs against either backend without changes.

Installation

pnpm add @azmr/db-supabase @supabase/supabase-js

@supabase/supabase-js is a peer dependency: bring your own client, so you keep full control over auth, session and cookie wiring (for example @supabase/ssr's browser and server client constructors).

Usage

import { createClient } from "@supabase/supabase-js";
import { SupabaseAdapter } from "@azmr/db-supabase";

const client = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_KEY!);
const db = new SupabaseAdapter(client);

await db.insert("products", { name: "Widget A", price: 9.99 });

const inStock = await db.findWhere("products", [
  { column: "inStock", operator: "is", value: true },
]);

Row Level Security is enforced by whatever JWT the SupabaseClient you pass in carries. This package never constructs its own client and never bypasses that.

What is different from SQLiteAdapter

  • Async throughout. Every method returns a Promise, because PostgREST is a network call.
  • No rawSelect. PostgREST does not accept arbitrary SQL. Use findWhere with a structured filter instead.
  • createTable always throws. PostgREST cannot run DDL, so this package is bring-your-own-schema: manage tables with the Supabase SQL editor, CLI migrations or the dashboard.
import { SupabaseAdapter } from "@azmr/db-supabase";

// `client` is your SupabaseClient. This call never reaches it.
const db = new SupabaseAdapter(client);

let message;
try {
  await db.createTable("products", { name: "string" });
} catch (error) {
  message = error.message;
}
// => "[azmara/db-supabase] createTable(\"products\") is not supported — PostgREST cannot execute DDL. Create/manage this table via the Supabase SQL editor, CLI migrations, or dashboard."

Errors

Every { data, error } result from supabase-js is checked. On failure the adapter throws a DbAdapterError that carries the original Postgres error's code, details and hint, with the raw error as cause. Nothing is swallowed.

Because the adapter only calls the client you give it, you can test code that uses it with a stub. This one fails the way Postgres does when a policy denies a read:

const denied = { message: "boom", code: "42501", details: "d", hint: "h" };
const failing = new SupabaseAdapter({
  from: () => ({ select: () => Promise.resolve({ data: null, error: denied }) }),
});

let fields;
try {
  await failing.getAll("products");
} catch (error) {
  fields = { name: error.name, message: error.message, code: error.code, details: error.details, hint: error.hint };
}
// => {"name":"DbAdapterError","message":"[azmara/db-supabase] getAll(\"products\") failed: boom","code":"42501","details":"d","hint":"h"}

Filters

findWhere and deleteWhere take a Filter: a list of conditions that are combined with AND. There is no OR yet.

type Filter = Array<{
  column: string;
  operator: "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "like" | "ilike" | "is" | "in";
  value: unknown;
}>;

See @azmr/db-core-interface for what each operator expects.

Runtime footprint

@azmr/db-supabase depends on @azmr/db-core-interface (the contract) and @azmr/security, and not on @azmr/db. Installing it does not install better-sqlite3 or any other native module, so it is safe to bundle into serverless and edge runtimes (Vercel, Next.js server components and actions, Cloudflare Workers).

Versions 0.2.1 and earlier depended on @azmr/db for the interface, which installed better-sqlite3 even though it was never loaded.

Requirements

  • Node.js 18 or later
  • @supabase/supabase-js ^2.103.0
  • ESM only: require("@azmr/db-supabase") does not work; use import.

On this page