Core Concepts — Query Builder (Fluent Chain)

The query builder is a fluent, typed chain you call on the client. You compose filters/modifiers first, and .select(...) executes the query. The same surface works across adapters (Web/Expo/Supabase).


Mental model

  1. Start with a table: db.from('<table>')
  2. Chain filters/modifiers (e.g., .where(...), .order(...), .limit(...))
  3. Call .select(projection) — this runs the query and returns typed rows

Use string projections for relations, e.g. 'id, title, users(name)'.


Select (read)

1// basic list
2const todos = await db
3  .from('todos')
4  .select('id, title')
5
6// filtered + ordered + projected (relations)
7const userTodos = await db
8  .from('todos')
9  .where({ user_id: 1, completed: false })
10  .order('created_at', { ascending: false })
11  .limit(20)
12  .select('id, title, created_at, users(name)')

Insert (create)

1await db
2  .from('todos')
3  .insert({ id: 't2', title: 'Ship docs', user_id: 1, completed: false })

Update (edit)

1await db
2  .from('todos')
3  .where({ id: 't2' })
4  .update({ completed: true })

Delete (remove)

1await db
2  .from('todos')
3  .where({ id: 't2' })
4  .delete()

Projections & typing

  • The projection string determines the returned shape.
  • Nested fields follow relations you declared in the schema:
    select('id, title, users(name)') → each row has { id, title, users: { name } }.
  • Types are inferred from your schema’s column kinds and relations graph.

Tips

  • Prefer a single, clear .where({...}); compose additional filters in code if needed.
  • Keep projections minimal to reduce payload and improve readability.
  • Remember: filters/modifiers first, .select(...) last.

Summary

One fluent chain for select / insert / update / delete. Compose intent first, then execute with .select(...). The API is identical across environments; only the adapter changes where the data lives.