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
- Start with a table:
db.from('<table>') - Chain filters/modifiers (e.g.,
.where(...),.order(...),.limit(...)) - 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.