API Reference: Public API reference for all VeguiPerms packages.
# @vperms/client
> API reference for the framework-agnostic client.
`@vperms/client` loads resolved subjects over HTTP and evaluates them locally. ## Factory [Section titled “Factory”](#factory)
```ts
interface VPermsConfig {
prefix?: string; // default "/vperms"
subjectResolver?: SubjectResolver;
fetch?: FetchLike;
fetchOptions?: RequestInit;
}
interface VPermsClient {
readonly origin: string;
readonly prefix: string;
getResolvedSubject(
subjectId?: SubjectId | Principal,
): Promise;
getAbility(subjectId?: SubjectId | Principal): Promise;
}
function createVPerms(origin: string, config?: VPermsConfig): VPermsClient;
```
* `origin` may be absolute (`https://api.example.com`), relative (`/api`) or empty (`""`). * With an explicit subject, the client requests `/subject/:subjectId`; with no argument it uses `subjectResolver()`, falling back to `/subject/me` when it returns `null` or `undefined`. ## Ability [Section titled “Ability”](#ability)
```ts
class PermissionAbility {
readonly subject: ResolvedSubject;
readonly permissions: ResolvedPermission[];
can(permission: string): boolean;
}
const Ability = PermissionAbility;
function createAbility(subject: ResolvedSubject): PermissionAbility;
```
`PermissionAbility` is an immutable wrapper over one `ResolvedSubject` snapshot. `can()` is synchronous and only evaluates the resolved permissions using `canResolved` precedence — no inheritance, adapters or network. ## HTTP [Section titled “HTTP”](#http)
```ts
class VPermsHttpError extends Error {
readonly status: number;
readonly url: string;
}
interface FetchResolvedSubjectOptions {
fetch?: FetchLike;
requestInit?: RequestInit;
}
function fetchResolvedSubject(
url: string,
options?: FetchResolvedSubjectOptions,
): Promise;
```
Validates the response with `ResolvedSubjectSchema` and throws `VPermsHttpError` on non-2xx responses. ## Other exports [Section titled “Other exports”](#other-exports) `DEFAULT_PREFIX` (`"/vperms"`), `FetchLike`, `SubjectResolver`, `resolveSubjectId`, and the re-exported `SubjectType`, `Subject`, `SubjectId`, `Principal`, `ResolvedSubject`, `ResolvedPermission`, `ResolvedPermissionSchema`, `ResolvedSubjectSchema`.
# @vperms/core
> API reference for the pure permission engine.
`@vperms/core` is the adapter-independent engine: types, pattern matching, reconciliation and resolution. It has no runtime dependencies. ## Adapter [Section titled “Adapter”](#adapter) ### `VeguiPermsAdapter` [Section titled “VeguiPermsAdapter”](#veguipermsadapter) Abstract persistence contract. Adapters perform **no** validation, resolution, inheritance or evaluation.
```ts
abstract class VeguiPermsAdapter {
abstract findSubject(
workspaceId: string,
subjectId: SubjectId,
): Promise;
abstract saveSubject(workspaceId: string, subject: Subject): Promise;
abstract deleteSubject(
workspaceId: string,
subjectId: SubjectId,
): Promise;
abstract findSubjectGrants(
workspaceId: string,
subjectId: SubjectId,
): Promise;
abstract grantPermission(
workspaceId: string,
subjectId: SubjectId,
permission: string,
value: boolean,
): Promise;
abstract ungrantPermission(
workspaceId: string,
subjectId: SubjectId,
permission: string,
): Promise;
}
```
* `saveSubject` upserts the subject record. * `grantPermission` **must** upsert: setting the same permission twice replaces the value. * Unknown subjects return `null` / `false` rather than throwing. ### `VeguiPermsMemoryAdapter` [Section titled “VeguiPermsMemoryAdapter”](#veguipermsmemoryadapter) Map-backed reference implementation. No options, no `migrate()`. Data is lost when the process restarts. ## Types [Section titled “Types”](#types)
```ts
type SubjectId = string;
enum SubjectType {
User = "user",
Service = "service",
Group = "group",
Anon = "anon",
}
interface Subject {
id: SubjectId;
type: SubjectType;
parents: SubjectId[]; // "!id" negates a default parent
}
interface Principal {
getSubjectId(): SubjectId;
}
interface DefaultParents {
global?: SubjectId[];
byType?: Partial>;
}
interface PermissionGrant {
permission: string;
value: boolean;
subjectId: SubjectId;
workspaceId: string;
}
interface ResolvedPermissionGrant extends PermissionGrant {
depth: number;
layer: number;
}
```
## Matching [Section titled “Matching”](#matching)
```ts
function matchesPattern(pattern: string, permission: string): boolean;
function matchPermission(
grants: PermissionGrant[],
permission: string,
): boolean | null;
function permissionSpecificity(pattern: string): number;
function exactSegments(pattern: string): number;
```
* `matchesPattern` compares dot-separated segments; `*` matches a single inner segment and a trailing `*` matches any remaining segments, including none. * `matchPermission` sorts candidates with `compareGrants` and returns the first match, or `null` when nothing matches. * `permissionSpecificity`: exact segment `+2`, inner `*` `+1`, trailing `*` `+0`. ## Reconciliation [Section titled “Reconciliation”](#reconciliation)
```ts
function compareGrants(
a: ResolvedPermissionGrant,
b: ResolvedPermissionGrant,
): number;
function depthOf(grant: PermissionGrant): number;
function layerOf(grant: PermissionGrant): number;
```
`compareGrants` is the single source of precedence, in order: 1. layer ascending 2. depth ascending 3. specificity descending 4. exact segments descending 5. deny before allow 6. pattern ascending 7. subject id ascending The last two make ordering deterministic and independent of insertion order. `depthOf`/`layerOf` return `0` for grants without resolution metadata. ## Layers [Section titled “Layers”](#layers)
```ts
const EXPLICIT_PARENT_LAYER = 0;
const TYPE_DEFAULT_PARENT_LAYER = 1;
const GLOBAL_DEFAULT_PARENT_LAYER = 2;
const BUILTIN_PERMISSION_LAYER = 3;
```
## Inheritance rules [Section titled “Inheritance rules”](#inheritance-rules)
```ts
const VIRTUAL_PARENT_NEGATION = "!";
function splitParents(parents: SubjectId[]): {
explicit: SubjectId[];
excluded: Set;
};
function effectiveParentLayers(
subject: Subject,
defaults?: DefaultParents,
): { explicit: SubjectId[]; byType: SubjectId[]; global: SubjectId[] };
```
`effectiveParentLayers` merges the explicit `subject.parents` with the type and global defaults, removing duplicates across layers. Negation only excludes virtual (default) parents; explicit parents are always kept. ## Inheritance resolution [Section titled “Inheritance resolution”](#inheritance-resolution)
```ts
interface ResolveInheritedPermissionsOptions {
defaultParents?: DefaultParents;
}
function resolveInheritedPermissions(
adapter: VeguiPermsAdapter,
workspaceId: string,
subject: Subject,
options?: ResolveInheritedPermissionsOptions,
): Promise;
```
Walks parents highest-priority-first (explicit, then type defaults, then global defaults; closer depth first). A visited set prevents infinite cycles. A negation directive on the root subject propagates through the whole walk. Default-reached parents contribute their explicit grants plus the applicable defaults, tagged with `max(layer, defaultLayer)`. The result is sorted with `compareGrants`. ## Subject resolution [Section titled “Subject resolution”](#subject-resolution)
```ts
interface ResolvedPermission {
permission: string;
value: boolean;
weight: number;
}
interface ResolvedSubject {
id: SubjectId;
type: SubjectType;
parents: SubjectId[];
permissions: ResolvedPermission[];
}
function resolveSubjectPermissions(
adapter: VeguiPermsAdapter,
workspaceId: string,
subject: Subject,
options?: ResolveInheritedPermissionsOptions,
): Promise;
function canResolved(
permissions: ResolvedPermission[],
permission: string,
): boolean;
```
Merges direct grants (depth `0`, layer `0`) with inherited grants and a synthesized built-in grant for `SELF_PERMISSIONS_PERMISSION` (value `true`, depth `0`, layer `3`). It sorts, dedupes by permission keeping the first (highest-priority) occurrence and assigns descending `weight`s (`total - index`). `canResolved` returns the value of the highest-weight matching candidate, or `false`. ## Permissions about permissions [Section titled “Permissions about permissions”](#permissions-about-permissions)
```ts
const SELF_PERMISSIONS_PERMISSION = "vperms.subject.me.permissions";
function subjectPermissionsPermission(subjectId: SubjectId): string;
// "vperms.subject..permissions"
```
These gate the [permissions export endpoint](/guides/exporting/). The built-in self grant has the lowest priority and can be overridden by an explicit deny.
# @vperms/drizzle-adapter
> API reference for the Drizzle SQLite, MySQL and Postgres adapters.
`@vperms/drizzle-adapter` provides Drizzle-backed adapters for SQLite, MySQL and Postgres. Each subpath ships its schema, a driver and the migration files. ## SQLite [Section titled “SQLite”](#sqlite)
```ts
import {
VeguiPermsSqliteAdapter,
sqliteSchema,
vpermsGrants,
vpermsSubjects,
} from "@vperms/drizzle-adapter/sqlite";
const adapter = new VeguiPermsSqliteAdapter({
db, // BetterSQLite3Database
migrationsFolder: "node_modules/@vperms/drizzle-adapter/migrations/sqlite",
});
await adapter.migrate();
```
## MySQL [Section titled “MySQL”](#mysql)
```ts
import {
VeguiPermsMysqlAdapter,
mysqlSchema,
vpermsGrants,
vpermsSubjects,
} from "@vperms/drizzle-adapter/mysql";
const adapter = new VeguiPermsMysqlAdapter({ db /* MySql2Database */ });
await adapter.migrate();
```
## Postgres [Section titled “Postgres”](#postgres)
```ts
import {
VeguiPermsPostgresAdapter,
postgresSchema,
vpermsGrants,
vpermsSubjects,
} from "@vperms/drizzle-adapter/postgres";
const adapter = new VeguiPermsPostgresAdapter({ db /* NodePgDatabase */ });
await adapter.migrate();
```
## Options [Section titled “Options”](#options) Every adapter accepts the Drizzle database plus driver options:
```ts
interface DriverOptions {
migrationsFolder?: string;
}
```
`migrate()` calls Drizzle’s `migrate` with the package’s bundled migrations folder. Pass `migrationsFolder` to point at your own copy, for example when bundling. ## Schema [Section titled “Schema”](#schema) The SQLite schema (mirrored, with native types, for MySQL and Postgres):
```ts
const vpermsSubjects = sqliteTable("vperms_subjects", {
workspaceId: text("workspace_id").notNull(),
id: text("id").notNull(),
type: text("type").notNull(),
parents: text("parents", { mode: "json" }).$type().notNull(),
});
const vpermsGrants = sqliteTable("vperms_grants", {
workspaceId: text("workspace_id").notNull(),
subjectId: text("subject_id").notNull(),
permission: text("permission").notNull(),
value: integer("value", { mode: "boolean" }).notNull(),
});
```
Subjects are keyed by `(workspace_id, id)` and grants by `(workspace_id, subject_id, permission)`. ## Root exports [Section titled “Root exports”](#root-exports) The package root re-exports the SQL base adapter and its record types: `VeguiPermsSqlAdapter`, `GrantRecord`, `SubjectRecord`, `SqlAdapterDriver`. Regenerate migration files in this repository with `bun run db:generate`.
# Errors
> Error types and their HTTP mappings across integrations.
The `vperms` package defines three errors. Integrations map them to HTTP status codes consistently. ## `PermissionDeniedError` [Section titled “PermissionDeniedError”](#permissiondeniederror)
```ts
class PermissionDeniedError extends Error {
readonly permission: string;
readonly status: 403;
}
```
Thrown when a caller is not allowed to perform an operation — most notably by [`exportResolvedSubject`](/guides/exporting/) when the caller cannot read the target subject’s permissions. ## `SubjectNotFoundError` [Section titled “SubjectNotFoundError”](#subjectnotfounderror)
```ts
class SubjectNotFoundError extends Error {
readonly subjectId: string;
readonly status: 404;
}
```
Thrown when a subject has no record, for example from `resolvePermissions` or when exporting permissions for a non-existent subject. ## `InvalidSubjectIdError` [Section titled “InvalidSubjectIdError”](#invalidsubjectiderror)
```ts
class InvalidSubjectIdError extends Error {
readonly subjectId: string;
readonly status: 400;
}
```
Thrown when a subject id fails schema validation, for example when a malformed id is requested from the export endpoint. ## HTTP mapping [Section titled “HTTP mapping”](#http-mapping) | Error | Status | Export/served by | | ----------------------- | ------ | ------------------------------------ | | `InvalidSubjectIdError` | `400` | Express/Hono/Nest/Next export routes | | `PermissionDeniedError` | `403` | All request integrations | | `SubjectNotFoundError` | `404` | All request integrations | * **Express** — `403`/`404` via `res.sendStatus`, `400` as JSON `{ error }`. * **Hono** — `403`/`404` via `c.body(null, ...)`, `400` as `c.json({ error })`. * **Nest** — guard denials become Nest’s own `403`; the export controller maps the errors above. * **Next.js** — `NextResponse.json` for success, the same status codes for failures. Client-side, `VPermsHttpError` wraps any non-2xx response from [`fetchResolvedSubject`](/reference/client/), exposing `status` and `url`.
# @vperms/express
> API reference for the Express integration.
## Middleware [Section titled “Middleware”](#middleware)
```ts
interface VpermsMiddlewareOptions {
adapter: VeguiPermsAdapter;
resolver: SubjectResolver;
workspace: string | WorkspaceResolver;
defaultParents?: DefaultParents;
permissionsExport?: PermissionsExportOptions;
}
type SubjectResolverResult = SubjectId | Principal | null;
type SubjectResolver =
(req: Request) => SubjectResolverResult | Promise;
type WorkspaceResolver = (req: Request) => string | Promise;
interface PermissionsExportOptions {
path: string;
}
function vpermsMiddleware(
options: VpermsMiddlewareOptions,
): RequestHandler;
```
Hydrates `req.ability`, `req.subject` and `req.kind` on each request, serves the export endpoint when configured, and forwards errors with `next(error)`. ## Request augmentation [Section titled “Request augmentation”](#request-augmentation)
```ts
interface RequestAbility {
can(permission: string): Promise;
}
declare global {
namespace Express {
interface Request {
ability: RequestAbility;
subject?: Subject;
kind?: SubjectType;
}
}
}
```
## Guards [Section titled “Guards”](#guards)
```ts
type PermissionBuilder = (
req: Request,
) => string | Promise;
type PermissionInput = string | PermissionBuilder;
function hasPermission(...inputs: PermissionInput[]): RequestHandler; // ALL
function hasAnyPermission(...inputs: PermissionInput[]): RequestHandler; // ANY
```
Both respond `403` via `res.sendStatus(403)` when denied, and throw if `vpermsMiddleware` was not registered first. ## Export [Section titled “Export”](#export) `ANONYMOUS_SUBJECT_ID`.
# @vperms/hono
> API reference for the Hono integration.
## Middleware [Section titled “Middleware”](#middleware)
```ts
interface VPermsVariables {
ability: RequestAbility;
subject: Subject | undefined;
kind: SubjectType | undefined;
}
interface VPermsEnv {
Variables: VPermsVariables;
}
type VPermsContext = Context;
interface VpermsMiddlewareOptions {
adapter: VeguiPermsAdapter;
resolver: SubjectResolver;
workspace: string | WorkspaceResolver;
defaultParents?: DefaultParents;
permissionsExport?: PermissionsExportOptions;
}
type SubjectResolverResult = SubjectId | Principal | null;
type SubjectResolver =
(c: VPermsContext) => SubjectResolverResult | Promise;
type WorkspaceResolver =
(c: VPermsContext) => string | Promise;
interface PermissionsExportOptions {
path: string;
}
function vpermsMiddleware(
options: VpermsMiddlewareOptions,
): MiddlewareHandler;
```
Sets `ability`, `subject` and `kind` on the context, serves the export endpoint when configured, otherwise calls `await next()`. ## Guards [Section titled “Guards”](#guards)
```ts
type PermissionBuilder = (
c: VPermsContext,
) => string | Promise;
type PermissionInput = string | PermissionBuilder;
function hasPermission(...inputs: PermissionInput[]): MiddlewareHandler; // ALL
function hasAnyPermission(...inputs: PermissionInput[]): MiddlewareHandler; // ANY
```
Both return `c.body(null, 403)` when denied. ## Other exports [Section titled “Other exports”](#other-exports) `RequestAbility` (alias of the vperms request `Ability`), `ANONYMOUS_SUBJECT_ID`.
# @vperms/mongodb-adapter
> API reference for the MongoDB adapter.
## Adapter [Section titled “Adapter”](#adapter)
```ts
import { MongoClient } from "mongodb";
import { VeguiPermsMongoDBAdapter } from "@vperms/mongodb-adapter";
const client = new MongoClient(process.env.MONGODB_URI!);
await client.connect();
const adapter = new VeguiPermsMongoDBAdapter({ db: client.db("app") });
await adapter.migrate();
```
```ts
interface VeguiPermsMongoDBAdapterOptions {
db: Db;
subjectsCollection?: string; // default "vperms_subjects"
grantsCollection?: string; // default "vperms_grants"
}
class VeguiPermsMongoDBAdapter extends VeguiPermsAdapter {
constructor(options: VeguiPermsMongoDBAdapterOptions);
migrate(): Promise;
}
```
`migrate()` idempotently creates both collections and their unique indexes: * subjects: `{ workspaceId: 1, id: 1 }` * grants: `{ workspaceId: 1, subjectId: 1, permission: 1 }` The adapter never opens or closes the MongoDB connection; pass a `Db` from a client you manage. ## Constants [Section titled “Constants”](#constants)
```ts
const SUBJECTS_COLLECTION = "vperms_subjects";
const GRANTS_COLLECTION = "vperms_grants";
```
## Documents [Section titled “Documents”](#documents)
```ts
interface SubjectDocument {
workspaceId: string;
id: string;
type: SubjectType;
parents: string[];
}
interface GrantDocument {
workspaceId: string;
subjectId: string;
permission: string;
value: boolean;
}
```
# @vperms/nest
> API reference for the NestJS integration.
## Module [Section titled “Module”](#module)
```ts
interface VPermsModuleOptions {
adapter: VeguiPermsAdapter;
resolver: SubjectResolver;
workspace: string;
defaultParents?: DefaultParents;
permissionsExport?: PermissionsExportOptions;
}
interface PermissionsExportOptions {
path: string;
}
type SubjectResolverResult = SubjectId | Principal | null;
type SubjectResolver =
(req: Request) => SubjectResolverResult | Promise;
class VPermsModule {
static forRoot(options: VPermsModuleOptions): DynamicModule;
}
```
Global module. Registers the service and `VPermsGuard` (as `APP_GUARD`) and, when `permissionsExport` is set, a controller that serves the export endpoint. ## Tokens [Section titled “Tokens”](#tokens) `VPERMS_OPTIONS`, `VPERMS_SERVICE`. ## Decorators [Section titled “Decorators”](#decorators)
```ts
function Permission(...inputs: PermissionInput[]): MethodDecorator & ClassDecorator;
function AnyPermission(...inputs: PermissionInput[]): MethodDecorator & ClassDecorator;
function Ability(): ParameterDecorator;
function Subject(): ParameterDecorator;
function Kind(): ParameterDecorator;
const PERMISSION_METADATA: symbol;
const ANY_PERMISSION_METADATA: symbol;
type PermissionBuilder = (
req: Request,
) => string | Promise;
type PermissionInput = string | PermissionBuilder;
```
`Permission`/`AnyPermission` only attach metadata. `Ability`/`Subject`/`Kind` read the hydrated request context; `Ability` throws when it is absent. ## Guards [Section titled “Guards”](#guards)
```ts
class VPermsGuard implements CanActivate {}
abstract class AbilityGuard implements CanActivate {
protected abstract check(
ability: Ability,
context: ExecutionContext,
): boolean | Promise;
protected getAbility(...): Ability;
protected getSubject(...): Subject | undefined;
protected getKind(...): SubjectType | undefined;
}
```
`VPermsGuard` hydrates once per request and evaluates `@Permission` / `@AnyPermission` metadata. Routes without metadata are untouched. ## Request [Section titled “Request”](#request)
```ts
interface VpermsRequest extends Request {
ability: Ability;
subject?: Subject;
kind?: SubjectType;
}
```
`VPERMS_STATE` is the symbol used to store the hydrated `RequestContext` on the request.
# @vperms/next
> API reference for the Next.js App Router integration.
## Factory [Section titled “Factory”](#factory)
```ts
interface NextVPermsConfig {
backend: Backend;
prefix?: string; // default "/vperms"
client?: ClientTransport; // default "proxy"
cache?: CacheWrapper;
browserFetchOptions?: RequestInit;
}
type ClientTransport = "direct" | "proxy";
interface NextVPerms {
backend: Backend;
prefix: string;
clientTransport: ClientTransport;
browserConfig: BrowserConfig;
client?: VPermsClient;
getResolvedSubject(subjectId?): Promise;
getAbility(subjectId?): Promise;
Provider: (props: { children?: ReactNode }) => Promise;
Ability: (props: AbilityProps) => Promise;
handlers: NextVPermsHandlers;
}
function createNextVPerms(config: NextVPermsConfig): NextVPerms;
```
## Backends [Section titled “Backends”](#backends)
```ts
interface NextBackendOptions {
adapter: VeguiPermsAdapter;
workspace: string;
subjectResolver: SubjectResolver;
defaultParents?: DefaultParents;
}
interface NextBackend extends NextBackendOptions {
kind: "next";
}
function nextBackend(options: NextBackendOptions): NextBackend;
interface ExternalBackendOptions {
origin: string;
prefix?: string; // default "/vperms"
subjectResolver?: SubjectResolver;
fetch?: FetchLike;
fetchOptions?: RequestInit;
forwardHeaders?: string[]; // default ["cookie", "authorization"]
}
interface ExternalBackend extends ExternalBackendOptions {
kind: "external";
}
function externalBackend(options: ExternalBackendOptions): ExternalBackend;
type Backend = NextBackend | ExternalBackend;
```
## Shared types [Section titled “Shared types”](#shared-types)
```ts
type SubjectResolverResult = SubjectId | Principal | null;
type SubjectResolver =
(request?: Request) => SubjectResolverResult | Promise;
interface BrowserConfig {
origin: string;
prefix: string;
fetchOptions?: RequestInit;
}
```
## Handlers [Section titled “Handlers”](#handlers)
```ts
interface NextRouteContext {
params?:
| Promise<{ path?: string[] }>
| { path?: string[] };
}
interface NextVPermsHandlers {
GET(request: Request, context?: NextRouteContext): Promise;
}
```
Export them from a catch-all route:
```ts
export const { GET } = vperms.handlers;
```
## Client [Section titled “Client”](#client)
```ts
interface NextClientConfig extends BrowserConfig {
fetch?: FetchLike;
}
function createNextVPerms(config: NextClientConfig): ClientVPerms;
```
`@vperms/next/client` also re-exports everything from `@vperms/react/client`.
# @vperms/react
> API reference for the React server and client bindings.
`@vperms/react` exposes two entry points — `@vperms/react` (server, resolved under the `react-server` condition) and `@vperms/react/client`. ## Server [Section titled “Server”](#server)
```ts
interface ServerVPermsConfig extends VPermsConfig {
cache?: CacheWrapper;
}
interface ServerVPerms {
client: VPermsClient;
getResolvedSubject(subjectId?): Promise;
getAbility(subjectId?): Promise;
Provider: (props: { children?: ReactNode }) => Promise;
Ability: (props: AbilityProps) => Promise;
}
function createVPerms(
origin: string,
config?: ServerVPermsConfig,
): ServerVPerms;
```
`getResolvedSubject` and `getAbility` are wrapped in `React.cache` by default, so a subject is resolved once per render. Standalone exports `getResolvedSubject`, `getAbility`, `Provider` and `ServerAbility` (alias `Ability`) use the most recently created instance, or throw `MISSING_INSTANCE_MESSAGE`. ## Client [Section titled “Client”](#client)
```ts
interface ClientVPerms {
client: VPermsClient;
getResolvedSubject: VPermsClient["getResolvedSubject"];
getAbility: VPermsClient["getAbility"];
Provider: typeof AbilityProvider;
Ability: typeof ClientAbility;
useAbility: typeof useAbility;
}
function createVPerms(origin: string, config?: VPermsConfig): ClientVPerms;
function useAbility(): PermissionAbility;
```
`useAbility` throws `MISSING_PROVIDER_MESSAGE` when used outside `AbilityProvider`. ## Components [Section titled “Components”](#components)
```ts
interface AbilityProps {
permission?: string;
permissions?: string[];
any?: boolean;
fallback?: ReactNode;
children?: ReactNode;
}
interface AbilityProviderProps {
subject: ResolvedSubject;
children?: ReactNode;
}
function AbilityProvider(props: AbilityProviderProps): JSX.Element;
function ClientAbility(props: AbilityProps): JSX.Element;
const Ability = ClientAbility;
function ServerAbility(props: AbilityProps): Promise;
```
`permission` and `permissions` require **all** permissions by default; set `any` to require at least one. Denied renders `fallback` (or nothing). ## Helpers [Section titled “Helpers”](#helpers)
```ts
interface AbilityCheck {
permission?: string;
permissions?: string[];
any?: boolean;
}
function abilityAllows(
ability: PermissionAbility,
check: AbilityCheck,
): boolean;
function getAbilityContext(): Context;
```
`abilityAllows` combines `permission` and `permissions`, requires everything by default and short-circuits on `any`. ## Constants [Section titled “Constants”](#constants) `MISSING_INSTANCE_MESSAGE` (server), `MISSING_PROVIDER_MESSAGE` (client).
# @vperms/sql-adapter
> API reference for the dialect-agnostic SQL base adapter.
`@vperms/sql-adapter` is a dialect-agnostic base for SQL-backed adapters. It implements `VeguiPermsAdapter` on top of a small `SqlAdapterDriver` that each dialect provides. ## `VeguiPermsSqlAdapter` [Section titled “VeguiPermsSqlAdapter”](#veguipermssqladapter)
```ts
abstract class VeguiPermsSqlAdapter extends VeguiPermsAdapter {
protected constructor(driver: SqlAdapterDriver);
migrate(): Promise;
}
```
`migrate()` delegates to the driver and must be idempotent. Adapters built on this class (such as the Drizzle adapters) accept a `migrationsFolder` option to override where migration files are read from. ## Driver contract [Section titled “Driver contract”](#driver-contract)
```ts
interface SqlAdapterDriver {
migrate(): Promise;
findSubject(
workspaceId: string,
subjectId: string,
): Promise;
upsertSubject(record: SubjectRecord): Promise;
deleteSubject(workspaceId: string, subjectId: string): Promise;
findGrants(workspaceId: string, subjectId: string): Promise;
upsertGrant(record: GrantRecord): Promise;
deleteGrant(
workspaceId: string,
subjectId: string,
permission: string,
): Promise;
}
```
## Record types [Section titled “Record types”](#record-types)
```ts
interface SubjectRecord {
workspaceId: string;
id: string;
type: SubjectType;
parents: string[];
}
interface GrantRecord {
workspaceId: string;
subjectId: string;
permission: string;
value: boolean;
}
```
These records are the storage representation; they are mapped to and from the core `Subject` and `PermissionGrant` types. See [drizzle-adapter](/reference/drizzle-adapter/) for concrete implementations.
# vperms
> API reference for the public service, schemas and errors.
`vperms` is the main package. It re-exports the core engine and adds the validated service, request runtime and export helpers. ## Re-exports [Section titled “Re-exports”](#re-exports) Values: `VeguiPermsAdapter`, `VeguiPermsMemoryAdapter`, `SubjectType`, `SELF_PERMISSIONS_PERMISSION`, `subjectPermissionsPermission`, `canResolved`, `resolveSubjectPermissions`, `BUILTIN_PERMISSION_LAYER`. Types: `Subject`, `SubjectId`, `Principal`, `DefaultParents`, `PermissionGrant`, `ResolvedPermission`, `ResolvedPermissionGrant`, `ResolvedSubject`. ## `VeguiPermsService` [Section titled “VeguiPermsService”](#veguipermsservice)
```ts
interface VeguiPermsServiceOptions {
adapter: VeguiPermsAdapter;
defaultParents?: DefaultParents;
}
class VeguiPermsService {
constructor(options: VeguiPermsServiceOptions);
can(
workspaceId: string,
subject: SubjectId | Principal,
permission: string,
): Promise;
resolvePermissions(
workspaceId: string,
subject: SubjectId | Principal,
): Promise;
saveSubject(workspaceId: string, subject: Subject): Promise;
deleteSubject(workspaceId: string, subject: Subject): Promise;
setPermission(
workspaceId: string,
subject: SubjectId | Principal,
permission: string,
value: boolean,
): Promise;
unsetPermission(
workspaceId: string,
subject: SubjectId | Principal,
permission: string,
): Promise;
}
```
`can` returns `false` when the subject has no record. It checks direct grants first, then inherited grants, and finally matches `SELF_PERMISSIONS_PERMISSION`. `resolvePermissions` throws `SubjectNotFoundError` when the subject has no record. Every input is validated with Zod; a `Principal` is normalized with `getSubjectId()`. ## Schemas [Section titled “Schemas”](#schemas)
```ts
const WorkspaceIdSchema; // non-empty string
const SubjectIdSchema; // non-empty string
const PermissionSchema; // dot-separated non-empty segments
const SubjectTypeSchema;
const SubjectSchema;
const PermissionGrantSchema;
const DefaultParentIdSchema; // rejects a leading "!"
const DefaultParentsSchema;
const ResolvedPermissionSchema;
const ResolvedSubjectSchema;
```
Each schema infers a `Validated*` type. ## Request runtime [Section titled “Request runtime”](#request-runtime)
```ts
interface Ability {
can(permission: string): Promise;
}
const ANONYMOUS_SUBJECT_ID = "anonymous";
function resolveSubjectId(subject: SubjectId | Principal): SubjectId;
function createAbility(
service: VeguiPermsService,
workspaceId: string,
subject: SubjectId | Principal,
): Ability;
function ensureAnonymousSubject(
adapter: VeguiPermsAdapter,
service: VeguiPermsService,
workspaceId: string,
): Promise;
interface RequestContextInput {
adapter: VeguiPermsAdapter;
service: VeguiPermsService;
workspaceId: string;
subject: SubjectId | Principal | null | undefined;
}
interface RequestContext {
id: SubjectId;
subject: Subject | undefined;
kind: SubjectType | undefined;
ability: Ability;
}
function resolveRequestContext(
input: RequestContextInput,
): Promise;
```
`createAbility` memoizes `can()` per permission. `resolveRequestContext` creates the anonymous subject on demand when `subject` is `null` or `undefined`, otherwise loads the subject record. ## Export [Section titled “Export”](#export)
```ts
function parsePermissionsExportPath(path: string): PermissionsExportPath;
interface PermissionsExportPath {
path: string;
base: string;
routePattern: string;
param: string;
match(pathname: string): PermissionsExportMatch | null;
}
function exportResolvedSubject(
input: ExportResolvedSubjectInput,
): Promise;
```
`exportResolvedSubject` authorizes the caller before returning anything: self requests require `SELF_PERMISSIONS_PERMISSION`, other targets require `subjectPermissionsPermission(target)`. It then loads the subject and resolves its permissions. Throws `PermissionDeniedError`, `SubjectNotFoundError` or `InvalidSubjectIdError`. ## Errors [Section titled “Errors”](#errors) ### `PermissionDeniedError` [Section titled “PermissionDeniedError”](#permissiondeniederror) `status = 403`. Thrown when the caller may not read the requested permissions. Holds `.permission`. ### `SubjectNotFoundError` [Section titled “SubjectNotFoundError”](#subjectnotfounderror) `status = 404`. Thrown when the subject has no record. Holds `.subjectId`. ### `InvalidSubjectIdError` [Section titled “InvalidSubjectIdError”](#invalidsubjectiderror) `status = 400`. Thrown when a subject id fails validation. Holds `.subjectId`. See [Errors](/reference/errors/) for the full reference.