Valet

Why Valet

When Valet is the right choice -- and when it is not.

There are a lot of great tools in this space. Convex, Supabase, InstantDB, Zero, and LiveStore are all excellent projects solving real problems, and we are fans of all of them. We built Valet because we wanted a specific combination of trade-offs that did not exist yet.

This page explains when Valet is a good fit, how it differs from alternatives, and -- equally important -- when you should use something else.

What makes Valet different

One API, two execution modes

Every query and mutation in Valet uses the same defineQuery / defineMutation API and the same ctx.db interface. The only difference is a single line:

valet/tasks.ts
import { defineQuery, v } from '../_generated/valet/api'

// Runs on the client -- instant, offline-capable
export const myTodos = defineQuery({
    args: {},
    execution: 'local',
    handler: async (ctx) => ctx.db.query('tasks').collect(),
})

// Runs on the server -- full dataset, cross-user access
export const search = defineQuery({
    args: { term: v.string() },
    execution: 'server',
    handler: async (ctx, args) => {
        return ctx.db
            .query('tasks')
            .filter((q) => q.eq('title', args.term))
            .collect()
    },
})

Both produce the same typed React hooks. Both use the same query builder. You learn one API and pick the execution mode that fits.

Rebase conflict resolution

When a client goes offline, mutations queue up. On reconnect, Valet replays those mutations against fresh server state -- so read-then-write patterns produce correct results, not stale ones.

This is conceptually similar to git rebase: your changes replay on top of the latest state rather than merging in parallel. For operations like counter increments, this means both offline changes are preserved rather than one overwriting the other.

Deterministic offline replay

The hard part of replaying mutations is non-determinism. If your handler calls crypto.randomUUID(), it would generate a different ID on replay. Valet patches JavaScript globals during handler execution:

GlobalBehavior
crypto.randomUUID()Captured on first execution, replayed identically
Math.random()Captured on first execution, replayed identically
Date.now()Frozen to the captured timestamp
new Date()Uses the patched Date.now(), so also deterministic

You write normal JavaScript. No special APIs:

valet/tasks.ts
import { defineMutation, v } from '../_generated/valet/api'

export const create = defineMutation({
    args: { title: v.string() },
    handler: async (ctx, args) => {
        const id = crypto.randomUUID() // deterministic on replay
        const now = Date.now()          // deterministic on replay

        return ctx.db.insert('tasks', {
            title: args.title,
            externalId: id,
            createdAt: now,
        })
    },
})

Managed hosting with scale-to-zero

Valet is hosted by Expo with automatic scale-to-zero. The orchestrator manages server instances for you:

  • Scale-to-zero: Idle projects shut down after a configurable timeout. No always-on infrastructure cost.
  • Scale-on-demand: When a client connects, the orchestrator spawns a server instance in 1-3 seconds.
  • Portable data: Your data lives in standard SQLite -- no proprietary format.

Cross-platform support

Valet runs on web and Expo. The same queries, mutations, and handlers work across all platforms. On web, the local database uses SQLite compiled to WebAssembly. On React Native, it uses native SQLite. The sync protocol and rebase model are identical on every platform.

How others approach it

Every tool in this space makes different trade-offs. Here is how we see the landscape:

Convex is a polished, fully-managed backend with excellent DX. It runs all logic on the server, which means consistent behavior and strong guarantees. The trade-off is that every read is a network round-trip -- there is no local query execution for instant reads. Great choice if you want a managed platform and do not need offline or local-first reads.

InstantDB provides a reactive client-side database with a clean API. Queries run locally for instant reads, and it handles real-time sync well. Server-side logic uses a separate API (transactions on the backend). A good pick for apps where local-first reads matter and you are comfortable with a managed service.

Zero is a local-first sync engine built on Postgres. It syncs data to the client for instant queries and supports self-hosting. Server-side logic runs through your own backend. Strong option if you are already invested in Postgres and want local-first reads with self-hosting.

Supabase gives you a full Postgres database with auth, storage, and edge functions. It is server-first -- reads go over the network -- but it is self-hostable and has a huge ecosystem. The right choice if you want a traditional backend with great tooling and do not need local-first architecture.

LiveStore is a client-only reactive store. It is fast and elegant for local state, but does not include server-side execution or sync. Worth considering if your app is purely local or you want to bring your own sync layer.

When to use Valet

Valet is a good fit when:

  • You are building a React app (web or Expo) that needs offline support and real-time sync
  • You want instant local reads without giving up server-side capabilities
  • You want managed hosting with scale-to-zero and no always-on cost
  • Your mutations need to produce correct results after offline replay (not field-level last-write-wins)

When to use something else

We would rather you pick the right tool than pick Valet for the wrong reasons:

  • You need file storage, vector search, or other platform services today -- Supabase and Convex have broader ecosystems right now. These features are on our roadmap but not yet available.
  • Your app does not need offline or local-first reads -- If every operation can go through the server and latency is acceptable, a traditional backend (Supabase, Convex) is simpler and more mature.
  • You are already happy with your current stack -- Seriously. If what you have works, there is no reason to switch. Valet is worth considering when you hit the specific pain points it solves.

On this page