Browser
Browser Extension
Section titled “Browser Extension”The uql-orm/browser extension allows you to consume UQL REST APIs (served by the HTTP core or the Express Extension) on the frontend with the same type-safe syntax you use on the backend.
Quick Start
Section titled “Quick Start”-
Initialize the HttpQuerier:
import { HttpQuerier } from 'uql-orm/browser';// Configure the frontend to point to your backend APIconst querier = new HttpQuerier('https://api.yourdomain.com/api'); -
Query Data: Interact with your entities as if you were on the backend.
import { User } from './shared/models/index.js';const { data: users } = await querier.findMany(User, {$select: { email: true },$populate: { profile: { $select: { picture: true } } },$where: { email: { $endsWith: '@domain.com' } },$sort: { createdAt: 'desc' },$limit: 10});
Client API
Section titled “Client API”Every wire operation has a typed method: findMany, findManyAndCount, findOne, findOneById, count, insertOne, insertMany, saveOne (upsert via PUT), saveMany, updateOneById, updateMany, deleteOneById, and deleteMany. Responses are { data, count? }.
Failed requests throw a RequestError carrying the server’s message and the numeric HTTP status, so status-driven flows (401 redirect to login, 402 payment required, error-boundary routing) work without string matching:
import { RequestError } from 'uql-orm/browser';
try { await querier.findMany(User, {});} catch (err) { if (err instanceof RequestError && err.status === 401) { location.href = '/login'; }}Long-running or cancelable calls take an AbortSignal:
await querier.findMany(User, q, { signal: AbortSignal.timeout(120_000) });HTTP QUERY transport
Section titled “HTTP QUERY transport”Opt in to the new QUERY method (RFC 10008) to send read queries in the request body instead of the URL, avoiding URL-length limits for large $where/$populate queries:
const querier = new HttpQuerier('/api', { readMethod: 'QUERY' });findOne, findMany, and count then use QUERY; writes and by-id reads keep their canonical methods. The default stays GET because cross-origin QUERY requires a CORS preflight and some proxies/CDNs do not forward the method yet; the server accepts both transports simultaneously, so you can switch per client.
For non-CRUD action endpoints (/api/payments/checkout, /api/resources/regenerate, …), the underlying typed helpers get, post, put, patch, remove, and query are exported from uql-orm/browser as an escape hatch; they share the same envelope, headers, notifications, and RequestError behavior.
The mapping stays in sync with the server automatically: URLs derive from the shared CRUD_ROUTES contract in uql-orm/http, and a compile-time check guarantees the client covers every wire operation.
Shared Entities & Type Safety
Section titled “Shared Entities & Type Safety”Because queries are validated against your entity classes, you can share those classes between backend and frontend and get the same types on both sides.
import { Entity, Id, Field } from 'uql-orm';
@Entity()export class User { @Id() id?: string;
@Field() name?: string;}
// frontend/app.tsimport { User } from '../shared/models/User.js';const { data: users } = await querier.findMany(User, { $where: { name: { $startsWith: 'A' } }});// 'users' is automatically typed as User[]Authentication & Headers
Section titled “Authentication & Headers”Pass headers per call for JWT tokens or other credentials; they are merged over the JSON defaults:
const users = await querier.findMany(User, { $where: { status: 'active' }}, { headers: { 'Authorization': `Bearer ${session.token}`, 'X-Custom-Header': 'value' }});For SSR, or to avoid repeating the token on every call, set instance-level default headers. Per-call headers win over instance defaults:
// one scoped instance per server-side request; no shared global stateconst querier = new HttpQuerier('/api', { headers: { Authorization: `Bearer ${session.token}` },});Request notifications
Section titled “Request notifications”uql-orm/browser exposes a small pub/sub bus (on) emitting start, success, error, and complete phases per request, handy for a global loading spinner in vanilla apps. It is optional: libraries like React Query already track loading/error state, so pass { silent: true } and skip the bus there.