Atelier

Database

Every project gets its own Postgres database with Row Level Security, a typed query builder, named functions, webhooks, and versioned migrations. PGlite-backed in development, Postgres in production — same SQL, same RLS, no surprises at deploy.

Schema

Schemas are plain SQL DDL, the same model as Supabase. Migration files live under your project source and are applied automatically on deploy.

.atelier/
└── migrations/
    ├── 20260615120000_init.sql
    ├── 20260616093000_add_todos.sql
    └── 20260620140000_add_index.sql

Studio’s AI writes new migration files when you ask it to add a table, change a column, or add an index. You can also write them by hand or with the CLI:

atelier db migration new add_todos
-- .atelier/migrations/20260616093000_add_todos.sql
CREATE TABLE todos (
  id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id     uuid NOT NULL,
  title       text NOT NULL,
  done        boolean NOT NULL DEFAULT false,
  created_at  timestamptz NOT NULL DEFAULT now()
);
 
CREATE INDEX todos_user_idx ON todos (user_id);

Apply locally with atelier db migrate or just save in Studio — pending migrations run automatically. Each migration is tracked by ID in a system table, so re-running is a no-op.

Row Level Security

Base threads the signed-in end-user’s identity from the OIDC session cookie into every query as a Postgres GUC (atelier.user_id, atelier.user_role, atelier.user_email). Write RLS policies against those and your authz is enforced at the database layer:

ALTER TABLE todos ENABLE ROW LEVEL SECURITY;
 
CREATE POLICY todos_owner_select ON todos
  FOR SELECT
  USING (user_id = current_setting('atelier.user_id')::uuid);
 
CREATE POLICY todos_owner_write ON todos
  FOR INSERT WITH CHECK (user_id = current_setting('atelier.user_id')::uuid);
Why GUC instead of JWT in app code

RLS using session context means the database — not your application — is the source of truth for who can see what. Bypass the app server with a different client and the rules still hold.

Query from your app

Base separates client query from server SQL. End-user client code never executes raw SQL or DDL — it uses the typed query builder or calls named functions you’ve defined.

Typed query builder

import { atelier } from '@atelier/sdk';
 
const todos = await atelier.from('todos')
  .select('id, title, done')
  .order('created_at', { ascending: false });
 
await atelier.from('todos').insert({ title: 'Ship the thing' });
 
await atelier.from('todos')
  .update({ done: true })
  .eq('id', 'abc');
 
await atelier.from('todos').delete().eq('id', 'abc');

The builder compiles to a small set of safe operations. The server validates them against your schema and RLS policies. Auth flows through the user’s cookie automatically.

Named functions

For anything that doesn’t fit the builder — joins across many tables, set-based updates, aggregations — define a server function and call it by name:

// .atelier/db-functions/open-todo-count.sql
CREATE FUNCTION open_todo_count(p_user_id uuid)
RETURNS int
LANGUAGE sql
SECURITY INVOKER
AS $$
  SELECT count(*)::int FROM todos
  WHERE done = false AND user_id = p_user_id;
$$;
import { atelier } from '@atelier/sdk';
 
const { count } = await atelier.rpc('open_todo_count', { user_id: me.id });

Functions run under the caller’s identity, so RLS still applies.

Webhooks on mutation

Base fires webhooks for inserts, updates, and deletes — useful for downstream systems, audit logs, or wiring side effects without polling.

{
  "table": "todos",
  "op": "INSERT",
  "row": { "id": "...", "title": "Ship the thing", "done": false },
  "user_id": "user_abc"
}

Dev vs production

AspectDevelopmentProduction
BackendPGlite (in-process)Postgres (RDS)
Co-locatedYes, in your dev serverNo, networked
LatencyIn-processSub-ms within VPC
SQL surfacePostgres-compatibleNative Postgres
Migration applyAuto on save / atelier db migrateAuto on deploy, tracked by ID
IsolationPer-project schemaPer-project schema

Limits

Free tier projects: 500 MB storage, 1 GB egress/month, 100k row reads/day. Paid plans lift these significantly. See pricing.