Valet|by

One API for client
and server

Valet is a sync engine for React. Define your queries and mutations once, then run them locally for instant reads or on the server for full-dataset access.

bunx valet-dev init

Instant reads

Queries run against a local SQLite database in the browser. No network round-trip, no loading spinner. Try both — the Valet version responds instantly.

LocalValet
  • Learn about Valet
  • Define a schema
  • Write queries
ServerTraditional
  • Learn about Valet
  • Define a schema
  • Write queries

Works offline

Toggle sync off and make changes — they queue up locally. Toggle sync back on and everything catches up. Mutations replay deterministically.

Connected
Your device
  • Buy groceries
  • Walk the dog
  • Ship feature
Another device
  • Buy groceries
  • Walk the dog
  • Ship feature

Only sync what you need

The server database has all the rows. Sync filters control which ones reach each client. Try reassigning a task — it moves between devices instantly. No over-fetching, no client-side filtering.

Alice
2 rows
  • Review PR #42
  • Update docs
Database4 rows
  • Review PR #42
  • Fix login bug
  • Update docs
  • Deploy staging
Bob
2 rows
  • Fix login bug
  • Deploy staging

Under the hood

Conflict resolution

Replay, not merge

Most sync engines merge values with last-write-wins. Valet replays each mutation against current server state so both changes are preserved.

Try it: click the buttons on both clients, then hit Reconnect to watch each operation replay on the server.

Both clients offline — make some changes
Client A
counter

10

Client B
counter

10

Dual execution

One API, two databases

Same function signature. Switch between client SQLite and your server DB with one line.

// Local — instant, offline
export const list = defineQuery({
  execution: 'local',
  handler: async (ctx) =>
    ctx.db.query('todos').collect(),
})

// Server — full dataset access
export const search = defineQuery({
  execution: 'server',
  handler: async (ctx, args) =>
    ctx.db.query('todos')
      .filter(q => q.eq('title', args.term))
      .collect(),
})

// Mutations work on both
export const create = defineMutation({
  handler: async (ctx, args) => {
    return ctx.db.insert('todos', {
      title: args.title,
      completed: false,
      createdAt: Date.now(),
    })
  },
})
Deterministic replay

Write normal JavaScript

crypto.randomUUID(), Math.random(), and Date.now() are automatically captured and replayed identically offline.

export const create = defineMutation({
  handler: async (ctx, args) => {
    const id = crypto.randomUUID()
    const now = Date.now()

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

Scale-to-zero

Hosted by Expo. Idle projects shut down automatically. No always-on cost. Standard SQLite — your data is always portable.

SQLiteWebSocketRustTypeScript

One codebase. Every platform.

Built by Expo. First-class React Native support with native SQLite — the same hooks and queries, running natively on every platform.

Webwa-sqlite in the browser
iOSNative SQLite via Expo
AndroidNative SQLite via Expo

How Valet compares

There are great tools in this space. Here's where Valet fits.

Local + server queries

Run queries instantly on a local SQLite replica, or on the server for full-dataset operations. Same defineQuery API — just change one line.

ConvexServer queries
InstantDBLocal queries
ZeroLocal queries
SupabaseServer queries

Offline-first with rebase

Mutations queue locally and replay against server state on reconnect. No last-write-wins data loss — every operation is preserved.

ConvexLimited offline
InstantDBLWW conflicts
ZeroLWW conflicts
SupabaseNo offline

Deterministic replay

crypto.randomUUID(), Math.random(), and Date.now() are captured on first execution and replayed identically through offline sync and server re-execution.

ConvexServer-only
InstantDBLWW
ZeroNo captured env
SupabaseNo sync

No vendor lock-in

Standard SQLite on both client and server. Your data is always portable — no proprietary storage format, no proprietary query language.

ConvexCustom DB
InstantDBManaged only
ZeroPostgres only
SupabaseManaged Postgres

Read the full comparison →