import { InsufficientPermissionError } from "./errors"; import type { Result } from "./result"; import { ok, err } from "./result"; import type { CommandContext, QueryContext } from "./types"; type PermissionKey = `${M}:${S}:${C}`; type NestedPermissionsRecord> = { readonly [S in keyof T & string]: { readonly [C in T[S][number]]: PermissionKey; }; }; interface PermissionResult> { permissions: NestedPermissionsRecord; } // Builds a typed `module:scope:command` permission map for a module. export function definePermissions< const M extends string, const T extends Record, >(moduleName: M, scopes: T): PermissionResult { const permissions: Record> = {}; for (const [scope, commands] of Object.entries(scopes)) { permissions[scope] = {}; for (const cmd of commands) { permissions[scope][cmd] = `${moduleName}:${scope}:${cmd}`; } } return { permissions } as unknown as PermissionResult; } type PermissionsMap = Record>; // Collects `module:scope` keys from one or more permission maps for SDK config. export function collectPermissionScopes(...permissionMaps: PermissionsMap[]): string[] { const scopes: string[] = []; for (const map of permissionMaps) { for (const [, commands] of Object.entries(map)) { const keys = Object.values(commands); if (keys.length === 0) continue; const scopeKey = keys[0].substring(0, keys[0].lastIndexOf(":")); scopes.push(scopeKey); } } return scopes.sort(); } // Hierarchical prefix-match: an umbrella scope grants every descendant key. function hasPermission(permissions: readonly string[], requiredKey: string): boolean { return permissions.some((p) => requiredKey === p || requiredKey.startsWith(p + ":")); } // Result-based permission gate for command/query composition. export function requirePermission( ctx: CommandContext | QueryContext, key: string, ): Result> { if (!ctx.permissions || !hasPermission(ctx.permissions, key)) { return err(new InsufficientPermissionError(ctx.actorId, key)); } return ok(undefined); } // Throw-based variant of requirePermission for resolver entry points. export function ensurePermission(ctx: CommandContext | QueryContext, key: string): void { const result = requirePermission(ctx, key); if (!result.ok) { throw result.error; } }