Skip to content

@vperms/core

@vperms/core is the adapter-independent engine: types, pattern matching, reconciliation and resolution. It has no runtime dependencies.

Abstract persistence contract. Adapters perform no validation, resolution, inheritance or evaluation.

abstract class VeguiPermsAdapter {
abstract findSubject(
workspaceId: string,
subjectId: SubjectId,
): Promise<Subject | null>;
abstract saveSubject(workspaceId: string, subject: Subject): Promise<Subject>;
abstract deleteSubject(
workspaceId: string,
subjectId: SubjectId,
): Promise<boolean>;
abstract findSubjectGrants(
workspaceId: string,
subjectId: SubjectId,
): Promise<PermissionGrant[]>;
abstract grantPermission(
workspaceId: string,
subjectId: SubjectId,
permission: string,
value: boolean,
): Promise<PermissionGrant>;
abstract ungrantPermission(
workspaceId: string,
subjectId: SubjectId,
permission: string,
): Promise<boolean>;
}
  • saveSubject upserts the subject record.
  • grantPermission must upsert: setting the same permission twice replaces the value.
  • Unknown subjects return null / false rather than throwing.

Map-backed reference implementation. No options, no migrate(). Data is lost when the process restarts.

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<Record<SubjectType, SubjectId[]>>;
}
interface PermissionGrant {
permission: string;
value: boolean;
subjectId: SubjectId;
workspaceId: string;
}
interface ResolvedPermissionGrant extends PermissionGrant {
depth: number;
layer: number;
}
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.
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.

const EXPLICIT_PARENT_LAYER = 0;
const TYPE_DEFAULT_PARENT_LAYER = 1;
const GLOBAL_DEFAULT_PARENT_LAYER = 2;
const BUILTIN_PERMISSION_LAYER = 3;
const VIRTUAL_PARENT_NEGATION = "!";
function splitParents(parents: SubjectId[]): {
explicit: SubjectId[];
excluded: Set<SubjectId>;
};
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.

interface ResolveInheritedPermissionsOptions {
defaultParents?: DefaultParents;
}
function resolveInheritedPermissions(
adapter: VeguiPermsAdapter,
workspaceId: string,
subject: Subject,
options?: ResolveInheritedPermissionsOptions,
): Promise<ResolvedPermissionGrant[]>;

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.

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<ResolvedSubject>;
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 weights (total - index). canResolved returns the value of the highest-weight matching candidate, or false.

const SELF_PERMISSIONS_PERMISSION = "vperms.subject.me.permissions";
function subjectPermissionsPermission(subjectId: SubjectId): string;
// "vperms.subject.<id>.permissions"

These gate the permissions export endpoint. The built-in self grant has the lowest priority and can be overridden by an explicit deny.