Manage roles
npx warlock add access ejects two models into your app: Role (the catalog — each role and the permissions it grants) and UserRole (assignments — which user holds which role, in which tenant). Both live under src/app/access/models/, so you own and evolve them. Assignment is a write to UserRole; the catalog is a row in Role.
Assign and revoke
Section titled “Assign and revoke”Use the UserRole statics. assign and revoke auto-flush the affected user’s cached set, so the next check reflects the change without a manual call:
import { UserRole } from "app/access/models/user-role";
await UserRole.assign(user, "editor", "tenant-1"); // editor in tenant-1 — flushes tenant-1await UserRole.assign(user, "viewer", "tenant-2"); // viewer in tenant-2 — same user, different roleawait UserRole.revoke(user, "editor", "tenant-1"); // revokes and flushes tenant-1Reach for access.flush(user, tenant) manually only for other out-of-band changes — editing a Role row’s permissions, or mutating role rows through a path that bypasses assign / revoke:
import { access } from "@warlock.js/access";
await access.flush(user, "tenant-1"); // after an out-of-band change to this user's grantsThe tenant argument is optional — omit it for single-tenant apps (roles are stored globally). In a multi-tenant app, always pass the tenant: an omitted or unresolved tenant scopes to global roles only, never the cross-tenant union.
Manage the catalog
Section titled “Manage the catalog”A role’s permissions are a Role row, edited at runtime — no deploy:
import { Role } from "app/access/models/role";
await Role.create({ name: "editor", permissions: ["orders.*", "posts.create"] });
const role = await Role.first({ name: "editor" });role.set("permissions", ["orders.*", "posts.*"]);await role.save();The DatabaseAccessResolver reads these rows when it resolves a user’s permissions, so an admin screen over the Role table is your runtime permission catalog.
Read roles
Section titled “Read roles”import { hasRole, hasAnyRole, hasAllRoles } from "@warlock.js/access";
await hasRole(user, "editor"); // tenant is an optional 3rd argumentawait hasAnyRole(user, ["admin", "manager"]);await hasAllRoles(user, ["staff", "verified"]);Extend the model
Section titled “Extend the model”UserRole is a plain cascade model in your app — { user_id, user_type, role, tenant }. Add columns the way you would any model: extend its schema and migration in src/app/access/models/user-role/. Because it’s ejected, there’s no subclass-and-rewire step — you edit the file directly.
user_id defaults to uuid in the ejected UserRole migration (src/app/access/models/user-role/migrations/). If your user ids are integers, change the column type in that migration before you run it.
Roles stored on the user?
Section titled “Roles stored on the user?”If you’d rather keep roles as a column on the user model or in a token claim, you don’t use the UserRole table at all — swap DatabaseAccessResolver for a resolver that reads them. See implement a resolver.