Quickstart
Install Joqi, create a runtime, and run your first public query.
Most apps should start with createQueryRuntime. The runtime wraps the full pipeline: registry resolution, query parsing, validation, param binding, SQL compilation, adapter execution, and result validation.
Install
pnpm add @ypanagidis/joqi @ypanagidis/joqi-drizzle drizzle-ormThe docs use the Drizzle adapter because it can create a physical registry from Drizzle relation metadata and execute Joqi SQL plans.
Define a query template
The public query uses public names, not SQL identifiers.
const activePlacementReport = {
version: "v1",
source: "placement",
select: ["name", "status", "budget", "campaign.name"],
where: {
and: [
{ field: "status", op: "eq", value: { $param: "status" } },
{ field: "budget", op: "gte", value: { $param: "minBudget" } },
{ field: "campaign.name", op: "contains", value: { $param: "campaignName" } },
],
},
orderBy: [{ field: "budget", direction: "desc" }],
limit: { $param: "limit" },
};This object is safe to store as JSON. Runtime values are passed separately through params.
Create the runtime
import { createQueryRuntime } from "@ypanagidis/joqi";
import { drizzleExecutor } from "@ypanagidis/joqi-drizzle";
const runtime = createQueryRuntime({
db,
physicalRegistry: physical,
defaults,
policy,
dialect: "postgres",
executor: drizzleExecutor(),
});The runtime needs five things:
db: the database object used by the adapter.physicalRegistry: trusted database facts, usually generated from an ORM adapter.defaults: default exposure and capability settings.policy: the app-controlled public query surface.executor: an adapter that runs the compiledSQLPlan.
Run it
const result = await runtime.run({
spec: activePlacementReport,
params: {
status: "active",
minBudget: 10000,
campaignName: "spring",
limit: 25,
},
explain: true,
});
console.log(result.rows);
console.log(result.explain.sqlPlan);When explain: true is passed, result.explain is typed as present and includes the resolved registry, QueryIR, and SQLPlan.
What happens before SQL executes
Joqi validates before execution:
- The query shape must match
QuerySpecSchema. - The source must exist in the resolved registry.
- Every selected, filtered, and sorted field must be exposed for that operation.
- Relation paths must stay within configured depth.
$paramreferences must be present inparams.- Filter params must match the resolved field type.
limitandoffsetparams must be non-negative integers.
If validation fails, the executor is not called.