ValetAlpha

Local-first sync
that just works

Define queries and mutations once. Run them instantly on a local SQLite replica or on the server.

Get started
npx create-valet-app

Try it — toggle a checkbox

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

Offline-first

Works offline

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

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

Selective sync

Only sync what you need

The server database has all the rows. Sync filters control which ones reach each client. Try reassigning a task.

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

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.

Click the buttons on both clients, then hit Reconnect.

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(),
})

Deterministic replay

Write normal JavaScript

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

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,
    })
  },
})
SQLiteWebSocketRustTypeScriptReact

Why Valet

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

Local + server queries

Run queries instantly on a local SQLite replica, or on the server for full-dataset operations.

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.

ConvexLimited offline
InstantDBLWW conflicts
ZeroLWW conflicts
SupabaseNo offline

Deterministic replay

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

ConvexServer-only
InstantDBLWW
ZeroNo captured env
SupabaseNo sync

No vendor lock-in

Standard SQLite on both client and server. Your data is always portable.

ConvexCustom DB
InstantDBManaged only
ZeroPostgres only
SupabaseManaged Postgres