Skip to main content
Alfiz ships as two packages you install today. @alfiz/core holds the evaluator: the catalog, grammar, closures, and check logic. @alfiz/application wraps a storage driver into the full local provider. You define your permissions in TypeScript, wire up storage, and every can() check runs in-process against your own database with no external round-trip. This page gets you from an empty project to a passing scoped check. It uses the in-memory driver so you can run it immediately; swapping in Prisma or MongoDB is a two-line change at the end.
1

Install the packages

@alfiz/application already depends on @alfiz/core; installing both is for the explicit import. Add the verifier as a dev dependency when you are ready to enforce coverage in CI:
Alfiz is ESM-only ("type": "module"), and @alfiz/verify needs TypeScript 5.5 or newer.
2

Declare the catalog

The catalog is the single source of truth for every permission key and every scope type. Declare it once, in the codebase that enforces it.
src/alfiz.ts
Permissions are declared by their full dotted key, the same notation every check, grant, role pattern, and nav entry uses, so a key at a call site greps straight back to its declaration. Group levels (docs, docs.files) are inferred from the keys; you never declare them.
parent is a commitment rather than a hint. A type declared parent: null has the ancestor chain [scope, "*"] by declaration, which is what lets a snapshot check it synchronously without consulting your resolver. Omitting parent means exactly the same thing. If folders should nest inside folders, name the type as its own parent ("docs.folder": { parent: "docs.folder" }) and it becomes hierarchical like docs.doc.
3

Create the Application

The Application is the provider: your database, your hierarchy, resolved by your code. ancestry is the seam where Alfiz asks your application where a resource sits.
src/alfiz.ts
parentPointerResolver builds a resolver from a synchronous parent-pointer lookup. When your parent lookup hits the database, write the resolver directly instead. An AncestryResolver is any function from a scope id to its ancestor chain, nearest-first, and it may be async:
4

Create the client

The client binds the catalog to the provider and gives you typed checks.
src/alfiz.ts
Create it once at startup and share it across requests. Call alfiz.close() on shutdown to detach from the provider’s invalidation stream.
5

Grant access

Every form of access is one row. Grant to a user, a group, an org, everyone, or a service, and the shape does not change.
provenance is required on every write and is validated before anything is stored, so a written row can never be missing its audit entry.
6

Run your first check

The grant was made once, at the folder. Alfiz never fans grants out to descendants. The check walks up the ancestor chain instead, so adding a document to a folder rewrites no rows.
can with no scope means the global scope. It asks “may they do this everywhere?”, the strictest question, and not “may they do this anywhere?”. The anywhere question is holds, and it is never a gate.
7

Take one snapshot per request

Server-rendered pages make hundreds of conditional-UI checks inside .map() callbacks and render helpers that cannot be async. Take one snapshot per request and check synchronously:
A snapshot is one consistent instant of the caches, a stronger per-request guarantee than repeated can calls. Pre-resolve the hierarchical scopes you intend to check with { scopes: [...] }; for a list page whose row ids are not known until after the query, extend it with await snap.resolve(rowScopes).

Moving to a real database

memoryDriver() keeps everything in process and is for tests and local development. For production, merge the Prisma schema fragment into your own schema and swap the driver:
On MongoDB there is no schema to merge. Point @alfiz/mongo at a database:
Nothing else changes. The catalog, the client, and every call site are identical. See Storage drivers for the schema fragment and the full setup.

Wire the deletion paths

Grants key on subject and scope strings, not foreign keys, so deleting a user or a resource in your own tables does not clean up its access. Call these from the same code paths that do the delete, or a reused id inherits stranded access:

Next steps

Core Concepts

The five building blocks (catalog, grants, scopes, subjects, checks) and the semantic rules that are fixed rather than configurable.

The four enforcement points

Where checks belong: page, navigation, server action, and conditional UI, and which shape each one takes.

Static verification

Run alfiz-verify in CI so an ungated server action fails the build instead of shipping.

Per-request snapshots

One provider round-trip per request, then synchronous checks. The recommended pattern for server-rendered frameworks.