Back to Blog

Announcing Supamachine: A Deterministic State Machine for Supabase Authentication

WebSupabaseexpoMobileSoftware
/

Announcing Supamachine: A Deterministic State Machine for Supabase Authentication

I was working on a somewhat complex client mobile app, using Supabase as a BaaS and to manage authentication, and my SB onAuthStateChange callback had gotten, well, gnarly.
const { data: { subscription }, } = supabase.auth.onAuthStateChange((event, session) => { if (event === "INITIAL_SESSION" || event === "TOKEN_REFRESHED") { setLoading(false); ...etc... } if (session) { setSession(session); ...etc... } else { setSession(null); ...etc... } ...and so much more of this });
And one night—I am not making this up—I actually dreamt about writing a state machine to replace it with.
I woke up thinking, “Oh. This is why we have state machines.”
The app I was working on had a lot of states, and each additional state means additional questions about how the new state affects the other states. Think onboarding, welcome screen, password reset, profile loading, and on and on.
I like event-driven architectures as much as the next nerd, but every once in awhile you’re reminded of the existence of declarative programming and you feel a little stupid for spending so much time in agony.
So long story short(er), I whipped up Supamachine, a deterministic state machine for Supabase auth. When I replaced my original client code with the SM version, it reduced my auth LOC by 84%! But more importantly than LOC, it made it far, FAR easier to reason about the complex web of states the app required, and way more reliable.
I considered making it backend- and UI-framework-agnostic, but decided for this first pass to focus on Supabase and React since that what I was already used for the project in question. If people find this useful and the API is proved out I’d like to evolve this into a ‘proper’ version that can be used with any backends and frontends.

Usage

Supamachine’s basic ideas are:
  • Instead of handling Supabase’s onAuthStateChange yourself, let Supamachine handle it
  • Define a configuration of custom app states (if necessary)
  • Use Supamachine’s state to derive view state.
Roughly like this:
import { SupamachineProvider, useSupamachine, AuthStateStatus, } from "@inventbuild/supamachine"; type MyContext = { userData: { name: string } }; type MyAppState = { status: "MAIN_APP"; session: Session; context: MyContext }; return ( <SupamachineProvider<MyContext, MyAppState> supabase={supabase} loadContext={async (session) => { const { data } = await supabase .from("profiles") .select("*") .eq("id", session.user.id) .single(); return { userData: data }; }} mapState={(snapshot) => ({ status: "MAIN_APP", session: snapshot.session, context: snapshot.context!, })} > <App /> </SupamachineProvider> ); function App() { const { state, updateContext } = useSupamachine<MyContext, MyAppState>(); switch (state.status) { case AuthStateStatus.CHECKING_SESSION: case AuthStateStatus.CONTEXT_LOADING: return <Loading />; case AuthStateStatus.SIGNED_OUT: return <Login />; case "MAIN_APP": return <Home session={state.session} />; default: return <Loading />; } }
The magic really happens in the useSupamachine hook, which deterministically manages auth state via a standard reducer pattern.
So in the most basic scenario, all you need to do is:
  1. Give useSupamachine your Supabase client
  1. Provide a basic loadContext function that gets the user’s profile
  1. Wrap your app in <SupamachineProvider>
  1. Get auth state from useSupamachine and use it to declaratively render your app.
The core state machine looks like this:
notion image

Philosophy

When I was sketching out the first drafts of Supamachine, I came up with this core philosophy:
  • Supamachine explicitly defines every possible state your app’s auth scenario requires.
  • Custom states can be provided via mapState .
  • The entire system is deterministic by way of being completely defined.
  • Your app code reacts to one single, public, strongly-typed AppState .
  • The Supamachine core contains no React, Expo, or Supabase-specific code. Platform and provider integrations live in adapters/bindings.
But underneath all that, Supamachine is built around strong invariants. Every state represents a contract: if the machine is in a given state, certain facts are guaranteed to be true. For example, CHECKING guarantees authentication is still being resolved, SIGNED_OUT guarantees no session exists, and APP_READY guarantees a valid session and fully loaded application context. Because these guarantees are encoded into both the runtime state machine and the TypeScript types, consumers simply look at the current state and know they can rely on its invariants.

Try it Out

If you’re using Supabase and you’d like to try Supamachine out, the Readme contains all the documentation you need, and there’s an examples directory too, as well as two full-blown demo apps: a Next.js web app and an Expo mobile app.
And there’s also a Supamachine Agent Skill to teach your agent how to use it!
If you try this out, I’d love to hear if this is as useful for you as it’s been for me.

Discussion

COMMENTS

Thoughts? Email me at [email protected]—I may publish thoughtful replies.

No comments yet. Be the first to share your thoughts!