aroraql-client
v0.1.0
Published
Supabase-inspired, type-safe fluent query builder that turns chained calls into a JSON payload and POSTs it to your AroraQL endpoint
Maintainers
Readme
aroraql-client
Supabase-inspired, type-safe fluent query builder for the frontend. Chain calls, get a JSON payload, and let the client POST it to your AroraQL endpoint — results come back mapped to your types.
const employees = await arora
.from<Employee>("Employees")
.select("Id", "Name", "Department.Name")
.where("Age")
.gt(18)
.where("Country")
.eq("Egypt")
.orderBy("Name")
.asc()
.take(50)
.many(); // Employee[]- Zero dependencies — just
fetch - Fully generic — field names and value types checked against your row type
- Runs anywhere — Bun, Node 18+, and browsers
Install
bun add aroraql-client
# or
npm install aroraql-clientSetup
The client auto-detects the endpoint from the AroraQL_Api environment variable:
# .env
AroraQL_Api=https://api.example.com/queryimport { createClient } from "aroraql-client";
const arora = createClient();Or configure it explicitly (required in browsers, where env variables don't exist):
const arora = createClient({
url: "https://api.example.com/query",
headers: { authorization: `Bearer ${token}` }, // optional, sent on every request
});Usage
Define your row type once and every part of the query is checked against it:
type Employee = {
Id: number;
Name: string;
Age: number;
Country: string;
Department: { Name: string };
};Fetch many rows
const rows = await arora
.from<Employee>("Employees")
.where("Age")
.gte(18)
.orderBy("Name")
.asc()
.many(); // Employee[]Fetch a single row
// First match or null — never throws on "not found"
const employee = await arora
.from<Employee>("Employees")
.where("Id")
.eq(42)
.maybeSingle(); // Employee | null
// Exactly one row — throws unless precisely 1 match
const employee = await arora
.from<Employee>("Employees")
.where("Id")
.eq(42)
.single(); // EmployeeBuild without fetching
const payload = arora
.from<Employee>("Employees")
.select("Id", "Name")
.where("Country")
.eq("Egypt")
.build();
// { from: "Employees", select: ["Id", "Name"],
// where: [{ field: "Country", op: "=", value: "Egypt" }], orderBy: [] }API
Query builder
| Method | Description |
| ----------------------------------- | -------------------------------------------------------------------------------- |
| .from<T>(table) | Start a query. T types everything downstream. |
| .select(...fields) | Project fields. Supports dotted paths ("Department.Name"). Empty = all fields. |
| .where(field) | Start a condition — follow with an operator (below). |
| .orderBy(field).asc() / .desc() | Sort. Chain multiple for multi-field ordering. |
| .take(n) | Limit the number of rows. |
Where operators
| Method | SQL equivalent |
| ---------------------------- | ------------------------------------ |
| .eq(value) | = |
| .ne(value) | != |
| .gt(value) / .gte(value) | > / >= |
| .lt(value) / .lte(value) | < / <= |
| .like(pattern) | LIKE (% any chars, _ one char) |
Operator values are typed as T[K] — where("Age").eq("old") is a compile error.
Executors
| Method | Returns | Behavior |
| ---------------- | -------------------- | --------------------------------------------------- |
| .many() | Promise<T[]> | All matching rows. |
| .maybeSingle() | Promise<T \| null> | First row or null. |
| .single() | Promise<T> | Exactly one row — throws AroraQLError on 0 or 2+. |
| .build() | QueryPayload | The raw JSON payload, no request made. |
Backend contract
The client sends a single POST with a JSON body:
{
"from": "Employees",
"select": ["Id", "Name", "Department.Name"],
"where": [{ "field": "Age", "op": ">", "value": 18 }],
"orderBy": [{ "field": "Name", "dir": "asc" }],
"take": 50
}Your endpoint maps this onto any data source and responds with:
{ "data": [ ... ] }or, on failure (any status):
{ "error": "Unknown table: Employes" }Errors are surfaced as AroraQLError (with .status). A minimal Bun endpoint:
import type { QueryPayload } from "aroraql-client";
Bun.serve({
port: 3123,
async fetch(req) {
const payload = (await req.json()) as QueryPayload;
return Response.json({ data: runQuery(payload) }); // map payload -> your DB
},
});All payload types (QueryPayload, WhereCondition, OrderByClause, Operator) are exported so your backend can share them.
License
MIT
