Apps and profiles
defineApp binds the data sources, opt-in features and services an app wires once (memberships, custom roles, relations, hosted policies, approval policies, limits, sink, approval store) to a policy, so createPermDock and every adapter read them from one place; profile dev fills in in-process defaults and profile prod refuses to start when a grant needs a service the app lacks.
defineApp(policy, options) returns the policy with the app's sources, features and services bound to it. Pass the result wherever a policy goes: core createPermDock, every adapter and the CLI. Each one reads the app definition, and its own options win key by key.
// src/permdock.ts
import {
approvalPolicies,
defineApp,
hostedPolicies,
limits,
memoryLimitStore,
} from "permdock";
import { memoryApprovalStore } from "permdock/approvals";
import { policy } from "./policy";
export const app = defineApp(policy, {
memberships, // MembershipSource, or several
customRoles, // CustomRoleSource
relations, // RelationSource
approvalPolicies: approvalPolicies(approvalPolicySource),
policies: hostedPolicies(policySource),
limits: limits(memoryLimitStore()),
sink,
store: memoryApprovalStore(),
profile: process.env.NODE_ENV === "production" ? "prod" : "dev",
});// src/server.ts
import { createPermDock } from "permdock/hono";
import { app } from "./permdock";
export const { protect, middleware } = createPermDock(app, { subject });What an app holds
| Key | What | Without it |
|---|---|---|
memberships, customRoles, entitlements, relations, principals, settings | The extension interfaces that read the application's own data | Memberships come from the principal; no custom roles, entitlements or relations |
policies | hostedPolicies(source): hosted grants on hostable permissions | The code policy only |
approvalPolicies | approvalPolicies(source), or a memoryApprovalPolicySource([...]) as is | Approval requirements from code only |
limits | limits(store): counts limit grants in a LimitStore | A grant with limit denies with limit-unavailable |
sink | A DecisionSink | No decision log |
store | The ApprovalStore every adapter opens and resumes approvals in | approval-required cannot be resumed |
profile | "prod" (default) or "dev" | "prod" |
hostedPolicies, approvalPolicies and limits are features, not bare sources. The code that merges hosted grants, tightens approvals and counts quotas ships only with an app that imports them, so a plain RBAC app does not carry it.
Profiles
devfills in in-process defaults: one memoryApprovalStorethat every adapter of the app shares, so a request opened through one adapter resumes through another, andlimits(memoryLimitStore())when a grant has alimit. The defaults come fromdefineApp, never from an adapter, so an adapter bundle carries no store. A new app runs before any infrastructure exists. Memory stores are per process: their state is lost on restart and is not shared between instances.prodfills in nothing.defineAppthrows at startup when a grant requires approval and there is nostore, or a grant has alimitand there are nolimits. The alternative is an app that starts and then denies every such call at run time.
No adapter falls back to a memory store on its own. An approval store exists because the app or the adapter call named one, or because the profile is dev.
Why
Before defineApp, each adapter took its own copy of the same sources and services, and the CLI and the runtime named the same tables twice. A team that added a second surface (an MCP server next to a Next.js app) wired everything again and could miss the sink or the store on one of them. One app definition removes that duplication without a new interface: the result is still a Policy, so no adapter signature changes and an adapter option still overrides the app.
The profile makes the trade-off between a quick start and a safe production setup explicit. A memory store is right for the first hour and wrong in production. The dev profile gives it, prod refuses to start without the real one, and the choice is one line the reader can see.
Last updated on
Policies
Grants name a grantee with to selectors; definePolicy binds typed vocabulary, a subject mapper, and portable conditions.
Presets
A preset is an archetype plus the features, auth, backend, framework, agent runtimes, UI and PermDock Cloud services an app needs, encoded as a short versioned code; permdock/preset generates the application's own files from it, the same in permdock init, the @permdock registry and the /create page.