Use this file to discover all available pages before exploring further.
The Supabase backend adapter gives dafny-replay apps managed authentication, database-level authorization through Row-Level Security, built-in realtime subscriptions, and serverless Edge Functions that run the compiled Dafny dispatch logic server-side in Deno. This is the default backend and the reference implementation for the multi-collaboration pattern.
The dispatch.single call invokes a Supabase Edge Function that runs the actual compiled Dafny code in Deno. The function:
Authenticates the caller via the Authorization header.
Checks project membership with RLS-aware queries.
Loads the current state, version, and applied_log from PostgreSQL.
Calls the Dafny dispatch() function from the pre-built bundle.
Persists the new state with an optimistic-lock eq('version', project.version) check.
Returns { status, version, state } to the client.
// supabase/functions/dispatch/index.ts (simplified)import { dispatch } from './dafny-bundle.ts' // compiled Dafnyconst result = dispatch( project.state, project.applied_log || [], baseVersion, action)if (result.status === 'rejected') { return new Response(JSON.stringify(result), { status: 200, ... })}// Optimistic lock: fails if another writer updated the rowconst { error } = await supabaseAdmin .from('projects') .update({ state: result.state, version: project.version + 1, ... }) .eq('id', projectId) .eq('version', project.version) // conflict if version changedif (error) { return new Response(JSON.stringify({ status: 'conflict' }), { status: 409, ... })}
The dafny-bundle.ts file is generated by dafny2js --deno and is not hand-written. Run ./compile.sh from the repo root to regenerate it after modifying domain logic.
realtime.subscribe opens a Supabase channel filtered to a single project row. When the Edge Function persists a new state, Supabase’s logical replication triggers an event that the client receives:
Supabase Realtime respects RLS: clients only receive events for rows their authenticated user can read. No extra membership check is needed in the subscription handler.
The pattern uses two core tables — one for entity state and one for membership:
CREATE TABLE projects ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, owner_id UUID NOT NULL REFERENCES auth.users(id), state JSONB NOT NULL DEFAULT '{}', version INT NOT NULL DEFAULT 0, applied_log JSONB NOT NULL DEFAULT '[]', created_at TIMESTAMPTZ DEFAULT now(), updated_at TIMESTAMPTZ DEFAULT now());CREATE TABLE project_members ( project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, role TEXT NOT NULL DEFAULT 'member', PRIMARY KEY (project_id, user_id));
Row-Level Security ensures members can read projects but all writes go through the Edge Function (which uses the service role key):
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;CREATE POLICY "members can read projects" ON projects FOR SELECT USING (is_project_member(id));
Use SECURITY DEFINER helper functions (like is_project_member) when RLS policies reference each other, to avoid infinite recursion. See SUPABASE.md in the repository for the full schema and RLS setup.
Supabase broadcasts auth state changes across browser tabs automatically. However, data-fetching hooks that run only on mount will not re-fetch if the user changes in another tab. Pass userId as a dependency to any fetch effect:
// Re-fetches when the user changes across tabsuseEffect(() => { if (!userId) return fetchProjects(userId)}, [userId])
# Login and link your projectsupabase loginsupabase link# Deploy the dispatch functionsupabase functions deploy dispatch
Set the frontend environment variables with your project URL and anon key, then deploy the static build to any host (Netlify, Cloudflare Pages, etc.):
npm run build# Upload dist/ to your chosen static host
The Supabase backend is described in detail in SUPABASE.md and DEPLOY_SUPABASE.md in the repository. Those files cover the full schema, RLS policies, offline hook API, and the trust boundary between Supabase and Dafny.