Query builder
biref.query<BirefSchema>(model) returns a two-layer Proxy keyed by namespace then entity. Every leaf is a fresh TypedChain.
const q = biref.query<BirefSchema>(model);
const users = q.public.users; // TypedChain bound to public.users
const products = q.catalog.products; // TypedChain bound to catalog.products
The chain is immutable -- every method returns a new chain.
select()
Project a subset of columns:
// Specific fields (narrows the return type)
q.public.users.select('id', 'email');
// Wildcard (all fields)
q.public.users.select('*');
// Zero-arg shorthand for wildcard
q.public.users.select();
Rules:
- Every named field must exist on the entity. Unknown names throw at chain time.
- Mixing
'*'with named fields throws.
where()
Filter by predicate. Operators narrow automatically based on the field's type category:
q.public.users
.where('status', 'eq', 'active')
.where('age', 'gte', 18)
.where('email', 'ilike', '%@example.com')
.where('deleted_at', 'is-null')
.where('role', 'in', ['admin', 'editor']);
Operators by category
| Category | Operators |
|---|---|
string, uuid, enum | eq, neq, in, not-in, like, ilike, is-null, is-not-null |
integer, decimal, date, timestamp, time | eq, neq, lt, lte, gt, gte, between, in, not-in, is-null, is-not-null |
boolean | eq, neq, is-null, is-not-null |
Value shapes
| Operator | Value |
|---|---|
eq, neq, lt, lte, gt, gte, like, ilike | scalar |
in, not-in | readonly T[] |
between | readonly [min, max] |
is-null, is-not-null | no value (two-arg form) |
orderBy()
q.public.orders
.orderBy('placed_at', 'desc')
.orderBy('id', 'asc');
Direction defaults to 'asc'.
limit() / offset()
q.public.products
.limit(10)
.offset(20);
include()
Attach related entities as nested results:
// Shorthand (default projection)
q.public.users
.select('id', 'email')
.include('orders');
// With sub-builder (narrowed projection)
q.public.users
.select('id', 'email')
.include('orders', (order) =>
order
.select('id', 'total', 'status')
.where('status', 'neq', 'cancelled')
.include('order_items', (item) =>
item.select('product_id', 'quantity'),
),
);
// Wildcard (every relation, default projection)
q.public.users
.select('id', 'email')
.include('*');
Include works in both directions:
.include('orders')onuserswalks the inbound hop (orders reference users).include('user')onorderswalks the outbound hop (orders declare the FK)
Cardinality:
- many (inbound): result is
readonly T[] - one (outbound): result is
T | null
Terminal methods
// All matching rows
const rows = await q.public.users.select('id', 'email').findMany();
// First match (applies LIMIT 1)
const row = await q.public.users
.select('id', 'email')
.where('email', 'eq', 'alice@example.com')
.findFirst();
// Returns T | null
JavaScript usage (no codegen)
Skip the schema generic and everything still works with dynamic types:
const q = biref.query(model);
const rows = await q.public.users.select('id', 'email').findMany();
// rows is readonly Record<string, ParsedValue>[]