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.