Joqi

Registry Design

Build the trusted public query surface from physical database facts and app policy.

The registry is the safety boundary. A caller can only query what appears in the resolved registry.

PhysicalRegistry

The physical registry describes database or ORM facts. It is trusted input created by your application or adapter.

{
  "version": "v1",
  "sources": {
    "placements": {
      "kind": "table",
      "name": "placements",
      "fields": {
        "id": { "type": "string", "nullable": false },
        "name": { "type": "string", "nullable": false },
        "status": { "type": "enum", "nullable": false },
        "budgetCents": { "type": "number", "nullable": false },
        "campaignId": { "type": "string", "nullable": false }
      },
      "relations": {
        "campaign": {
          "kind": "one",
          "target": "campaigns",
          "localFields": ["campaignId"],
          "foreignFields": ["id"]
        }
      }
    }
  }
}

Supported source kinds are table, view, and model.

Supported field types are string, number, boolean, date, datetime, json, enum, and unknown.

RegistryDefaults

Defaults let you choose a baseline. A conservative setup starts deny-by-default and opts fields in explicitly.

import type { RegistryDefaults } from "@ypanagidis/joqi";

export const defaults = {
  exposure: "deny-by-default",
  source: { selectable: true, filterable: true, sortable: true, maxLimit: 100 },
  field: {
    selectable: true,
    filterable: false,
    sortable: false,
    groupable: false,
    operators: "byType",
  },
  relation: { selectable: false, filterable: false, maxDepth: 1 },
} satisfies RegistryDefaults;

Use allow-by-default only when your physical registry is already a curated public model.

RegistryPolicy

The policy exposes sources, renames fields, and grants capabilities.

import type { Policy } from "@ypanagidis/joqi";

export const policy = {
  version: "v1",
  sources: {
    placements: {
      expose: true,
      exposeAs: "placement",
      label: "Placement",
      defaultLimit: 25,
      fields: {
        name: { expose: true, filterable: true, sortable: true },
        status: { expose: true, filterable: true, sortable: true },
        budgetCents: {
          expose: true,
          exposeAs: "budget",
          type: "number",
          filterable: true,
          sortable: true,
          aggregations: ["sum", "avg"],
          operators: ["eq", "gt", "gte", "lt", "lte"],
        },
      },
      relations: {
        campaign: {
          expose: true,
          target: "campaign",
          selectable: true,
          filterable: true,
          maxDepth: 1,
        },
      },
    },
    campaigns: {
      expose: true,
      exposeAs: "campaign",
      label: "Campaign",
      fields: {
        name: { expose: true, filterable: true, sortable: true },
      },
    },
  },
} satisfies Policy<typeof physical>;

The Policy<typeof physical> helper gives TypeScript autocomplete for physical source, field, and relation names.

ResolvedRegistry

resolveRegistry merges physical facts, defaults, and policy into the final public model.

import { resolveRegistry } from "@ypanagidis/joqi";

const registry = resolveRegistry({ physical, defaults, policy });

Applications often resolve per request. For example, one user may get a wider policy than another user, or a tenant may have a different maximum limit.

On this page