Joqi

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-orm

The 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 compiled SQLPlan.

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.
  • $param references must be present in params.
  • Filter params must match the resolved field type.
  • limit and offset params must be non-negative integers.

If validation fails, the executor is not called.

On this page