The schema
The schema is the root type definition for your authorization domain. Every role name, resource name, and action string flows from a single interface extending SchemaDefinition.
Defining a schema
import type { SchemaDefinition } from "@siremzam/sentinel";
interface AppSchema extends SchemaDefinition {
roles: "owner" | "admin" | "member" | "viewer";
resources: "invoice" | "project" | "settings";
actions:
| "invoice:create"
| "invoice:read"
| "invoice:approve"
| "project:read"
| "settings:update";
}Fields
| Field | Required | Purpose |
|---|---|---|
roles | Yes | Union of role identifiers assigned to subjects |
resources | Yes | Union of resource types being protected |
actions | Yes | Union of actions — conventionally `${resource}:${verb}` |
tenantId | No | Union of tenant identifiers if you want branded tenant strings |
actions should extend ActionString (`${string}:${string}`). TypeScript enforces the colon-separated shape.
How the schema propagates types
When you write AccessEngine<AppSchema>, these inferred types become available everywhere:
InferRole<AppSchema>→"owner" | "admin" | …InferResource<AppSchema>→"invoice" | "project" | …InferAction<AppSchema>→ your action unionInferTenantId<AppSchema>→ your tenant union orstring
Policy builders, evaluate(), middleware, and Subject all use the same AppSchema generic parameter.
The runtime schema option
const engine = new AccessEngine<AppSchema>({
schema: {} as AppSchema,
});Important: The schema property is not validated at runtime. It is an empty object cast so TypeScript can infer S in the constructor. Your real source of truth is the AppSchema interface in your codebase.
Do not expect runtime errors if someone passes a wrong action at runtime from untyped JavaScript — TypeScript is the primary guard; add runtime validation at API boundaries if you accept untyped input.
Evolving the schema
Adding a new action or role is a compile-time migration:
- Extend the interface union.
- Fix compile errors in policies and call sites.
- Deploy.
Removing an action forces you to delete or update every rule and evaluate() that referenced it — the compiler lists all locations.