PURPOSE

Single Supabase client instance for the entire app, plus the canonical invokeRpc() helper that every server-authoritative mutation flows through. Validates required env vars at import time so misconfigured builds fail loudly rather than silently.

OWNS

  • The exported supabase client (created via createClient from @supabase/supabase-js).
  • The invokeRpc<T>() async helper that wraps supabase.rpc() with error extraction, typed returns, offline short-circuit, and Sentry breadcrumb emission.
  • Import-time validation of VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY.
  • The isOffline() lazy lookup that reads the __PLAYER_STORE_OFFLINE__ flag off window to avoid a circular playerStore -> supabase -> playerStore import graph.

READS FROM

  • import.meta.env.VITE_SUPABASE_URL and import.meta.env.VITE_SUPABASE_ANON_KEY (Vite env, validated at module load).
  • window.__PLAYER_STORE_OFFLINE__ global flag mirrored from the zustand player store.
  • localStorage (passed as the auth session storage to createClient).

PUSHES TO

  • Supabase RPC endpoints via supabase.rpc(functionName, params).
  • Sentry as addBreadcrumb entries (category supabase, level error) when an RPC errors.
  • console.warn when an RPC is skipped because the user is in offline mode.

DOES NOT

  • Does not import the service role key — anon key only, service role is server-side.
  • Does not bypass RLS — security is enforced server-side via SECURITY DEFINER RPCs.
  • Does not call supabase.from() directly outside of auth.ts (which uses it for session management only).
  • Does not send Sentry events for RPC failures (breadcrumbs only) to avoid one-event-per-call issue spam during production outages.
  • Does not import from playerStore directly — uses the window.__PLAYER_STORE_OFFLINE__ global to break the circular dependency.
  • Does not provide a non-null default for offline short-circuits — callers needing one must check usePlayerStore.getState().offlineMode themselves.
  • Does not persist anything beyond what the Supabase JS client persists to localStorage for session.

Signals

  • Throws Error('VITE_SUPABASE_URL not set — check .env.development') at import time if URL env is missing.
  • Throws Error('VITE_SUPABASE_ANON_KEY not set — check .env.development') at import time if anon key env is missing.
  • Throws Error('RPC <name> failed: <message>') when an RPC returns an error.
  • Emits Sentry breadcrumb { category: 'supabase', level: 'error', message: 'RPC <name> failed', data: { endpoint, message } } before throwing.
  • Emits console.warn('[offline] skipping rpc:', functionName) on offline-mode short-circuit.
  • Returns null cast to T when offline mode is active.

Entry points

  • supabase — exported Supabase client used directly by auth.ts for session management.
  • invokeRpc<T>(functionName, params?) — exported helper imported by every server-authoritative mutation in the metagame layer.

Pattern notes

  • Fail-fast env validation. Missing env vars throw at module import, not on first use, so a misconfigured build never reaches runtime in a half-working state.
  • Single client. One createClient call at module top level; consumers import the shared instance rather than constructing their own.
  • All mutations through invokeRpc. Direct .from() chains are restricted to auth.ts. This keeps the RPC surface auditable and gives one place to add cross-cutting concerns (offline guard, breadcrumbs, error formatting).
  • Breadcrumbs not events. Failed RPCs add a Sentry breadcrumb so a downstream uncaught throw carries the RPC context, without flooding Sentry with one issue per failed call during an outage.
  • Offline guard via global, not import. isOffline() reads window.__PLAYER_STORE_OFFLINE__ instead of importing playerStore to avoid a circular dependency between the client module and the store that consumes it.
  • Generic-typed return. invokeRpc<T> is generic with T = unknown default; callers narrow with their own row/payload types at the call site.
  • auth.persistSession: true with localStorage keeps a signed-in user across reloads on the same device.