@siremzam/sentinel 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,11 +28,11 @@ This library was built from a different starting point:
28
28
 
29
29
  ---
30
30
 
31
- ### What's New in 0.3.2
31
+ ### What's New in 0.3.3
32
32
 
33
+ - Hono middleware — `honoGuard()` via `@siremzam/sentinel/middleware/hono`
33
34
  - README rewrite: evaluation walkthrough, concepts glossary, patterns & recipes, migration guide, benchmark data
34
35
  - Standalone example (`examples/standalone/`) — no HTTP server needed
35
- - "When NOT to Use This" and "Testing Your Policies" sections
36
36
 
37
37
  See the full [CHANGELOG](./CHANGELOG.md).
38
38
 
@@ -61,7 +61,7 @@ See the full [CHANGELOG](./CHANGELOG.md).
61
61
  - [toAuditEntry()](#toauditentry)
62
62
  - [permitted() — UI Rendering](#permitted--ui-rendering)
63
63
  - [Integration](#integration)
64
- - [Middleware (Express, Fastify, NestJS)](#middleware)
64
+ - [Middleware (Express, Fastify, Hono, NestJS)](#middleware)
65
65
  - [Server Mode](#server-mode)
66
66
  - [JSON Policy Serialization](#json-policy-serialization)
67
67
  - [Performance](#performance)
@@ -93,7 +93,7 @@ See the full [CHANGELOG](./CHANGELOG.md).
93
93
  | UI permission set | `permitted()` returns `Set` | No | `permission.filter()` | `ability.can()` per action |
94
94
  | JSON policy storage | `exportRules` / `importRules` + `ConditionRegistry` | CSV / JSON adapters | No | Via `@casl/ability/extra` |
95
95
  | Server mode (HTTP microservice) | Built-in (`createAuthServer`) | No | No | No |
96
- | Middleware | Express, Fastify, NestJS | Express (community) | Express (community) | Express, NestJS |
96
+ | Middleware | Express, Fastify, Hono, NestJS | Express (community) | Express (community) | Express, NestJS |
97
97
  | Dependencies | **0** | 2+ | 2 | 1+ |
98
98
  | DSL required | **No** (pure TypeScript) | Yes (Casbin model) | No | No |
99
99
 
@@ -325,12 +325,11 @@ Conditions receive the full `EvaluationContext` — subject, action, resource, r
325
325
 
326
326
  ### Async Conditions
327
327
 
328
- For conditions that need database lookups or API calls:
328
+ For conditions that need database lookups or API calls, use async functions and the `*Async` evaluation methods:
329
329
 
330
330
  ```typescript
331
331
  const engine = new AccessEngine<MySchema>({
332
332
  schema: {} as MySchema,
333
- asyncConditions: true,
334
333
  });
335
334
 
336
335
  engine.addRule(
@@ -348,7 +347,7 @@ engine.addRule(
348
347
  const decision = await engine.evaluateAsync(user, "report:export", "report");
349
348
  ```
350
349
 
351
- When `asyncConditions` is enabled, use `evaluateAsync()`, `permittedAsync()`, and `explainAsync()` instead of their synchronous counterparts.
350
+ Use `evaluateAsync()`, `permittedAsync()`, and `explainAsync()` when you have async conditions. If you accidentally call the synchronous `evaluate()` or `explain()` with async conditions, the engine throws a clear error guiding you to the async API.
352
351
 
353
352
  ### Wildcard Action Patterns
354
353
 
@@ -573,6 +572,22 @@ fastify.post("/invoices/:id/approve", {
573
572
  }, handler);
574
573
  ```
575
574
 
575
+ **Hono:**
576
+
577
+ ```typescript
578
+ import { honoGuard } from "@siremzam/sentinel/middleware/hono";
579
+
580
+ app.post(
581
+ "/invoices/:id/approve",
582
+ honoGuard(engine, "invoice:approve", "invoice", {
583
+ getSubject: (c) => c.get("user"),
584
+ getResourceContext: (c) => ({ id: c.req.param("id") }),
585
+ getTenantId: (c) => c.req.header("x-tenant-id"),
586
+ }),
587
+ handler,
588
+ );
589
+ ```
590
+
576
591
  **NestJS:**
577
592
 
578
593
  ```typescript
@@ -970,7 +985,7 @@ See [SECURITY.md](./SECURITY.md) for responsible disclosure instructions.
970
985
  | `defaultEffect` | `"deny"` (default) or `"allow"` |
971
986
  | `onDecision` | Listener called on every evaluation |
972
987
  | `onConditionError` | Called when a condition throws (fail-closed) |
973
- | `asyncConditions` | Enable async condition support |
988
+ | `asyncConditions` | *(deprecated)* When true, sync methods throw immediately. Will be removed in v2. Async conditions are now detected automatically. |
974
989
  | `strictTenancy` | Throw if tenantId is omitted for tenant-scoped subjects |
975
990
  | `roleHierarchy` | A `RoleHierarchy` instance |
976
991
  | `cacheSize` | LRU cache capacity (0 = disabled) |
@@ -122,9 +122,14 @@ interface EngineOptions<S extends SchemaDefinition> {
122
122
  onDecision?: DecisionListener<S>;
123
123
  onConditionError?: ConditionErrorHandler;
124
124
  /**
125
- * When true, async conditions are awaited.
126
- * When false (default), only synchronous conditions are supported
127
- * and evaluate is guaranteed synchronous.
125
+ * When true, sync methods (evaluate, explain, permitted) throw immediately
126
+ * to force use of evaluateAsync, explainAsync, permittedAsync.
127
+ * When false (default), async conditions are detected at runtime and throw
128
+ * with a clear error pointing to the async API.
129
+ *
130
+ * @deprecated This option is deprecated and will be removed in v2. Async
131
+ * conditions are now detected automatically. Use evaluateAsync(),
132
+ * explainAsync(), or permittedAsync() when you have async conditions.
128
133
  */
129
134
  asyncConditions?: boolean;
130
135
  /**
@@ -122,9 +122,14 @@ interface EngineOptions<S extends SchemaDefinition> {
122
122
  onDecision?: DecisionListener<S>;
123
123
  onConditionError?: ConditionErrorHandler;
124
124
  /**
125
- * When true, async conditions are awaited.
126
- * When false (default), only synchronous conditions are supported
127
- * and evaluate is guaranteed synchronous.
125
+ * When true, sync methods (evaluate, explain, permitted) throw immediately
126
+ * to force use of evaluateAsync, explainAsync, permittedAsync.
127
+ * When false (default), async conditions are detected at runtime and throw
128
+ * with a clear error pointing to the async API.
129
+ *
130
+ * @deprecated This option is deprecated and will be removed in v2. Async
131
+ * conditions are now detected automatically. Use evaluateAsync(),
132
+ * explainAsync(), or permittedAsync() when you have async conditions.
128
133
  */
129
134
  asyncConditions?: boolean;
130
135
  /**
package/dist/index.cjs CHANGED
@@ -123,6 +123,11 @@ function createPolicyFactory() {
123
123
  function escapeRegexMeta(s) {
124
124
  return s.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
125
125
  }
126
+ function isThenable(value) {
127
+ return value != null && typeof value === "object" && typeof value.then === "function";
128
+ }
129
+ var ASYNC_CONDITION_EVALUATE_MSG = "Async condition encountered. Use evaluateAsync() instead.";
130
+ var ASYNC_CONDITION_EXPLAIN_MSG = "Async condition encountered. Use explainAsync() instead.";
126
131
  function compileActionPatterns(actions) {
127
132
  if (actions === "*") return null;
128
133
  const patterns = [];
@@ -365,6 +370,9 @@ var AccessEngine = class {
365
370
  for (let i = 0; i < rule.conditions.length; i++) {
366
371
  try {
367
372
  const result = rule.conditions[i](ctx);
373
+ if (isThenable(result)) {
374
+ throw new Error(ASYNC_CONDITION_EXPLAIN_MSG);
375
+ }
368
376
  if (result !== true) {
369
377
  conditionResults.push({ index: i, passed: false });
370
378
  allConditionsPassed = false;
@@ -372,6 +380,9 @@ var AccessEngine = class {
372
380
  conditionResults.push({ index: i, passed: true });
373
381
  }
374
382
  } catch (err) {
383
+ if (err instanceof Error && err.message === ASYNC_CONDITION_EXPLAIN_MSG) {
384
+ throw err;
385
+ }
375
386
  conditionResults.push({
376
387
  index: i,
377
388
  passed: false,
@@ -503,8 +514,15 @@ var AccessEngine = class {
503
514
  if (!rule.conditions) return true;
504
515
  for (let i = 0; i < rule.conditions.length; i++) {
505
516
  try {
506
- if (rule.conditions[i](ctx) !== true) return false;
517
+ const result = rule.conditions[i](ctx);
518
+ if (isThenable(result)) {
519
+ throw new Error(ASYNC_CONDITION_EVALUATE_MSG);
520
+ }
521
+ if (result !== true) return false;
507
522
  } catch (err) {
523
+ if (err instanceof Error && err.message === ASYNC_CONDITION_EVALUATE_MSG) {
524
+ throw err;
525
+ }
508
526
  this.emitConditionError(rule.id, i, err);
509
527
  return false;
510
528
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/policy-builder.ts","../src/engine.ts","../src/role-hierarchy.ts","../src/serialization.ts","../src/types.ts"],"sourcesContent":["export { AccessEngine } from \"./engine.js\";\nexport type { AccessEngineOptions } from \"./engine.js\";\nexport { RuleBuilder, allow, deny, createPolicyFactory } from \"./policy-builder.js\";\nexport { RoleHierarchy } from \"./role-hierarchy.js\";\nexport {\n ConditionRegistry,\n exportRules,\n exportRulesToJson,\n importRules,\n importRulesFromJson,\n} from \"./serialization.js\";\n\nexport { toAuditEntry } from \"./types.js\";\n\nexport type {\n SchemaDefinition,\n ActionString,\n InferRole,\n InferResource,\n InferAction,\n InferTenantId,\n RoleAssignment,\n Subject,\n ResourceContext,\n EvaluationContext,\n Condition,\n PolicyEffect,\n PolicyRule,\n Decision,\n AuditEntry,\n DecisionListener,\n ConditionError,\n ConditionErrorHandler,\n EngineOptions,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\n\nexport type {\n JsonPolicyRule,\n JsonPolicyDocument,\n} from \"./serialization.js\";\n\nexport type { ServerOptions, EvalRequestBody } from \"./server.js\";\n","import type {\n SchemaDefinition,\n InferRole,\n InferAction,\n InferResource,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\nlet ruleCounter = 0;\n\nfunction nextRuleId(prefix: string): string {\n return `${prefix}-${++ruleCounter}`;\n}\n\nexport class RuleBuilder<S extends SchemaDefinition> {\n private _effect: PolicyEffect;\n private _roles: InferRole<S>[] | \"*\" = \"*\";\n private _actions: InferAction<S>[] | \"*\" = \"*\";\n private _resources: InferResource<S>[] | \"*\" = \"*\";\n private _conditions: Condition<S>[] = [];\n private _priority = 0;\n private _description?: string;\n private _id: string;\n\n constructor(effect: PolicyEffect) {\n this._effect = effect;\n this._id = nextRuleId(effect);\n }\n\n id(id: string): this {\n this._id = id;\n return this;\n }\n\n roles(...roles: InferRole<S>[]): this {\n this._roles = roles;\n return this;\n }\n\n anyRole(): this {\n this._roles = \"*\";\n return this;\n }\n\n actions(...actions: InferAction<S>[]): this {\n this._actions = actions;\n return this;\n }\n\n anyAction(): this {\n this._actions = \"*\";\n return this;\n }\n\n on(...resources: InferResource<S>[]): this {\n this._resources = resources;\n return this;\n }\n\n anyResource(): this {\n this._resources = \"*\";\n return this;\n }\n\n when(condition: Condition<S>): this {\n this._conditions.push(condition);\n return this;\n }\n\n priority(p: number): this {\n this._priority = p;\n return this;\n }\n\n describe(desc: string): this {\n this._description = desc;\n return this;\n }\n\n build(): PolicyRule<S> {\n return {\n id: this._id,\n effect: this._effect,\n roles: this._roles,\n actions: this._actions,\n resources: this._resources,\n conditions: this._conditions.length > 0 ? this._conditions : undefined,\n priority: this._priority,\n description: this._description,\n };\n }\n}\n\nexport function allow<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"allow\");\n}\n\nexport function deny<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"deny\");\n}\n\n/**\n * Creates schema-bound allow/deny factories so you don't need to pass\n * the generic parameter on every call.\n *\n * ```ts\n * const { allow, deny } = createPolicyFactory<MySchema>();\n * allow().roles(\"admin\").anyAction().anyResource().build();\n * ```\n */\nexport function createPolicyFactory<S extends SchemaDefinition>(): {\n allow: () => RuleBuilder<S>;\n deny: () => RuleBuilder<S>;\n} {\n return {\n allow: () => new RuleBuilder<S>(\"allow\"),\n deny: () => new RuleBuilder<S>(\"deny\"),\n };\n}\n","import type {\n SchemaDefinition,\n InferAction,\n InferResource,\n InferRole,\n PolicyRule,\n Decision,\n Subject,\n ResourceContext,\n EvaluationContext,\n DecisionListener,\n EngineOptions,\n ConditionErrorHandler,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\nimport type { RuleBuilder } from \"./policy-builder.js\";\nimport { allow as _allow, deny as _deny } from \"./policy-builder.js\";\nimport type { RoleHierarchy } from \"./role-hierarchy.js\";\n\n// ---------------------------------------------------------------------------\n// Compiled rule — internal representation with pre-compiled regex\n// ---------------------------------------------------------------------------\n\ninterface CompiledRule<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n actionPatterns: RegExp[] | null;\n}\n\nfunction escapeRegexMeta(s: string): string {\n return s.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction compileActionPatterns(actions: string[] | \"*\"): RegExp[] | null {\n if (actions === \"*\") return null;\n const patterns: RegExp[] = [];\n for (const action of actions) {\n if (action.includes(\"*\")) {\n const escaped = escapeRegexMeta(action).replace(/\\*/g, \"[^:]*\");\n patterns.push(new RegExp(\"^\" + escaped + \"$\"));\n }\n }\n return patterns.length > 0 ? patterns : null;\n}\n\n// ---------------------------------------------------------------------------\n// Engine\n// ---------------------------------------------------------------------------\n\nexport interface AccessEngineOptions<S extends SchemaDefinition> extends EngineOptions<S> {\n roleHierarchy?: RoleHierarchy<S>;\n /**\n * Enable LRU cache for evaluation results.\n * Only caches evaluations of rules WITHOUT conditions (context-independent).\n * Rules with conditions are never cached since their result depends on resourceContext.\n */\n cacheSize?: number;\n}\n\nexport class AccessEngine<S extends SchemaDefinition> {\n private compiled: CompiledRule<S>[] = [];\n private listeners: DecisionListener<S>[] = [];\n private asyncConditions: boolean;\n private _defaultDeny: boolean;\n private _strictTenancy: boolean;\n private hierarchy?: RoleHierarchy<S>;\n private cache?: LRUCache<Decision<S>>;\n private conditionErrorHandler?: ConditionErrorHandler;\n\n constructor(options: AccessEngineOptions<S>) {\n this.asyncConditions = options.asyncConditions ?? false;\n this._defaultDeny = (options.defaultEffect ?? \"deny\") === \"deny\";\n this._strictTenancy = options.strictTenancy ?? false;\n this.hierarchy = options.roleHierarchy;\n this.conditionErrorHandler = options.onConditionError;\n if (options.cacheSize && options.cacheSize > 0) {\n this.cache = new LRUCache(options.cacheSize);\n }\n if (options.onDecision) {\n this.listeners.push(options.onDecision);\n }\n }\n\n // -----------------------------------------------------------------------\n // Rule management\n // -----------------------------------------------------------------------\n\n addRule(rule: PolicyRule<S>): this {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n this.cache?.clear();\n return this;\n }\n\n addRules(...rules: PolicyRule<S>[]): this {\n for (const rule of rules) {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n }\n this.cache?.clear();\n return this;\n }\n\n removeRule(ruleId: string): boolean {\n const idx = this.compiled.findIndex((c) => c.rule.id === ruleId);\n if (idx === -1) return false;\n this.compiled.splice(idx, 1);\n this.cache?.clear();\n return true;\n }\n\n getRules(): ReadonlyArray<PolicyRule<S>> {\n return this.compiled.map((c) => c.rule);\n }\n\n clearRules(): void {\n this.compiled = [];\n this.cache?.clear();\n }\n\n // -----------------------------------------------------------------------\n // Cache control\n // -----------------------------------------------------------------------\n\n clearCache(): void {\n this.cache?.clear();\n }\n\n get cacheStats(): { size: number; maxSize: number } | null {\n if (!this.cache) return null;\n return { size: this.cache.size, maxSize: this.cache.maxSize };\n }\n\n // -----------------------------------------------------------------------\n // Fluent rule builders bound to this engine's schema\n // -----------------------------------------------------------------------\n\n allow(): RuleBuilder<S> {\n return _allow<S>();\n }\n\n deny(): RuleBuilder<S> {\n return _deny<S>();\n }\n\n // -----------------------------------------------------------------------\n // Observability\n // -----------------------------------------------------------------------\n\n onDecision(listener: DecisionListener<S>): () => void {\n this.listeners.push(listener);\n return () => {\n const idx = this.listeners.indexOf(listener);\n if (idx !== -1) this.listeners.splice(idx, 1);\n };\n }\n\n private emit(decision: Decision<S>): void {\n for (const listener of this.listeners) {\n try {\n const result = listener(decision);\n if (result instanceof Promise) {\n result.catch(() => {});\n }\n } catch {\n // listeners must not break evaluation\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Evaluation\n // -----------------------------------------------------------------------\n\n evaluate(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use evaluateAsync() instead.\",\n );\n }\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n\n const cacheKey = this.cache\n ? buildCacheKey(subject.id, action as string, resource as string, tenantId)\n : undefined;\n if (cacheKey) {\n const cached = this.cache!.get(cacheKey);\n if (cached) {\n this.emit(cached);\n return cached;\n }\n }\n\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: CompiledRule<S> | null = null;\n let matchedHasConditions = false;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = compiled;\n matchedHasConditions = false;\n break;\n }\n const allMet = this.evaluateConditionsSync(rule, ctx);\n if (allMet) {\n matched = compiled;\n matchedHasConditions = true;\n break;\n }\n }\n\n const decision = this.buildDecision(matched?.rule ?? null, ctx, start);\n\n if (cacheKey && !matchedHasConditions) {\n this.cache!.set(cacheKey, decision);\n }\n\n this.emit(decision);\n return decision;\n }\n\n async evaluateAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: PolicyRule<S> | null = null;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = rule;\n break;\n }\n const results = await Promise.all(\n rule.conditions.map((c, i) =>\n Promise.resolve()\n .then(() => c(ctx))\n .catch((err) => {\n this.emitConditionError(rule.id, i, err);\n return false;\n }),\n ),\n );\n if (results.every(Boolean)) {\n matched = rule;\n break;\n }\n }\n\n const decision = this.buildDecision(matched, ctx, start);\n this.emit(decision);\n return decision;\n }\n\n // -----------------------------------------------------------------------\n // permitted() — list allowed actions on a resource for UI rendering\n // -----------------------------------------------------------------------\n\n permitted(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Set<InferAction<S>> {\n const allowed = new Set<InferAction<S>>();\n for (const action of actions) {\n const decision = this.asyncConditions\n ? (() => { throw new Error(\"Use permittedAsync() with asyncConditions enabled.\"); })()\n : this.evaluate(subject, action, resource, resourceContext, tenantId);\n if (decision.allowed) {\n allowed.add(action);\n }\n }\n return allowed;\n }\n\n async permittedAsync(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Set<InferAction<S>>> {\n const allowed = new Set<InferAction<S>>();\n const results = await Promise.all(\n actions.map((action) =>\n this.evaluateAsync(subject, action, resource, resourceContext, tenantId),\n ),\n );\n for (let i = 0; i < actions.length; i++) {\n if (results[i]!.allowed) {\n allowed.add(actions[i]!);\n }\n }\n return allowed;\n }\n\n // -----------------------------------------------------------------------\n // explain() — full evaluation trace for debugging\n // -----------------------------------------------------------------------\n\n explain(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): ExplainResult<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use explainAsync() instead.\",\n );\n }\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n async explainAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<ExplainResult<S>> {\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = await rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n // -----------------------------------------------------------------------\n // Fluent check API: can(subject).perform(action).on(resource)\n // -----------------------------------------------------------------------\n\n can(subject: Subject<S>): PerformStep<S> {\n return new PerformStep(this, subject);\n }\n\n // -----------------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------------\n\n private validateInput(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n ): void {\n if (!subject || typeof subject.id !== \"string\") {\n throw new Error(\"subject must be an object with a string id\");\n }\n if (!Array.isArray(subject.roles)) {\n throw new Error(\"subject.roles must be an array\");\n }\n if (!action || typeof action !== \"string\") {\n throw new Error(\"action must be a non-empty string\");\n }\n if (!resource || typeof resource !== \"string\") {\n throw new Error(\"resource must be a non-empty string\");\n }\n }\n\n private enforceTenancy(subject: Subject<S>, tenantId?: string): void {\n if (!this._strictTenancy || tenantId != null) return;\n const hasTenantScoped = subject.roles.some((r) => r.tenantId != null);\n if (hasTenantScoped) {\n throw new Error(\n \"strictTenancy is enabled and subject has tenant-scoped roles, \" +\n \"but no tenantId was provided to evaluate(). This could cause \" +\n \"cross-tenant privilege escalation. Pass an explicit tenantId.\",\n );\n }\n }\n\n private buildContext(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext,\n tenantId?: string,\n ): EvaluationContext<S> {\n return { subject, action, resource, resourceContext, tenantId };\n }\n\n private evaluateConditionsSync(\n rule: PolicyRule<S>,\n ctx: EvaluationContext<S>,\n ): boolean {\n if (!rule.conditions) return true;\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n if (rule.conditions[i]!(ctx) !== true) return false;\n } catch (err) {\n this.emitConditionError(rule.id, i, err);\n return false;\n }\n }\n return true;\n }\n\n private emitConditionError(ruleId: string, conditionIndex: number, error: unknown): void {\n if (this.conditionErrorHandler) {\n try {\n this.conditionErrorHandler({ ruleId, conditionIndex, error });\n } catch {\n // error handler must not break evaluation\n }\n }\n }\n\n private matchRules(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n tenantId?: string,\n ): CompiledRule<S>[] {\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const matched = this.compiled.filter((compiled) => {\n const { rule } = compiled;\n if (rule.roles !== \"*\" && !rule.roles.some((r) => subjectRoles.has(r))) return false;\n if (rule.actions !== \"*\" && !this.matchesAction(compiled, action)) return false;\n if (rule.resources !== \"*\" && !rule.resources.includes(resource)) return false;\n return true;\n });\n\n return this.sortCandidates(matched);\n }\n\n private sortCandidates(candidates: CompiledRule<S>[]): CompiledRule<S>[] {\n return candidates.sort((a, b) => {\n const pa = a.rule.priority ?? 0;\n const pb = b.rule.priority ?? 0;\n if (pb !== pa) return pb - pa;\n if (a.rule.effect === \"deny\" && b.rule.effect === \"allow\") return -1;\n if (a.rule.effect === \"allow\" && b.rule.effect === \"deny\") return 1;\n return 0;\n });\n }\n\n private matchesAction(\n compiled: CompiledRule<S>,\n action: InferAction<S>,\n ): boolean {\n const { rule, actionPatterns } = compiled;\n if (rule.actions === \"*\") return true;\n const actionStr = action as string;\n if ((rule.actions as string[]).includes(actionStr)) return true;\n if (actionPatterns) {\n for (const pattern of actionPatterns) {\n if (pattern.test(actionStr)) return true;\n }\n }\n return false;\n }\n\n private resolveRoles(\n subject: Subject<S>,\n tenantId?: string,\n ): Set<string> {\n const directRoles = new Set<string>();\n for (const assignment of subject.roles) {\n if (tenantId == null || assignment.tenantId == null || assignment.tenantId === tenantId) {\n directRoles.add(assignment.role);\n }\n }\n\n if (!this.hierarchy) return directRoles;\n\n const expanded = new Set<string>();\n for (const role of directRoles) {\n for (const r of this.hierarchy.resolve(role as InferRole<S>)) {\n expanded.add(r);\n }\n }\n return expanded;\n }\n\n private buildDecision(\n matched: PolicyRule<S> | null,\n ctx: EvaluationContext<S>,\n start: number,\n ): Decision<S> {\n const allowed =\n matched != null ? matched.effect === \"allow\" : !this._defaultDeny;\n const effect = matched?.effect ?? \"default-deny\";\n const reason = matched\n ? `Matched rule \"${matched.id}\"${matched.description ? `: ${matched.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n matchedRule: matched,\n subject: ctx.subject,\n action: ctx.action,\n resource: ctx.resource,\n resourceContext: ctx.resourceContext,\n tenantId: ctx.tenantId,\n timestamp: Date.now(),\n durationMs: performance.now() - start,\n reason,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fluent check chain: can(user).perform(action).on(resource)\n// ---------------------------------------------------------------------------\n\nclass PerformStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n ) {}\n\n perform(action: InferAction<S>): OnStep<S> {\n return new OnStep(this.engine, this.subject, action);\n }\n}\n\nclass OnStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n private action: InferAction<S>,\n ) {}\n\n on(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n return this.engine.evaluate(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n\n async onAsync(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n return this.engine.evaluateAsync(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Simple LRU cache\n// ---------------------------------------------------------------------------\n\nfunction buildCacheKey(\n subjectId: string,\n action: string,\n resource: string,\n tenantId?: string,\n): string {\n return `${subjectId.length}:${subjectId}\\0${action}\\0${resource}\\0${tenantId ?? \"\"}`;\n}\n\nclass LRUCache<V> {\n private map = new Map<string, V>();\n readonly maxSize: number;\n\n constructor(maxSize: number) {\n this.maxSize = maxSize;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(key: string): V | undefined {\n const value = this.map.get(key);\n if (value !== undefined) {\n this.map.delete(key);\n this.map.set(key, value);\n }\n return value;\n }\n\n set(key: string, value: V): void {\n if (this.map.has(key)) {\n this.map.delete(key);\n } else if (this.map.size >= this.maxSize) {\n const oldest = this.map.keys().next().value;\n if (oldest !== undefined) this.map.delete(oldest);\n }\n this.map.set(key, value);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import type { SchemaDefinition, InferRole } from \"./types.js\";\n\n/**\n * Defines a role inheritance hierarchy.\n *\n * When a role inherits from another, it gains all permissions of its parent roles.\n * Cycles are detected and rejected at definition time.\n *\n * ```ts\n * const hierarchy = new RoleHierarchy<MySchema>()\n * .define(\"admin\", [\"manager\", \"viewer\"])\n * .define(\"manager\", [\"member\"])\n * .define(\"member\", [\"viewer\"]);\n *\n * hierarchy.resolve(\"admin\");\n * // Set { \"admin\", \"manager\", \"member\", \"viewer\" }\n * ```\n */\nexport class RoleHierarchy<S extends SchemaDefinition> {\n private parents = new Map<string, string[]>();\n private cache = new Map<string, Set<string>>();\n\n /**\n * Define that `role` inherits permissions from `inheritsFrom` roles.\n * Clears the resolution cache.\n */\n define(role: InferRole<S>, inheritsFrom: InferRole<S>[]): this {\n this.parents.set(role, inheritsFrom as string[]);\n this.cache.clear();\n this.detectCycle(role as string, new Set());\n return this;\n }\n\n /**\n * Resolve the full set of roles a given role expands to,\n * including all inherited roles (transitive).\n */\n resolve(role: InferRole<S>): Set<string> {\n const roleStr = role as string;\n const cached = this.cache.get(roleStr);\n if (cached) return cached;\n\n const result = new Set<string>();\n this.walk(roleStr, result);\n this.cache.set(roleStr, result);\n return result;\n }\n\n /**\n * Resolve multiple roles at once, returning the merged set.\n */\n resolveAll(roles: Iterable<InferRole<S>>): Set<string> {\n const result = new Set<string>();\n for (const role of roles) {\n for (const r of this.resolve(role)) {\n result.add(r);\n }\n }\n return result;\n }\n\n /**\n * Get all defined roles that have inheritance rules.\n */\n definedRoles(): string[] {\n return [...this.parents.keys()];\n }\n\n private walk(role: string, visited: Set<string>): void {\n if (visited.has(role)) return;\n visited.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.walk(parent, visited);\n }\n }\n }\n\n private detectCycle(role: string, visiting: Set<string>): void {\n if (visiting.has(role)) {\n throw new Error(\n `Cycle detected in role hierarchy: ${[...visiting, role].join(\" → \")}`,\n );\n }\n visiting.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.detectCycle(parent, visiting);\n }\n }\n visiting.delete(role);\n }\n}\n","import type {\n SchemaDefinition,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSON-safe policy representation (conditions become named references)\n// ---------------------------------------------------------------------------\n\nexport interface JsonPolicyRule {\n id: string;\n effect: PolicyEffect;\n roles: string[] | \"*\";\n actions: string[] | \"*\";\n resources: string[] | \"*\";\n conditions?: string[];\n priority?: number;\n description?: string;\n}\n\nexport interface JsonPolicyDocument {\n version: 1;\n rules: JsonPolicyRule[];\n}\n\n/**\n * A registry that maps condition names to condition functions.\n * This allows JSON policies to reference conditions by name\n * while keeping the actual logic in code.\n *\n * ```ts\n * const conditions = new ConditionRegistry<MySchema>();\n * conditions.register(\"isOwner\", ctx => ctx.subject.id === ctx.resourceContext.ownerId);\n * conditions.register(\"isActive\", ctx => ctx.resourceContext.status === \"active\");\n * ```\n */\nexport class ConditionRegistry<S extends SchemaDefinition> {\n private conditions = new Map<string, Condition<S>>();\n\n register(name: string, condition: Condition<S>): this {\n if (!name || typeof name !== \"string\") {\n throw new Error(\"Condition name must be a non-empty string\");\n }\n if (typeof condition !== \"function\") {\n throw new Error(`Condition \"${name}\" must be a function`);\n }\n this.conditions.set(name, condition);\n return this;\n }\n\n get(name: string): Condition<S> | undefined {\n return this.conditions.get(name);\n }\n\n has(name: string): boolean {\n return this.conditions.has(name);\n }\n\n names(): string[] {\n return [...this.conditions.keys()];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Export: PolicyRule[] → JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize rules to a JSON-safe document.\n * Conditions are stripped unless a reverse lookup map is provided.\n */\nexport function exportRules<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): JsonPolicyDocument {\n const jsonRules: JsonPolicyRule[] = rules.map((rule) => {\n const jr: JsonPolicyRule = {\n id: rule.id,\n effect: rule.effect,\n roles: rule.roles,\n actions: rule.actions as string[] | \"*\",\n resources: rule.resources as string[] | \"*\",\n priority: rule.priority,\n description: rule.description,\n };\n\n if (rule.conditions && conditionNames) {\n const names: string[] = [];\n for (const cond of rule.conditions) {\n const name = conditionNames.get(cond);\n if (name) names.push(name);\n }\n if (names.length > 0) jr.conditions = names;\n }\n\n return jr;\n });\n\n return { version: 1, rules: jsonRules };\n}\n\n/**\n * Serialize rules to a JSON string.\n */\nexport function exportRulesToJson<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): string {\n return JSON.stringify(exportRules(rules, conditionNames), null, 2);\n}\n\n// ---------------------------------------------------------------------------\n// Import: JSON → PolicyRule[]\n// ---------------------------------------------------------------------------\n\n/**\n * Deserialize a JSON policy document into PolicyRule objects.\n * Condition names are resolved via the provided registry.\n */\nexport function importRules<S extends SchemaDefinition>(\n doc: JsonPolicyDocument,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n if (!doc || typeof doc !== \"object\") {\n throw new Error(\"Policy document must be a non-null object\");\n }\n if (doc.version !== 1) {\n throw new Error(`Unsupported policy document version: ${doc.version}`);\n }\n if (!Array.isArray(doc.rules)) {\n throw new Error(\"Policy document must have a 'rules' array\");\n }\n\n return doc.rules.map((jr, index) => {\n if (!jr || typeof jr !== \"object\") {\n throw new Error(`Rule at index ${index} must be a non-null object`);\n }\n\n if (!jr.id || typeof jr.id !== \"string\") {\n throw new Error(`Rule at index ${index} is missing a valid \"id\" field.`);\n }\n\n if (jr.effect !== \"allow\" && jr.effect !== \"deny\") {\n throw new Error(\n `Invalid effect \"${jr.effect}\" in rule \"${jr.id}\". Must be \"allow\" or \"deny\".`,\n );\n }\n\n if (jr.roles !== \"*\" && !Array.isArray(jr.roles)) {\n throw new Error(`Rule \"${jr.id}\": roles must be \"*\" or an array of strings`);\n }\n if (jr.actions !== \"*\" && !Array.isArray(jr.actions)) {\n throw new Error(`Rule \"${jr.id}\": actions must be \"*\" or an array of strings`);\n }\n if (jr.resources !== \"*\" && !Array.isArray(jr.resources)) {\n throw new Error(`Rule \"${jr.id}\": resources must be \"*\" or an array of strings`);\n }\n\n const conditions: Condition<S>[] = [];\n if (jr.conditions && registry) {\n for (const name of jr.conditions) {\n const cond = registry.get(name);\n if (!cond) {\n throw new Error(\n `Unknown condition \"${name}\" in rule \"${jr.id}\". ` +\n `Registered conditions: ${registry.names().join(\", \") || \"(none)\"}`,\n );\n }\n conditions.push(cond);\n }\n }\n\n return {\n id: jr.id,\n effect: jr.effect,\n roles: jr.roles,\n actions: jr.actions,\n resources: jr.resources,\n conditions: conditions.length > 0 ? conditions : undefined,\n priority: jr.priority,\n description: jr.description,\n } as PolicyRule<S>;\n });\n}\n\n/**\n * Parse a JSON string into PolicyRule objects.\n */\nexport function importRulesFromJson<S extends SchemaDefinition>(\n json: string,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n let doc: JsonPolicyDocument;\n try {\n doc = JSON.parse(json) as JsonPolicyDocument;\n } catch (err) {\n throw new Error(\n `Failed to parse policy JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n return importRules(doc, registry);\n}\n","/**\n * Core type definitions for the authorization engine.\n *\n * The type system is designed so that defining a schema once\n * propagates full autocomplete through every API surface:\n * policies, checks, audits, and middleware.\n */\n\n// ---------------------------------------------------------------------------\n// Schema definition types — what users provide to configure the engine\n// ---------------------------------------------------------------------------\n\nexport type ActionString = `${string}:${string}`;\n\nexport interface SchemaDefinition {\n roles: string;\n resources: string;\n actions: ActionString;\n tenantId?: string;\n}\n\n/**\n * Infer concrete union types from a schema definition.\n * Used internally to thread type narrowing everywhere.\n */\nexport type InferRole<S extends SchemaDefinition> = S[\"roles\"];\nexport type InferResource<S extends SchemaDefinition> = S[\"resources\"];\nexport type InferAction<S extends SchemaDefinition> = S[\"actions\"];\nexport type InferTenantId<S extends SchemaDefinition> = S[\"tenantId\"] extends string\n ? S[\"tenantId\"]\n : string;\n\n// ---------------------------------------------------------------------------\n// User / Subject\n// ---------------------------------------------------------------------------\n\nexport interface RoleAssignment<S extends SchemaDefinition> {\n role: InferRole<S>;\n tenantId?: InferTenantId<S>;\n}\n\nexport interface Subject<S extends SchemaDefinition> {\n id: string;\n roles: RoleAssignment<S>[];\n attributes?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Resource context passed during evaluation\n// ---------------------------------------------------------------------------\n\nexport interface ResourceContext {\n id?: string;\n tenantId?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Evaluation context — what conditions receive\n// ---------------------------------------------------------------------------\n\nexport interface EvaluationContext<S extends SchemaDefinition> {\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n environment?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Condition — a predicate attached to a policy rule\n// ---------------------------------------------------------------------------\n\nexport type Condition<S extends SchemaDefinition> = (\n ctx: EvaluationContext<S>,\n) => boolean | Promise<boolean>;\n\n// ---------------------------------------------------------------------------\n// Policy rule — the atomic unit of authorization\n// ---------------------------------------------------------------------------\n\nexport type PolicyEffect = \"allow\" | \"deny\";\n\nexport interface PolicyRule<S extends SchemaDefinition> {\n readonly id: string;\n readonly effect: PolicyEffect;\n readonly roles: InferRole<S>[] | \"*\";\n readonly actions: InferAction<S>[] | \"*\";\n readonly resources: InferResource<S>[] | \"*\";\n readonly conditions?: Condition<S>[];\n /**\n * Higher priority wins. Deny at equal priority wins over allow.\n * Default: 0\n */\n readonly priority?: number;\n readonly description?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Decision — the result of evaluating a request\n// ---------------------------------------------------------------------------\n\nexport interface Decision<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n matchedRule: PolicyRule<S> | null;\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n// ---------------------------------------------------------------------------\n// Audit entry — serialization-safe version of Decision\n// ---------------------------------------------------------------------------\n\nexport interface AuditEntry {\n allowed: boolean;\n effect: string;\n matchedRuleId: string | null;\n matchedRuleDescription: string | null;\n subjectId: string;\n action: string;\n resource: string;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n/**\n * Convert a Decision to a serialization-safe AuditEntry\n * (strips functions, large objects, and condition references).\n */\nexport function toAuditEntry<S extends SchemaDefinition>(decision: Decision<S>): AuditEntry {\n return {\n allowed: decision.allowed,\n effect: decision.effect,\n matchedRuleId: decision.matchedRule?.id ?? null,\n matchedRuleDescription: decision.matchedRule?.description ?? null,\n subjectId: decision.subject.id,\n action: decision.action as string,\n resource: decision.resource as string,\n tenantId: decision.tenantId,\n timestamp: decision.timestamp,\n durationMs: decision.durationMs,\n reason: decision.reason,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Explain result — detailed evaluation trace for debugging\n// ---------------------------------------------------------------------------\n\nexport interface ConditionResult {\n index: number;\n passed: boolean;\n error?: string;\n}\n\nexport interface RuleEvaluation<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n roleMatched: boolean;\n actionMatched: boolean;\n resourceMatched: boolean;\n conditionResults: ConditionResult[];\n matched: boolean;\n}\n\nexport interface ExplainResult<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n reason: string;\n evaluatedRules: RuleEvaluation<S>[];\n durationMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Audit / Observability\n// ---------------------------------------------------------------------------\n\nexport type DecisionListener<S extends SchemaDefinition> = (\n decision: Decision<S>,\n) => void | Promise<void>;\n\nexport interface ConditionError {\n ruleId: string;\n conditionIndex: number;\n error: unknown;\n}\n\nexport type ConditionErrorHandler = (err: ConditionError) => void;\n\n// ---------------------------------------------------------------------------\n// Engine options\n// ---------------------------------------------------------------------------\n\nexport interface EngineOptions<S extends SchemaDefinition> {\n schema: S;\n defaultEffect?: PolicyEffect;\n onDecision?: DecisionListener<S>;\n onConditionError?: ConditionErrorHandler;\n /**\n * When true, async conditions are awaited.\n * When false (default), only synchronous conditions are supported\n * and evaluate is guaranteed synchronous.\n */\n asyncConditions?: boolean;\n /**\n * When true, evaluate() throws if tenantId is omitted and the subject\n * has any tenant-scoped role assignments. Prevents accidental\n * cross-tenant privilege escalation.\n */\n strictTenancy?: boolean;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/policy-builder.ts","../src/engine.ts","../src/role-hierarchy.ts","../src/serialization.ts","../src/types.ts"],"sourcesContent":["export { AccessEngine } from \"./engine.js\";\nexport type { AccessEngineOptions } from \"./engine.js\";\nexport { RuleBuilder, allow, deny, createPolicyFactory } from \"./policy-builder.js\";\nexport { RoleHierarchy } from \"./role-hierarchy.js\";\nexport {\n ConditionRegistry,\n exportRules,\n exportRulesToJson,\n importRules,\n importRulesFromJson,\n} from \"./serialization.js\";\n\nexport { toAuditEntry } from \"./types.js\";\n\nexport type {\n SchemaDefinition,\n ActionString,\n InferRole,\n InferResource,\n InferAction,\n InferTenantId,\n RoleAssignment,\n Subject,\n ResourceContext,\n EvaluationContext,\n Condition,\n PolicyEffect,\n PolicyRule,\n Decision,\n AuditEntry,\n DecisionListener,\n ConditionError,\n ConditionErrorHandler,\n EngineOptions,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\n\nexport type {\n JsonPolicyRule,\n JsonPolicyDocument,\n} from \"./serialization.js\";\n\nexport type { ServerOptions, EvalRequestBody } from \"./server.js\";\n","import type {\n SchemaDefinition,\n InferRole,\n InferAction,\n InferResource,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\nlet ruleCounter = 0;\n\nfunction nextRuleId(prefix: string): string {\n return `${prefix}-${++ruleCounter}`;\n}\n\nexport class RuleBuilder<S extends SchemaDefinition> {\n private _effect: PolicyEffect;\n private _roles: InferRole<S>[] | \"*\" = \"*\";\n private _actions: InferAction<S>[] | \"*\" = \"*\";\n private _resources: InferResource<S>[] | \"*\" = \"*\";\n private _conditions: Condition<S>[] = [];\n private _priority = 0;\n private _description?: string;\n private _id: string;\n\n constructor(effect: PolicyEffect) {\n this._effect = effect;\n this._id = nextRuleId(effect);\n }\n\n id(id: string): this {\n this._id = id;\n return this;\n }\n\n roles(...roles: InferRole<S>[]): this {\n this._roles = roles;\n return this;\n }\n\n anyRole(): this {\n this._roles = \"*\";\n return this;\n }\n\n actions(...actions: InferAction<S>[]): this {\n this._actions = actions;\n return this;\n }\n\n anyAction(): this {\n this._actions = \"*\";\n return this;\n }\n\n on(...resources: InferResource<S>[]): this {\n this._resources = resources;\n return this;\n }\n\n anyResource(): this {\n this._resources = \"*\";\n return this;\n }\n\n when(condition: Condition<S>): this {\n this._conditions.push(condition);\n return this;\n }\n\n priority(p: number): this {\n this._priority = p;\n return this;\n }\n\n describe(desc: string): this {\n this._description = desc;\n return this;\n }\n\n build(): PolicyRule<S> {\n return {\n id: this._id,\n effect: this._effect,\n roles: this._roles,\n actions: this._actions,\n resources: this._resources,\n conditions: this._conditions.length > 0 ? this._conditions : undefined,\n priority: this._priority,\n description: this._description,\n };\n }\n}\n\nexport function allow<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"allow\");\n}\n\nexport function deny<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"deny\");\n}\n\n/**\n * Creates schema-bound allow/deny factories so you don't need to pass\n * the generic parameter on every call.\n *\n * ```ts\n * const { allow, deny } = createPolicyFactory<MySchema>();\n * allow().roles(\"admin\").anyAction().anyResource().build();\n * ```\n */\nexport function createPolicyFactory<S extends SchemaDefinition>(): {\n allow: () => RuleBuilder<S>;\n deny: () => RuleBuilder<S>;\n} {\n return {\n allow: () => new RuleBuilder<S>(\"allow\"),\n deny: () => new RuleBuilder<S>(\"deny\"),\n };\n}\n","import type {\n SchemaDefinition,\n InferAction,\n InferResource,\n InferRole,\n PolicyRule,\n Decision,\n Subject,\n ResourceContext,\n EvaluationContext,\n DecisionListener,\n EngineOptions,\n ConditionErrorHandler,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\nimport type { RuleBuilder } from \"./policy-builder.js\";\nimport { allow as _allow, deny as _deny } from \"./policy-builder.js\";\nimport type { RoleHierarchy } from \"./role-hierarchy.js\";\n\n// ---------------------------------------------------------------------------\n// Compiled rule — internal representation with pre-compiled regex\n// ---------------------------------------------------------------------------\n\ninterface CompiledRule<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n actionPatterns: RegExp[] | null;\n}\n\nfunction escapeRegexMeta(s: string): string {\n return s.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n value != null &&\n typeof value === \"object\" &&\n typeof (value as PromiseLike<unknown>).then === \"function\"\n );\n}\n\nconst ASYNC_CONDITION_EVALUATE_MSG =\n \"Async condition encountered. Use evaluateAsync() instead.\";\nconst ASYNC_CONDITION_EXPLAIN_MSG =\n \"Async condition encountered. Use explainAsync() instead.\";\n\nfunction compileActionPatterns(actions: string[] | \"*\"): RegExp[] | null {\n if (actions === \"*\") return null;\n const patterns: RegExp[] = [];\n for (const action of actions) {\n if (action.includes(\"*\")) {\n const escaped = escapeRegexMeta(action).replace(/\\*/g, \"[^:]*\");\n patterns.push(new RegExp(\"^\" + escaped + \"$\"));\n }\n }\n return patterns.length > 0 ? patterns : null;\n}\n\n// ---------------------------------------------------------------------------\n// Engine\n// ---------------------------------------------------------------------------\n\nexport interface AccessEngineOptions<S extends SchemaDefinition> extends EngineOptions<S> {\n roleHierarchy?: RoleHierarchy<S>;\n /**\n * Enable LRU cache for evaluation results.\n * Only caches evaluations of rules WITHOUT conditions (context-independent).\n * Rules with conditions are never cached since their result depends on resourceContext.\n */\n cacheSize?: number;\n}\n\nexport class AccessEngine<S extends SchemaDefinition> {\n private compiled: CompiledRule<S>[] = [];\n private listeners: DecisionListener<S>[] = [];\n private asyncConditions: boolean;\n private _defaultDeny: boolean;\n private _strictTenancy: boolean;\n private hierarchy?: RoleHierarchy<S>;\n private cache?: LRUCache<Decision<S>>;\n private conditionErrorHandler?: ConditionErrorHandler;\n\n constructor(options: AccessEngineOptions<S>) {\n this.asyncConditions = options.asyncConditions ?? false;\n this._defaultDeny = (options.defaultEffect ?? \"deny\") === \"deny\";\n this._strictTenancy = options.strictTenancy ?? false;\n this.hierarchy = options.roleHierarchy;\n this.conditionErrorHandler = options.onConditionError;\n if (options.cacheSize && options.cacheSize > 0) {\n this.cache = new LRUCache(options.cacheSize);\n }\n if (options.onDecision) {\n this.listeners.push(options.onDecision);\n }\n }\n\n // -----------------------------------------------------------------------\n // Rule management\n // -----------------------------------------------------------------------\n\n addRule(rule: PolicyRule<S>): this {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n this.cache?.clear();\n return this;\n }\n\n addRules(...rules: PolicyRule<S>[]): this {\n for (const rule of rules) {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n }\n this.cache?.clear();\n return this;\n }\n\n removeRule(ruleId: string): boolean {\n const idx = this.compiled.findIndex((c) => c.rule.id === ruleId);\n if (idx === -1) return false;\n this.compiled.splice(idx, 1);\n this.cache?.clear();\n return true;\n }\n\n getRules(): ReadonlyArray<PolicyRule<S>> {\n return this.compiled.map((c) => c.rule);\n }\n\n clearRules(): void {\n this.compiled = [];\n this.cache?.clear();\n }\n\n // -----------------------------------------------------------------------\n // Cache control\n // -----------------------------------------------------------------------\n\n clearCache(): void {\n this.cache?.clear();\n }\n\n get cacheStats(): { size: number; maxSize: number } | null {\n if (!this.cache) return null;\n return { size: this.cache.size, maxSize: this.cache.maxSize };\n }\n\n // -----------------------------------------------------------------------\n // Fluent rule builders bound to this engine's schema\n // -----------------------------------------------------------------------\n\n allow(): RuleBuilder<S> {\n return _allow<S>();\n }\n\n deny(): RuleBuilder<S> {\n return _deny<S>();\n }\n\n // -----------------------------------------------------------------------\n // Observability\n // -----------------------------------------------------------------------\n\n onDecision(listener: DecisionListener<S>): () => void {\n this.listeners.push(listener);\n return () => {\n const idx = this.listeners.indexOf(listener);\n if (idx !== -1) this.listeners.splice(idx, 1);\n };\n }\n\n private emit(decision: Decision<S>): void {\n for (const listener of this.listeners) {\n try {\n const result = listener(decision);\n if (result instanceof Promise) {\n result.catch(() => {});\n }\n } catch {\n // listeners must not break evaluation\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Evaluation\n // -----------------------------------------------------------------------\n\n evaluate(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use evaluateAsync() instead.\",\n );\n }\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n\n const cacheKey = this.cache\n ? buildCacheKey(subject.id, action as string, resource as string, tenantId)\n : undefined;\n if (cacheKey) {\n const cached = this.cache!.get(cacheKey);\n if (cached) {\n this.emit(cached);\n return cached;\n }\n }\n\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: CompiledRule<S> | null = null;\n let matchedHasConditions = false;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = compiled;\n matchedHasConditions = false;\n break;\n }\n const allMet = this.evaluateConditionsSync(rule, ctx);\n if (allMet) {\n matched = compiled;\n matchedHasConditions = true;\n break;\n }\n }\n\n const decision = this.buildDecision(matched?.rule ?? null, ctx, start);\n\n if (cacheKey && !matchedHasConditions) {\n this.cache!.set(cacheKey, decision);\n }\n\n this.emit(decision);\n return decision;\n }\n\n async evaluateAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: PolicyRule<S> | null = null;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = rule;\n break;\n }\n const results = await Promise.all(\n rule.conditions.map((c, i) =>\n Promise.resolve()\n .then(() => c(ctx))\n .catch((err) => {\n this.emitConditionError(rule.id, i, err);\n return false;\n }),\n ),\n );\n if (results.every(Boolean)) {\n matched = rule;\n break;\n }\n }\n\n const decision = this.buildDecision(matched, ctx, start);\n this.emit(decision);\n return decision;\n }\n\n // -----------------------------------------------------------------------\n // permitted() — list allowed actions on a resource for UI rendering\n // -----------------------------------------------------------------------\n\n permitted(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Set<InferAction<S>> {\n const allowed = new Set<InferAction<S>>();\n for (const action of actions) {\n const decision = this.asyncConditions\n ? (() => { throw new Error(\"Use permittedAsync() with asyncConditions enabled.\"); })()\n : this.evaluate(subject, action, resource, resourceContext, tenantId);\n if (decision.allowed) {\n allowed.add(action);\n }\n }\n return allowed;\n }\n\n async permittedAsync(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Set<InferAction<S>>> {\n const allowed = new Set<InferAction<S>>();\n const results = await Promise.all(\n actions.map((action) =>\n this.evaluateAsync(subject, action, resource, resourceContext, tenantId),\n ),\n );\n for (let i = 0; i < actions.length; i++) {\n if (results[i]!.allowed) {\n allowed.add(actions[i]!);\n }\n }\n return allowed;\n }\n\n // -----------------------------------------------------------------------\n // explain() — full evaluation trace for debugging\n // -----------------------------------------------------------------------\n\n explain(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): ExplainResult<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use explainAsync() instead.\",\n );\n }\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (isThenable(result)) {\n throw new Error(ASYNC_CONDITION_EXPLAIN_MSG);\n }\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n if (\n err instanceof Error &&\n err.message === ASYNC_CONDITION_EXPLAIN_MSG\n ) {\n throw err;\n }\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n async explainAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<ExplainResult<S>> {\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = await rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n // -----------------------------------------------------------------------\n // Fluent check API: can(subject).perform(action).on(resource)\n // -----------------------------------------------------------------------\n\n can(subject: Subject<S>): PerformStep<S> {\n return new PerformStep(this, subject);\n }\n\n // -----------------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------------\n\n private validateInput(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n ): void {\n if (!subject || typeof subject.id !== \"string\") {\n throw new Error(\"subject must be an object with a string id\");\n }\n if (!Array.isArray(subject.roles)) {\n throw new Error(\"subject.roles must be an array\");\n }\n if (!action || typeof action !== \"string\") {\n throw new Error(\"action must be a non-empty string\");\n }\n if (!resource || typeof resource !== \"string\") {\n throw new Error(\"resource must be a non-empty string\");\n }\n }\n\n private enforceTenancy(subject: Subject<S>, tenantId?: string): void {\n if (!this._strictTenancy || tenantId != null) return;\n const hasTenantScoped = subject.roles.some((r) => r.tenantId != null);\n if (hasTenantScoped) {\n throw new Error(\n \"strictTenancy is enabled and subject has tenant-scoped roles, \" +\n \"but no tenantId was provided to evaluate(). This could cause \" +\n \"cross-tenant privilege escalation. Pass an explicit tenantId.\",\n );\n }\n }\n\n private buildContext(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext,\n tenantId?: string,\n ): EvaluationContext<S> {\n return { subject, action, resource, resourceContext, tenantId };\n }\n\n private evaluateConditionsSync(\n rule: PolicyRule<S>,\n ctx: EvaluationContext<S>,\n ): boolean {\n if (!rule.conditions) return true;\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (isThenable(result)) {\n throw new Error(ASYNC_CONDITION_EVALUATE_MSG);\n }\n if (result !== true) return false;\n } catch (err) {\n if (\n err instanceof Error &&\n err.message === ASYNC_CONDITION_EVALUATE_MSG\n ) {\n throw err;\n }\n this.emitConditionError(rule.id, i, err);\n return false;\n }\n }\n return true;\n }\n\n private emitConditionError(ruleId: string, conditionIndex: number, error: unknown): void {\n if (this.conditionErrorHandler) {\n try {\n this.conditionErrorHandler({ ruleId, conditionIndex, error });\n } catch {\n // error handler must not break evaluation\n }\n }\n }\n\n private matchRules(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n tenantId?: string,\n ): CompiledRule<S>[] {\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const matched = this.compiled.filter((compiled) => {\n const { rule } = compiled;\n if (rule.roles !== \"*\" && !rule.roles.some((r) => subjectRoles.has(r))) return false;\n if (rule.actions !== \"*\" && !this.matchesAction(compiled, action)) return false;\n if (rule.resources !== \"*\" && !rule.resources.includes(resource)) return false;\n return true;\n });\n\n return this.sortCandidates(matched);\n }\n\n private sortCandidates(candidates: CompiledRule<S>[]): CompiledRule<S>[] {\n return candidates.sort((a, b) => {\n const pa = a.rule.priority ?? 0;\n const pb = b.rule.priority ?? 0;\n if (pb !== pa) return pb - pa;\n if (a.rule.effect === \"deny\" && b.rule.effect === \"allow\") return -1;\n if (a.rule.effect === \"allow\" && b.rule.effect === \"deny\") return 1;\n return 0;\n });\n }\n\n private matchesAction(\n compiled: CompiledRule<S>,\n action: InferAction<S>,\n ): boolean {\n const { rule, actionPatterns } = compiled;\n if (rule.actions === \"*\") return true;\n const actionStr = action as string;\n if ((rule.actions as string[]).includes(actionStr)) return true;\n if (actionPatterns) {\n for (const pattern of actionPatterns) {\n if (pattern.test(actionStr)) return true;\n }\n }\n return false;\n }\n\n private resolveRoles(\n subject: Subject<S>,\n tenantId?: string,\n ): Set<string> {\n const directRoles = new Set<string>();\n for (const assignment of subject.roles) {\n if (tenantId == null || assignment.tenantId == null || assignment.tenantId === tenantId) {\n directRoles.add(assignment.role);\n }\n }\n\n if (!this.hierarchy) return directRoles;\n\n const expanded = new Set<string>();\n for (const role of directRoles) {\n for (const r of this.hierarchy.resolve(role as InferRole<S>)) {\n expanded.add(r);\n }\n }\n return expanded;\n }\n\n private buildDecision(\n matched: PolicyRule<S> | null,\n ctx: EvaluationContext<S>,\n start: number,\n ): Decision<S> {\n const allowed =\n matched != null ? matched.effect === \"allow\" : !this._defaultDeny;\n const effect = matched?.effect ?? \"default-deny\";\n const reason = matched\n ? `Matched rule \"${matched.id}\"${matched.description ? `: ${matched.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n matchedRule: matched,\n subject: ctx.subject,\n action: ctx.action,\n resource: ctx.resource,\n resourceContext: ctx.resourceContext,\n tenantId: ctx.tenantId,\n timestamp: Date.now(),\n durationMs: performance.now() - start,\n reason,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fluent check chain: can(user).perform(action).on(resource)\n// ---------------------------------------------------------------------------\n\nclass PerformStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n ) {}\n\n perform(action: InferAction<S>): OnStep<S> {\n return new OnStep(this.engine, this.subject, action);\n }\n}\n\nclass OnStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n private action: InferAction<S>,\n ) {}\n\n on(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n return this.engine.evaluate(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n\n async onAsync(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n return this.engine.evaluateAsync(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Simple LRU cache\n// ---------------------------------------------------------------------------\n\nfunction buildCacheKey(\n subjectId: string,\n action: string,\n resource: string,\n tenantId?: string,\n): string {\n return `${subjectId.length}:${subjectId}\\0${action}\\0${resource}\\0${tenantId ?? \"\"}`;\n}\n\nclass LRUCache<V> {\n private map = new Map<string, V>();\n readonly maxSize: number;\n\n constructor(maxSize: number) {\n this.maxSize = maxSize;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(key: string): V | undefined {\n const value = this.map.get(key);\n if (value !== undefined) {\n this.map.delete(key);\n this.map.set(key, value);\n }\n return value;\n }\n\n set(key: string, value: V): void {\n if (this.map.has(key)) {\n this.map.delete(key);\n } else if (this.map.size >= this.maxSize) {\n const oldest = this.map.keys().next().value;\n if (oldest !== undefined) this.map.delete(oldest);\n }\n this.map.set(key, value);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import type { SchemaDefinition, InferRole } from \"./types.js\";\n\n/**\n * Defines a role inheritance hierarchy.\n *\n * When a role inherits from another, it gains all permissions of its parent roles.\n * Cycles are detected and rejected at definition time.\n *\n * ```ts\n * const hierarchy = new RoleHierarchy<MySchema>()\n * .define(\"admin\", [\"manager\", \"viewer\"])\n * .define(\"manager\", [\"member\"])\n * .define(\"member\", [\"viewer\"]);\n *\n * hierarchy.resolve(\"admin\");\n * // Set { \"admin\", \"manager\", \"member\", \"viewer\" }\n * ```\n */\nexport class RoleHierarchy<S extends SchemaDefinition> {\n private parents = new Map<string, string[]>();\n private cache = new Map<string, Set<string>>();\n\n /**\n * Define that `role` inherits permissions from `inheritsFrom` roles.\n * Clears the resolution cache.\n */\n define(role: InferRole<S>, inheritsFrom: InferRole<S>[]): this {\n this.parents.set(role, inheritsFrom as string[]);\n this.cache.clear();\n this.detectCycle(role as string, new Set());\n return this;\n }\n\n /**\n * Resolve the full set of roles a given role expands to,\n * including all inherited roles (transitive).\n */\n resolve(role: InferRole<S>): Set<string> {\n const roleStr = role as string;\n const cached = this.cache.get(roleStr);\n if (cached) return cached;\n\n const result = new Set<string>();\n this.walk(roleStr, result);\n this.cache.set(roleStr, result);\n return result;\n }\n\n /**\n * Resolve multiple roles at once, returning the merged set.\n */\n resolveAll(roles: Iterable<InferRole<S>>): Set<string> {\n const result = new Set<string>();\n for (const role of roles) {\n for (const r of this.resolve(role)) {\n result.add(r);\n }\n }\n return result;\n }\n\n /**\n * Get all defined roles that have inheritance rules.\n */\n definedRoles(): string[] {\n return [...this.parents.keys()];\n }\n\n private walk(role: string, visited: Set<string>): void {\n if (visited.has(role)) return;\n visited.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.walk(parent, visited);\n }\n }\n }\n\n private detectCycle(role: string, visiting: Set<string>): void {\n if (visiting.has(role)) {\n throw new Error(\n `Cycle detected in role hierarchy: ${[...visiting, role].join(\" → \")}`,\n );\n }\n visiting.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.detectCycle(parent, visiting);\n }\n }\n visiting.delete(role);\n }\n}\n","import type {\n SchemaDefinition,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSON-safe policy representation (conditions become named references)\n// ---------------------------------------------------------------------------\n\nexport interface JsonPolicyRule {\n id: string;\n effect: PolicyEffect;\n roles: string[] | \"*\";\n actions: string[] | \"*\";\n resources: string[] | \"*\";\n conditions?: string[];\n priority?: number;\n description?: string;\n}\n\nexport interface JsonPolicyDocument {\n version: 1;\n rules: JsonPolicyRule[];\n}\n\n/**\n * A registry that maps condition names to condition functions.\n * This allows JSON policies to reference conditions by name\n * while keeping the actual logic in code.\n *\n * ```ts\n * const conditions = new ConditionRegistry<MySchema>();\n * conditions.register(\"isOwner\", ctx => ctx.subject.id === ctx.resourceContext.ownerId);\n * conditions.register(\"isActive\", ctx => ctx.resourceContext.status === \"active\");\n * ```\n */\nexport class ConditionRegistry<S extends SchemaDefinition> {\n private conditions = new Map<string, Condition<S>>();\n\n register(name: string, condition: Condition<S>): this {\n if (!name || typeof name !== \"string\") {\n throw new Error(\"Condition name must be a non-empty string\");\n }\n if (typeof condition !== \"function\") {\n throw new Error(`Condition \"${name}\" must be a function`);\n }\n this.conditions.set(name, condition);\n return this;\n }\n\n get(name: string): Condition<S> | undefined {\n return this.conditions.get(name);\n }\n\n has(name: string): boolean {\n return this.conditions.has(name);\n }\n\n names(): string[] {\n return [...this.conditions.keys()];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Export: PolicyRule[] → JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize rules to a JSON-safe document.\n * Conditions are stripped unless a reverse lookup map is provided.\n */\nexport function exportRules<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): JsonPolicyDocument {\n const jsonRules: JsonPolicyRule[] = rules.map((rule) => {\n const jr: JsonPolicyRule = {\n id: rule.id,\n effect: rule.effect,\n roles: rule.roles,\n actions: rule.actions as string[] | \"*\",\n resources: rule.resources as string[] | \"*\",\n priority: rule.priority,\n description: rule.description,\n };\n\n if (rule.conditions && conditionNames) {\n const names: string[] = [];\n for (const cond of rule.conditions) {\n const name = conditionNames.get(cond);\n if (name) names.push(name);\n }\n if (names.length > 0) jr.conditions = names;\n }\n\n return jr;\n });\n\n return { version: 1, rules: jsonRules };\n}\n\n/**\n * Serialize rules to a JSON string.\n */\nexport function exportRulesToJson<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): string {\n return JSON.stringify(exportRules(rules, conditionNames), null, 2);\n}\n\n// ---------------------------------------------------------------------------\n// Import: JSON → PolicyRule[]\n// ---------------------------------------------------------------------------\n\n/**\n * Deserialize a JSON policy document into PolicyRule objects.\n * Condition names are resolved via the provided registry.\n */\nexport function importRules<S extends SchemaDefinition>(\n doc: JsonPolicyDocument,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n if (!doc || typeof doc !== \"object\") {\n throw new Error(\"Policy document must be a non-null object\");\n }\n if (doc.version !== 1) {\n throw new Error(`Unsupported policy document version: ${doc.version}`);\n }\n if (!Array.isArray(doc.rules)) {\n throw new Error(\"Policy document must have a 'rules' array\");\n }\n\n return doc.rules.map((jr, index) => {\n if (!jr || typeof jr !== \"object\") {\n throw new Error(`Rule at index ${index} must be a non-null object`);\n }\n\n if (!jr.id || typeof jr.id !== \"string\") {\n throw new Error(`Rule at index ${index} is missing a valid \"id\" field.`);\n }\n\n if (jr.effect !== \"allow\" && jr.effect !== \"deny\") {\n throw new Error(\n `Invalid effect \"${jr.effect}\" in rule \"${jr.id}\". Must be \"allow\" or \"deny\".`,\n );\n }\n\n if (jr.roles !== \"*\" && !Array.isArray(jr.roles)) {\n throw new Error(`Rule \"${jr.id}\": roles must be \"*\" or an array of strings`);\n }\n if (jr.actions !== \"*\" && !Array.isArray(jr.actions)) {\n throw new Error(`Rule \"${jr.id}\": actions must be \"*\" or an array of strings`);\n }\n if (jr.resources !== \"*\" && !Array.isArray(jr.resources)) {\n throw new Error(`Rule \"${jr.id}\": resources must be \"*\" or an array of strings`);\n }\n\n const conditions: Condition<S>[] = [];\n if (jr.conditions && registry) {\n for (const name of jr.conditions) {\n const cond = registry.get(name);\n if (!cond) {\n throw new Error(\n `Unknown condition \"${name}\" in rule \"${jr.id}\". ` +\n `Registered conditions: ${registry.names().join(\", \") || \"(none)\"}`,\n );\n }\n conditions.push(cond);\n }\n }\n\n return {\n id: jr.id,\n effect: jr.effect,\n roles: jr.roles,\n actions: jr.actions,\n resources: jr.resources,\n conditions: conditions.length > 0 ? conditions : undefined,\n priority: jr.priority,\n description: jr.description,\n } as PolicyRule<S>;\n });\n}\n\n/**\n * Parse a JSON string into PolicyRule objects.\n */\nexport function importRulesFromJson<S extends SchemaDefinition>(\n json: string,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n let doc: JsonPolicyDocument;\n try {\n doc = JSON.parse(json) as JsonPolicyDocument;\n } catch (err) {\n throw new Error(\n `Failed to parse policy JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n return importRules(doc, registry);\n}\n","/**\n * Core type definitions for the authorization engine.\n *\n * The type system is designed so that defining a schema once\n * propagates full autocomplete through every API surface:\n * policies, checks, audits, and middleware.\n */\n\n// ---------------------------------------------------------------------------\n// Schema definition types — what users provide to configure the engine\n// ---------------------------------------------------------------------------\n\nexport type ActionString = `${string}:${string}`;\n\nexport interface SchemaDefinition {\n roles: string;\n resources: string;\n actions: ActionString;\n tenantId?: string;\n}\n\n/**\n * Infer concrete union types from a schema definition.\n * Used internally to thread type narrowing everywhere.\n */\nexport type InferRole<S extends SchemaDefinition> = S[\"roles\"];\nexport type InferResource<S extends SchemaDefinition> = S[\"resources\"];\nexport type InferAction<S extends SchemaDefinition> = S[\"actions\"];\nexport type InferTenantId<S extends SchemaDefinition> = S[\"tenantId\"] extends string\n ? S[\"tenantId\"]\n : string;\n\n// ---------------------------------------------------------------------------\n// User / Subject\n// ---------------------------------------------------------------------------\n\nexport interface RoleAssignment<S extends SchemaDefinition> {\n role: InferRole<S>;\n tenantId?: InferTenantId<S>;\n}\n\nexport interface Subject<S extends SchemaDefinition> {\n id: string;\n roles: RoleAssignment<S>[];\n attributes?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Resource context passed during evaluation\n// ---------------------------------------------------------------------------\n\nexport interface ResourceContext {\n id?: string;\n tenantId?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Evaluation context — what conditions receive\n// ---------------------------------------------------------------------------\n\nexport interface EvaluationContext<S extends SchemaDefinition> {\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n environment?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Condition — a predicate attached to a policy rule\n// ---------------------------------------------------------------------------\n\nexport type Condition<S extends SchemaDefinition> = (\n ctx: EvaluationContext<S>,\n) => boolean | Promise<boolean>;\n\n// ---------------------------------------------------------------------------\n// Policy rule — the atomic unit of authorization\n// ---------------------------------------------------------------------------\n\nexport type PolicyEffect = \"allow\" | \"deny\";\n\nexport interface PolicyRule<S extends SchemaDefinition> {\n readonly id: string;\n readonly effect: PolicyEffect;\n readonly roles: InferRole<S>[] | \"*\";\n readonly actions: InferAction<S>[] | \"*\";\n readonly resources: InferResource<S>[] | \"*\";\n readonly conditions?: Condition<S>[];\n /**\n * Higher priority wins. Deny at equal priority wins over allow.\n * Default: 0\n */\n readonly priority?: number;\n readonly description?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Decision — the result of evaluating a request\n// ---------------------------------------------------------------------------\n\nexport interface Decision<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n matchedRule: PolicyRule<S> | null;\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n// ---------------------------------------------------------------------------\n// Audit entry — serialization-safe version of Decision\n// ---------------------------------------------------------------------------\n\nexport interface AuditEntry {\n allowed: boolean;\n effect: string;\n matchedRuleId: string | null;\n matchedRuleDescription: string | null;\n subjectId: string;\n action: string;\n resource: string;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n/**\n * Convert a Decision to a serialization-safe AuditEntry\n * (strips functions, large objects, and condition references).\n */\nexport function toAuditEntry<S extends SchemaDefinition>(decision: Decision<S>): AuditEntry {\n return {\n allowed: decision.allowed,\n effect: decision.effect,\n matchedRuleId: decision.matchedRule?.id ?? null,\n matchedRuleDescription: decision.matchedRule?.description ?? null,\n subjectId: decision.subject.id,\n action: decision.action as string,\n resource: decision.resource as string,\n tenantId: decision.tenantId,\n timestamp: decision.timestamp,\n durationMs: decision.durationMs,\n reason: decision.reason,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Explain result — detailed evaluation trace for debugging\n// ---------------------------------------------------------------------------\n\nexport interface ConditionResult {\n index: number;\n passed: boolean;\n error?: string;\n}\n\nexport interface RuleEvaluation<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n roleMatched: boolean;\n actionMatched: boolean;\n resourceMatched: boolean;\n conditionResults: ConditionResult[];\n matched: boolean;\n}\n\nexport interface ExplainResult<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n reason: string;\n evaluatedRules: RuleEvaluation<S>[];\n durationMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Audit / Observability\n// ---------------------------------------------------------------------------\n\nexport type DecisionListener<S extends SchemaDefinition> = (\n decision: Decision<S>,\n) => void | Promise<void>;\n\nexport interface ConditionError {\n ruleId: string;\n conditionIndex: number;\n error: unknown;\n}\n\nexport type ConditionErrorHandler = (err: ConditionError) => void;\n\n// ---------------------------------------------------------------------------\n// Engine options\n// ---------------------------------------------------------------------------\n\nexport interface EngineOptions<S extends SchemaDefinition> {\n schema: S;\n defaultEffect?: PolicyEffect;\n onDecision?: DecisionListener<S>;\n onConditionError?: ConditionErrorHandler;\n /**\n * When true, sync methods (evaluate, explain, permitted) throw immediately\n * to force use of evaluateAsync, explainAsync, permittedAsync.\n * When false (default), async conditions are detected at runtime and throw\n * with a clear error pointing to the async API.\n *\n * @deprecated This option is deprecated and will be removed in v2. Async\n * conditions are now detected automatically. Use evaluateAsync(),\n * explainAsync(), or permittedAsync() when you have async conditions.\n */\n asyncConditions?: boolean;\n /**\n * When true, evaluate() throws if tenantId is omitted and the subject\n * has any tenant-scoped role assignments. Prevents accidental\n * cross-tenant privilege escalation.\n */\n strictTenancy?: boolean;\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SchemaDefinition, C as Condition, P as PolicyEffect, a as PolicyRule } from './engine-C6IASR5F.cjs';
2
- export { A as AccessEngine, b as AccessEngineOptions, c as ActionString, d as AuditEntry, e as ConditionError, f as ConditionErrorHandler, g as ConditionResult, D as Decision, h as DecisionListener, E as EngineOptions, i as EvaluationContext, j as ExplainResult, I as InferAction, k as InferResource, l as InferRole, m as InferTenantId, R as ResourceContext, n as RoleAssignment, o as RoleHierarchy, p as RuleBuilder, q as RuleEvaluation, r as Subject, s as allow, t as createPolicyFactory, u as deny, v as toAuditEntry } from './engine-C6IASR5F.cjs';
1
+ import { S as SchemaDefinition, C as Condition, P as PolicyEffect, a as PolicyRule } from './engine-Ccws3LFj.cjs';
2
+ export { A as AccessEngine, b as AccessEngineOptions, c as ActionString, d as AuditEntry, e as ConditionError, f as ConditionErrorHandler, g as ConditionResult, D as Decision, h as DecisionListener, E as EngineOptions, i as EvaluationContext, j as ExplainResult, I as InferAction, k as InferResource, l as InferRole, m as InferTenantId, R as ResourceContext, n as RoleAssignment, o as RoleHierarchy, p as RuleBuilder, q as RuleEvaluation, r as Subject, s as allow, t as createPolicyFactory, u as deny, v as toAuditEntry } from './engine-Ccws3LFj.cjs';
3
3
  export { EvalRequestBody, ServerOptions } from './server.cjs';
4
4
  import 'http';
5
5
  import 'node:http';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SchemaDefinition, C as Condition, P as PolicyEffect, a as PolicyRule } from './engine-C6IASR5F.js';
2
- export { A as AccessEngine, b as AccessEngineOptions, c as ActionString, d as AuditEntry, e as ConditionError, f as ConditionErrorHandler, g as ConditionResult, D as Decision, h as DecisionListener, E as EngineOptions, i as EvaluationContext, j as ExplainResult, I as InferAction, k as InferResource, l as InferRole, m as InferTenantId, R as ResourceContext, n as RoleAssignment, o as RoleHierarchy, p as RuleBuilder, q as RuleEvaluation, r as Subject, s as allow, t as createPolicyFactory, u as deny, v as toAuditEntry } from './engine-C6IASR5F.js';
1
+ import { S as SchemaDefinition, C as Condition, P as PolicyEffect, a as PolicyRule } from './engine-Ccws3LFj.js';
2
+ export { A as AccessEngine, b as AccessEngineOptions, c as ActionString, d as AuditEntry, e as ConditionError, f as ConditionErrorHandler, g as ConditionResult, D as Decision, h as DecisionListener, E as EngineOptions, i as EvaluationContext, j as ExplainResult, I as InferAction, k as InferResource, l as InferRole, m as InferTenantId, R as ResourceContext, n as RoleAssignment, o as RoleHierarchy, p as RuleBuilder, q as RuleEvaluation, r as Subject, s as allow, t as createPolicyFactory, u as deny, v as toAuditEntry } from './engine-Ccws3LFj.js';
3
3
  export { EvalRequestBody, ServerOptions } from './server.js';
4
4
  import 'http';
5
5
  import 'node:http';
package/dist/index.js CHANGED
@@ -86,6 +86,11 @@ function createPolicyFactory() {
86
86
  function escapeRegexMeta(s) {
87
87
  return s.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
88
88
  }
89
+ function isThenable(value) {
90
+ return value != null && typeof value === "object" && typeof value.then === "function";
91
+ }
92
+ var ASYNC_CONDITION_EVALUATE_MSG = "Async condition encountered. Use evaluateAsync() instead.";
93
+ var ASYNC_CONDITION_EXPLAIN_MSG = "Async condition encountered. Use explainAsync() instead.";
89
94
  function compileActionPatterns(actions) {
90
95
  if (actions === "*") return null;
91
96
  const patterns = [];
@@ -328,6 +333,9 @@ var AccessEngine = class {
328
333
  for (let i = 0; i < rule.conditions.length; i++) {
329
334
  try {
330
335
  const result = rule.conditions[i](ctx);
336
+ if (isThenable(result)) {
337
+ throw new Error(ASYNC_CONDITION_EXPLAIN_MSG);
338
+ }
331
339
  if (result !== true) {
332
340
  conditionResults.push({ index: i, passed: false });
333
341
  allConditionsPassed = false;
@@ -335,6 +343,9 @@ var AccessEngine = class {
335
343
  conditionResults.push({ index: i, passed: true });
336
344
  }
337
345
  } catch (err) {
346
+ if (err instanceof Error && err.message === ASYNC_CONDITION_EXPLAIN_MSG) {
347
+ throw err;
348
+ }
338
349
  conditionResults.push({
339
350
  index: i,
340
351
  passed: false,
@@ -466,8 +477,15 @@ var AccessEngine = class {
466
477
  if (!rule.conditions) return true;
467
478
  for (let i = 0; i < rule.conditions.length; i++) {
468
479
  try {
469
- if (rule.conditions[i](ctx) !== true) return false;
480
+ const result = rule.conditions[i](ctx);
481
+ if (isThenable(result)) {
482
+ throw new Error(ASYNC_CONDITION_EVALUATE_MSG);
483
+ }
484
+ if (result !== true) return false;
470
485
  } catch (err) {
486
+ if (err instanceof Error && err.message === ASYNC_CONDITION_EVALUATE_MSG) {
487
+ throw err;
488
+ }
471
489
  this.emitConditionError(rule.id, i, err);
472
490
  return false;
473
491
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/policy-builder.ts","../src/engine.ts","../src/role-hierarchy.ts","../src/serialization.ts","../src/types.ts"],"sourcesContent":["import type {\n SchemaDefinition,\n InferRole,\n InferAction,\n InferResource,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\nlet ruleCounter = 0;\n\nfunction nextRuleId(prefix: string): string {\n return `${prefix}-${++ruleCounter}`;\n}\n\nexport class RuleBuilder<S extends SchemaDefinition> {\n private _effect: PolicyEffect;\n private _roles: InferRole<S>[] | \"*\" = \"*\";\n private _actions: InferAction<S>[] | \"*\" = \"*\";\n private _resources: InferResource<S>[] | \"*\" = \"*\";\n private _conditions: Condition<S>[] = [];\n private _priority = 0;\n private _description?: string;\n private _id: string;\n\n constructor(effect: PolicyEffect) {\n this._effect = effect;\n this._id = nextRuleId(effect);\n }\n\n id(id: string): this {\n this._id = id;\n return this;\n }\n\n roles(...roles: InferRole<S>[]): this {\n this._roles = roles;\n return this;\n }\n\n anyRole(): this {\n this._roles = \"*\";\n return this;\n }\n\n actions(...actions: InferAction<S>[]): this {\n this._actions = actions;\n return this;\n }\n\n anyAction(): this {\n this._actions = \"*\";\n return this;\n }\n\n on(...resources: InferResource<S>[]): this {\n this._resources = resources;\n return this;\n }\n\n anyResource(): this {\n this._resources = \"*\";\n return this;\n }\n\n when(condition: Condition<S>): this {\n this._conditions.push(condition);\n return this;\n }\n\n priority(p: number): this {\n this._priority = p;\n return this;\n }\n\n describe(desc: string): this {\n this._description = desc;\n return this;\n }\n\n build(): PolicyRule<S> {\n return {\n id: this._id,\n effect: this._effect,\n roles: this._roles,\n actions: this._actions,\n resources: this._resources,\n conditions: this._conditions.length > 0 ? this._conditions : undefined,\n priority: this._priority,\n description: this._description,\n };\n }\n}\n\nexport function allow<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"allow\");\n}\n\nexport function deny<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"deny\");\n}\n\n/**\n * Creates schema-bound allow/deny factories so you don't need to pass\n * the generic parameter on every call.\n *\n * ```ts\n * const { allow, deny } = createPolicyFactory<MySchema>();\n * allow().roles(\"admin\").anyAction().anyResource().build();\n * ```\n */\nexport function createPolicyFactory<S extends SchemaDefinition>(): {\n allow: () => RuleBuilder<S>;\n deny: () => RuleBuilder<S>;\n} {\n return {\n allow: () => new RuleBuilder<S>(\"allow\"),\n deny: () => new RuleBuilder<S>(\"deny\"),\n };\n}\n","import type {\n SchemaDefinition,\n InferAction,\n InferResource,\n InferRole,\n PolicyRule,\n Decision,\n Subject,\n ResourceContext,\n EvaluationContext,\n DecisionListener,\n EngineOptions,\n ConditionErrorHandler,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\nimport type { RuleBuilder } from \"./policy-builder.js\";\nimport { allow as _allow, deny as _deny } from \"./policy-builder.js\";\nimport type { RoleHierarchy } from \"./role-hierarchy.js\";\n\n// ---------------------------------------------------------------------------\n// Compiled rule — internal representation with pre-compiled regex\n// ---------------------------------------------------------------------------\n\ninterface CompiledRule<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n actionPatterns: RegExp[] | null;\n}\n\nfunction escapeRegexMeta(s: string): string {\n return s.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction compileActionPatterns(actions: string[] | \"*\"): RegExp[] | null {\n if (actions === \"*\") return null;\n const patterns: RegExp[] = [];\n for (const action of actions) {\n if (action.includes(\"*\")) {\n const escaped = escapeRegexMeta(action).replace(/\\*/g, \"[^:]*\");\n patterns.push(new RegExp(\"^\" + escaped + \"$\"));\n }\n }\n return patterns.length > 0 ? patterns : null;\n}\n\n// ---------------------------------------------------------------------------\n// Engine\n// ---------------------------------------------------------------------------\n\nexport interface AccessEngineOptions<S extends SchemaDefinition> extends EngineOptions<S> {\n roleHierarchy?: RoleHierarchy<S>;\n /**\n * Enable LRU cache for evaluation results.\n * Only caches evaluations of rules WITHOUT conditions (context-independent).\n * Rules with conditions are never cached since their result depends on resourceContext.\n */\n cacheSize?: number;\n}\n\nexport class AccessEngine<S extends SchemaDefinition> {\n private compiled: CompiledRule<S>[] = [];\n private listeners: DecisionListener<S>[] = [];\n private asyncConditions: boolean;\n private _defaultDeny: boolean;\n private _strictTenancy: boolean;\n private hierarchy?: RoleHierarchy<S>;\n private cache?: LRUCache<Decision<S>>;\n private conditionErrorHandler?: ConditionErrorHandler;\n\n constructor(options: AccessEngineOptions<S>) {\n this.asyncConditions = options.asyncConditions ?? false;\n this._defaultDeny = (options.defaultEffect ?? \"deny\") === \"deny\";\n this._strictTenancy = options.strictTenancy ?? false;\n this.hierarchy = options.roleHierarchy;\n this.conditionErrorHandler = options.onConditionError;\n if (options.cacheSize && options.cacheSize > 0) {\n this.cache = new LRUCache(options.cacheSize);\n }\n if (options.onDecision) {\n this.listeners.push(options.onDecision);\n }\n }\n\n // -----------------------------------------------------------------------\n // Rule management\n // -----------------------------------------------------------------------\n\n addRule(rule: PolicyRule<S>): this {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n this.cache?.clear();\n return this;\n }\n\n addRules(...rules: PolicyRule<S>[]): this {\n for (const rule of rules) {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n }\n this.cache?.clear();\n return this;\n }\n\n removeRule(ruleId: string): boolean {\n const idx = this.compiled.findIndex((c) => c.rule.id === ruleId);\n if (idx === -1) return false;\n this.compiled.splice(idx, 1);\n this.cache?.clear();\n return true;\n }\n\n getRules(): ReadonlyArray<PolicyRule<S>> {\n return this.compiled.map((c) => c.rule);\n }\n\n clearRules(): void {\n this.compiled = [];\n this.cache?.clear();\n }\n\n // -----------------------------------------------------------------------\n // Cache control\n // -----------------------------------------------------------------------\n\n clearCache(): void {\n this.cache?.clear();\n }\n\n get cacheStats(): { size: number; maxSize: number } | null {\n if (!this.cache) return null;\n return { size: this.cache.size, maxSize: this.cache.maxSize };\n }\n\n // -----------------------------------------------------------------------\n // Fluent rule builders bound to this engine's schema\n // -----------------------------------------------------------------------\n\n allow(): RuleBuilder<S> {\n return _allow<S>();\n }\n\n deny(): RuleBuilder<S> {\n return _deny<S>();\n }\n\n // -----------------------------------------------------------------------\n // Observability\n // -----------------------------------------------------------------------\n\n onDecision(listener: DecisionListener<S>): () => void {\n this.listeners.push(listener);\n return () => {\n const idx = this.listeners.indexOf(listener);\n if (idx !== -1) this.listeners.splice(idx, 1);\n };\n }\n\n private emit(decision: Decision<S>): void {\n for (const listener of this.listeners) {\n try {\n const result = listener(decision);\n if (result instanceof Promise) {\n result.catch(() => {});\n }\n } catch {\n // listeners must not break evaluation\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Evaluation\n // -----------------------------------------------------------------------\n\n evaluate(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use evaluateAsync() instead.\",\n );\n }\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n\n const cacheKey = this.cache\n ? buildCacheKey(subject.id, action as string, resource as string, tenantId)\n : undefined;\n if (cacheKey) {\n const cached = this.cache!.get(cacheKey);\n if (cached) {\n this.emit(cached);\n return cached;\n }\n }\n\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: CompiledRule<S> | null = null;\n let matchedHasConditions = false;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = compiled;\n matchedHasConditions = false;\n break;\n }\n const allMet = this.evaluateConditionsSync(rule, ctx);\n if (allMet) {\n matched = compiled;\n matchedHasConditions = true;\n break;\n }\n }\n\n const decision = this.buildDecision(matched?.rule ?? null, ctx, start);\n\n if (cacheKey && !matchedHasConditions) {\n this.cache!.set(cacheKey, decision);\n }\n\n this.emit(decision);\n return decision;\n }\n\n async evaluateAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: PolicyRule<S> | null = null;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = rule;\n break;\n }\n const results = await Promise.all(\n rule.conditions.map((c, i) =>\n Promise.resolve()\n .then(() => c(ctx))\n .catch((err) => {\n this.emitConditionError(rule.id, i, err);\n return false;\n }),\n ),\n );\n if (results.every(Boolean)) {\n matched = rule;\n break;\n }\n }\n\n const decision = this.buildDecision(matched, ctx, start);\n this.emit(decision);\n return decision;\n }\n\n // -----------------------------------------------------------------------\n // permitted() — list allowed actions on a resource for UI rendering\n // -----------------------------------------------------------------------\n\n permitted(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Set<InferAction<S>> {\n const allowed = new Set<InferAction<S>>();\n for (const action of actions) {\n const decision = this.asyncConditions\n ? (() => { throw new Error(\"Use permittedAsync() with asyncConditions enabled.\"); })()\n : this.evaluate(subject, action, resource, resourceContext, tenantId);\n if (decision.allowed) {\n allowed.add(action);\n }\n }\n return allowed;\n }\n\n async permittedAsync(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Set<InferAction<S>>> {\n const allowed = new Set<InferAction<S>>();\n const results = await Promise.all(\n actions.map((action) =>\n this.evaluateAsync(subject, action, resource, resourceContext, tenantId),\n ),\n );\n for (let i = 0; i < actions.length; i++) {\n if (results[i]!.allowed) {\n allowed.add(actions[i]!);\n }\n }\n return allowed;\n }\n\n // -----------------------------------------------------------------------\n // explain() — full evaluation trace for debugging\n // -----------------------------------------------------------------------\n\n explain(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): ExplainResult<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use explainAsync() instead.\",\n );\n }\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n async explainAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<ExplainResult<S>> {\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = await rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n // -----------------------------------------------------------------------\n // Fluent check API: can(subject).perform(action).on(resource)\n // -----------------------------------------------------------------------\n\n can(subject: Subject<S>): PerformStep<S> {\n return new PerformStep(this, subject);\n }\n\n // -----------------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------------\n\n private validateInput(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n ): void {\n if (!subject || typeof subject.id !== \"string\") {\n throw new Error(\"subject must be an object with a string id\");\n }\n if (!Array.isArray(subject.roles)) {\n throw new Error(\"subject.roles must be an array\");\n }\n if (!action || typeof action !== \"string\") {\n throw new Error(\"action must be a non-empty string\");\n }\n if (!resource || typeof resource !== \"string\") {\n throw new Error(\"resource must be a non-empty string\");\n }\n }\n\n private enforceTenancy(subject: Subject<S>, tenantId?: string): void {\n if (!this._strictTenancy || tenantId != null) return;\n const hasTenantScoped = subject.roles.some((r) => r.tenantId != null);\n if (hasTenantScoped) {\n throw new Error(\n \"strictTenancy is enabled and subject has tenant-scoped roles, \" +\n \"but no tenantId was provided to evaluate(). This could cause \" +\n \"cross-tenant privilege escalation. Pass an explicit tenantId.\",\n );\n }\n }\n\n private buildContext(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext,\n tenantId?: string,\n ): EvaluationContext<S> {\n return { subject, action, resource, resourceContext, tenantId };\n }\n\n private evaluateConditionsSync(\n rule: PolicyRule<S>,\n ctx: EvaluationContext<S>,\n ): boolean {\n if (!rule.conditions) return true;\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n if (rule.conditions[i]!(ctx) !== true) return false;\n } catch (err) {\n this.emitConditionError(rule.id, i, err);\n return false;\n }\n }\n return true;\n }\n\n private emitConditionError(ruleId: string, conditionIndex: number, error: unknown): void {\n if (this.conditionErrorHandler) {\n try {\n this.conditionErrorHandler({ ruleId, conditionIndex, error });\n } catch {\n // error handler must not break evaluation\n }\n }\n }\n\n private matchRules(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n tenantId?: string,\n ): CompiledRule<S>[] {\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const matched = this.compiled.filter((compiled) => {\n const { rule } = compiled;\n if (rule.roles !== \"*\" && !rule.roles.some((r) => subjectRoles.has(r))) return false;\n if (rule.actions !== \"*\" && !this.matchesAction(compiled, action)) return false;\n if (rule.resources !== \"*\" && !rule.resources.includes(resource)) return false;\n return true;\n });\n\n return this.sortCandidates(matched);\n }\n\n private sortCandidates(candidates: CompiledRule<S>[]): CompiledRule<S>[] {\n return candidates.sort((a, b) => {\n const pa = a.rule.priority ?? 0;\n const pb = b.rule.priority ?? 0;\n if (pb !== pa) return pb - pa;\n if (a.rule.effect === \"deny\" && b.rule.effect === \"allow\") return -1;\n if (a.rule.effect === \"allow\" && b.rule.effect === \"deny\") return 1;\n return 0;\n });\n }\n\n private matchesAction(\n compiled: CompiledRule<S>,\n action: InferAction<S>,\n ): boolean {\n const { rule, actionPatterns } = compiled;\n if (rule.actions === \"*\") return true;\n const actionStr = action as string;\n if ((rule.actions as string[]).includes(actionStr)) return true;\n if (actionPatterns) {\n for (const pattern of actionPatterns) {\n if (pattern.test(actionStr)) return true;\n }\n }\n return false;\n }\n\n private resolveRoles(\n subject: Subject<S>,\n tenantId?: string,\n ): Set<string> {\n const directRoles = new Set<string>();\n for (const assignment of subject.roles) {\n if (tenantId == null || assignment.tenantId == null || assignment.tenantId === tenantId) {\n directRoles.add(assignment.role);\n }\n }\n\n if (!this.hierarchy) return directRoles;\n\n const expanded = new Set<string>();\n for (const role of directRoles) {\n for (const r of this.hierarchy.resolve(role as InferRole<S>)) {\n expanded.add(r);\n }\n }\n return expanded;\n }\n\n private buildDecision(\n matched: PolicyRule<S> | null,\n ctx: EvaluationContext<S>,\n start: number,\n ): Decision<S> {\n const allowed =\n matched != null ? matched.effect === \"allow\" : !this._defaultDeny;\n const effect = matched?.effect ?? \"default-deny\";\n const reason = matched\n ? `Matched rule \"${matched.id}\"${matched.description ? `: ${matched.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n matchedRule: matched,\n subject: ctx.subject,\n action: ctx.action,\n resource: ctx.resource,\n resourceContext: ctx.resourceContext,\n tenantId: ctx.tenantId,\n timestamp: Date.now(),\n durationMs: performance.now() - start,\n reason,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fluent check chain: can(user).perform(action).on(resource)\n// ---------------------------------------------------------------------------\n\nclass PerformStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n ) {}\n\n perform(action: InferAction<S>): OnStep<S> {\n return new OnStep(this.engine, this.subject, action);\n }\n}\n\nclass OnStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n private action: InferAction<S>,\n ) {}\n\n on(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n return this.engine.evaluate(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n\n async onAsync(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n return this.engine.evaluateAsync(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Simple LRU cache\n// ---------------------------------------------------------------------------\n\nfunction buildCacheKey(\n subjectId: string,\n action: string,\n resource: string,\n tenantId?: string,\n): string {\n return `${subjectId.length}:${subjectId}\\0${action}\\0${resource}\\0${tenantId ?? \"\"}`;\n}\n\nclass LRUCache<V> {\n private map = new Map<string, V>();\n readonly maxSize: number;\n\n constructor(maxSize: number) {\n this.maxSize = maxSize;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(key: string): V | undefined {\n const value = this.map.get(key);\n if (value !== undefined) {\n this.map.delete(key);\n this.map.set(key, value);\n }\n return value;\n }\n\n set(key: string, value: V): void {\n if (this.map.has(key)) {\n this.map.delete(key);\n } else if (this.map.size >= this.maxSize) {\n const oldest = this.map.keys().next().value;\n if (oldest !== undefined) this.map.delete(oldest);\n }\n this.map.set(key, value);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import type { SchemaDefinition, InferRole } from \"./types.js\";\n\n/**\n * Defines a role inheritance hierarchy.\n *\n * When a role inherits from another, it gains all permissions of its parent roles.\n * Cycles are detected and rejected at definition time.\n *\n * ```ts\n * const hierarchy = new RoleHierarchy<MySchema>()\n * .define(\"admin\", [\"manager\", \"viewer\"])\n * .define(\"manager\", [\"member\"])\n * .define(\"member\", [\"viewer\"]);\n *\n * hierarchy.resolve(\"admin\");\n * // Set { \"admin\", \"manager\", \"member\", \"viewer\" }\n * ```\n */\nexport class RoleHierarchy<S extends SchemaDefinition> {\n private parents = new Map<string, string[]>();\n private cache = new Map<string, Set<string>>();\n\n /**\n * Define that `role` inherits permissions from `inheritsFrom` roles.\n * Clears the resolution cache.\n */\n define(role: InferRole<S>, inheritsFrom: InferRole<S>[]): this {\n this.parents.set(role, inheritsFrom as string[]);\n this.cache.clear();\n this.detectCycle(role as string, new Set());\n return this;\n }\n\n /**\n * Resolve the full set of roles a given role expands to,\n * including all inherited roles (transitive).\n */\n resolve(role: InferRole<S>): Set<string> {\n const roleStr = role as string;\n const cached = this.cache.get(roleStr);\n if (cached) return cached;\n\n const result = new Set<string>();\n this.walk(roleStr, result);\n this.cache.set(roleStr, result);\n return result;\n }\n\n /**\n * Resolve multiple roles at once, returning the merged set.\n */\n resolveAll(roles: Iterable<InferRole<S>>): Set<string> {\n const result = new Set<string>();\n for (const role of roles) {\n for (const r of this.resolve(role)) {\n result.add(r);\n }\n }\n return result;\n }\n\n /**\n * Get all defined roles that have inheritance rules.\n */\n definedRoles(): string[] {\n return [...this.parents.keys()];\n }\n\n private walk(role: string, visited: Set<string>): void {\n if (visited.has(role)) return;\n visited.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.walk(parent, visited);\n }\n }\n }\n\n private detectCycle(role: string, visiting: Set<string>): void {\n if (visiting.has(role)) {\n throw new Error(\n `Cycle detected in role hierarchy: ${[...visiting, role].join(\" → \")}`,\n );\n }\n visiting.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.detectCycle(parent, visiting);\n }\n }\n visiting.delete(role);\n }\n}\n","import type {\n SchemaDefinition,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSON-safe policy representation (conditions become named references)\n// ---------------------------------------------------------------------------\n\nexport interface JsonPolicyRule {\n id: string;\n effect: PolicyEffect;\n roles: string[] | \"*\";\n actions: string[] | \"*\";\n resources: string[] | \"*\";\n conditions?: string[];\n priority?: number;\n description?: string;\n}\n\nexport interface JsonPolicyDocument {\n version: 1;\n rules: JsonPolicyRule[];\n}\n\n/**\n * A registry that maps condition names to condition functions.\n * This allows JSON policies to reference conditions by name\n * while keeping the actual logic in code.\n *\n * ```ts\n * const conditions = new ConditionRegistry<MySchema>();\n * conditions.register(\"isOwner\", ctx => ctx.subject.id === ctx.resourceContext.ownerId);\n * conditions.register(\"isActive\", ctx => ctx.resourceContext.status === \"active\");\n * ```\n */\nexport class ConditionRegistry<S extends SchemaDefinition> {\n private conditions = new Map<string, Condition<S>>();\n\n register(name: string, condition: Condition<S>): this {\n if (!name || typeof name !== \"string\") {\n throw new Error(\"Condition name must be a non-empty string\");\n }\n if (typeof condition !== \"function\") {\n throw new Error(`Condition \"${name}\" must be a function`);\n }\n this.conditions.set(name, condition);\n return this;\n }\n\n get(name: string): Condition<S> | undefined {\n return this.conditions.get(name);\n }\n\n has(name: string): boolean {\n return this.conditions.has(name);\n }\n\n names(): string[] {\n return [...this.conditions.keys()];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Export: PolicyRule[] → JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize rules to a JSON-safe document.\n * Conditions are stripped unless a reverse lookup map is provided.\n */\nexport function exportRules<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): JsonPolicyDocument {\n const jsonRules: JsonPolicyRule[] = rules.map((rule) => {\n const jr: JsonPolicyRule = {\n id: rule.id,\n effect: rule.effect,\n roles: rule.roles,\n actions: rule.actions as string[] | \"*\",\n resources: rule.resources as string[] | \"*\",\n priority: rule.priority,\n description: rule.description,\n };\n\n if (rule.conditions && conditionNames) {\n const names: string[] = [];\n for (const cond of rule.conditions) {\n const name = conditionNames.get(cond);\n if (name) names.push(name);\n }\n if (names.length > 0) jr.conditions = names;\n }\n\n return jr;\n });\n\n return { version: 1, rules: jsonRules };\n}\n\n/**\n * Serialize rules to a JSON string.\n */\nexport function exportRulesToJson<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): string {\n return JSON.stringify(exportRules(rules, conditionNames), null, 2);\n}\n\n// ---------------------------------------------------------------------------\n// Import: JSON → PolicyRule[]\n// ---------------------------------------------------------------------------\n\n/**\n * Deserialize a JSON policy document into PolicyRule objects.\n * Condition names are resolved via the provided registry.\n */\nexport function importRules<S extends SchemaDefinition>(\n doc: JsonPolicyDocument,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n if (!doc || typeof doc !== \"object\") {\n throw new Error(\"Policy document must be a non-null object\");\n }\n if (doc.version !== 1) {\n throw new Error(`Unsupported policy document version: ${doc.version}`);\n }\n if (!Array.isArray(doc.rules)) {\n throw new Error(\"Policy document must have a 'rules' array\");\n }\n\n return doc.rules.map((jr, index) => {\n if (!jr || typeof jr !== \"object\") {\n throw new Error(`Rule at index ${index} must be a non-null object`);\n }\n\n if (!jr.id || typeof jr.id !== \"string\") {\n throw new Error(`Rule at index ${index} is missing a valid \"id\" field.`);\n }\n\n if (jr.effect !== \"allow\" && jr.effect !== \"deny\") {\n throw new Error(\n `Invalid effect \"${jr.effect}\" in rule \"${jr.id}\". Must be \"allow\" or \"deny\".`,\n );\n }\n\n if (jr.roles !== \"*\" && !Array.isArray(jr.roles)) {\n throw new Error(`Rule \"${jr.id}\": roles must be \"*\" or an array of strings`);\n }\n if (jr.actions !== \"*\" && !Array.isArray(jr.actions)) {\n throw new Error(`Rule \"${jr.id}\": actions must be \"*\" or an array of strings`);\n }\n if (jr.resources !== \"*\" && !Array.isArray(jr.resources)) {\n throw new Error(`Rule \"${jr.id}\": resources must be \"*\" or an array of strings`);\n }\n\n const conditions: Condition<S>[] = [];\n if (jr.conditions && registry) {\n for (const name of jr.conditions) {\n const cond = registry.get(name);\n if (!cond) {\n throw new Error(\n `Unknown condition \"${name}\" in rule \"${jr.id}\". ` +\n `Registered conditions: ${registry.names().join(\", \") || \"(none)\"}`,\n );\n }\n conditions.push(cond);\n }\n }\n\n return {\n id: jr.id,\n effect: jr.effect,\n roles: jr.roles,\n actions: jr.actions,\n resources: jr.resources,\n conditions: conditions.length > 0 ? conditions : undefined,\n priority: jr.priority,\n description: jr.description,\n } as PolicyRule<S>;\n });\n}\n\n/**\n * Parse a JSON string into PolicyRule objects.\n */\nexport function importRulesFromJson<S extends SchemaDefinition>(\n json: string,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n let doc: JsonPolicyDocument;\n try {\n doc = JSON.parse(json) as JsonPolicyDocument;\n } catch (err) {\n throw new Error(\n `Failed to parse policy JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n return importRules(doc, registry);\n}\n","/**\n * Core type definitions for the authorization engine.\n *\n * The type system is designed so that defining a schema once\n * propagates full autocomplete through every API surface:\n * policies, checks, audits, and middleware.\n */\n\n// ---------------------------------------------------------------------------\n// Schema definition types — what users provide to configure the engine\n// ---------------------------------------------------------------------------\n\nexport type ActionString = `${string}:${string}`;\n\nexport interface SchemaDefinition {\n roles: string;\n resources: string;\n actions: ActionString;\n tenantId?: string;\n}\n\n/**\n * Infer concrete union types from a schema definition.\n * Used internally to thread type narrowing everywhere.\n */\nexport type InferRole<S extends SchemaDefinition> = S[\"roles\"];\nexport type InferResource<S extends SchemaDefinition> = S[\"resources\"];\nexport type InferAction<S extends SchemaDefinition> = S[\"actions\"];\nexport type InferTenantId<S extends SchemaDefinition> = S[\"tenantId\"] extends string\n ? S[\"tenantId\"]\n : string;\n\n// ---------------------------------------------------------------------------\n// User / Subject\n// ---------------------------------------------------------------------------\n\nexport interface RoleAssignment<S extends SchemaDefinition> {\n role: InferRole<S>;\n tenantId?: InferTenantId<S>;\n}\n\nexport interface Subject<S extends SchemaDefinition> {\n id: string;\n roles: RoleAssignment<S>[];\n attributes?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Resource context passed during evaluation\n// ---------------------------------------------------------------------------\n\nexport interface ResourceContext {\n id?: string;\n tenantId?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Evaluation context — what conditions receive\n// ---------------------------------------------------------------------------\n\nexport interface EvaluationContext<S extends SchemaDefinition> {\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n environment?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Condition — a predicate attached to a policy rule\n// ---------------------------------------------------------------------------\n\nexport type Condition<S extends SchemaDefinition> = (\n ctx: EvaluationContext<S>,\n) => boolean | Promise<boolean>;\n\n// ---------------------------------------------------------------------------\n// Policy rule — the atomic unit of authorization\n// ---------------------------------------------------------------------------\n\nexport type PolicyEffect = \"allow\" | \"deny\";\n\nexport interface PolicyRule<S extends SchemaDefinition> {\n readonly id: string;\n readonly effect: PolicyEffect;\n readonly roles: InferRole<S>[] | \"*\";\n readonly actions: InferAction<S>[] | \"*\";\n readonly resources: InferResource<S>[] | \"*\";\n readonly conditions?: Condition<S>[];\n /**\n * Higher priority wins. Deny at equal priority wins over allow.\n * Default: 0\n */\n readonly priority?: number;\n readonly description?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Decision — the result of evaluating a request\n// ---------------------------------------------------------------------------\n\nexport interface Decision<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n matchedRule: PolicyRule<S> | null;\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n// ---------------------------------------------------------------------------\n// Audit entry — serialization-safe version of Decision\n// ---------------------------------------------------------------------------\n\nexport interface AuditEntry {\n allowed: boolean;\n effect: string;\n matchedRuleId: string | null;\n matchedRuleDescription: string | null;\n subjectId: string;\n action: string;\n resource: string;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n/**\n * Convert a Decision to a serialization-safe AuditEntry\n * (strips functions, large objects, and condition references).\n */\nexport function toAuditEntry<S extends SchemaDefinition>(decision: Decision<S>): AuditEntry {\n return {\n allowed: decision.allowed,\n effect: decision.effect,\n matchedRuleId: decision.matchedRule?.id ?? null,\n matchedRuleDescription: decision.matchedRule?.description ?? null,\n subjectId: decision.subject.id,\n action: decision.action as string,\n resource: decision.resource as string,\n tenantId: decision.tenantId,\n timestamp: decision.timestamp,\n durationMs: decision.durationMs,\n reason: decision.reason,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Explain result — detailed evaluation trace for debugging\n// ---------------------------------------------------------------------------\n\nexport interface ConditionResult {\n index: number;\n passed: boolean;\n error?: string;\n}\n\nexport interface RuleEvaluation<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n roleMatched: boolean;\n actionMatched: boolean;\n resourceMatched: boolean;\n conditionResults: ConditionResult[];\n matched: boolean;\n}\n\nexport interface ExplainResult<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n reason: string;\n evaluatedRules: RuleEvaluation<S>[];\n durationMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Audit / Observability\n// ---------------------------------------------------------------------------\n\nexport type DecisionListener<S extends SchemaDefinition> = (\n decision: Decision<S>,\n) => void | Promise<void>;\n\nexport interface ConditionError {\n ruleId: string;\n conditionIndex: number;\n error: unknown;\n}\n\nexport type ConditionErrorHandler = (err: ConditionError) => void;\n\n// ---------------------------------------------------------------------------\n// Engine options\n// ---------------------------------------------------------------------------\n\nexport interface EngineOptions<S extends SchemaDefinition> {\n schema: S;\n defaultEffect?: PolicyEffect;\n onDecision?: DecisionListener<S>;\n onConditionError?: ConditionErrorHandler;\n /**\n * When true, async conditions are awaited.\n * When false (default), only synchronous conditions are supported\n * and evaluate is guaranteed synchronous.\n */\n asyncConditions?: boolean;\n /**\n * When true, evaluate() throws if tenantId is omitted and the subject\n * has any tenant-scoped role assignments. Prevents accidental\n * cross-tenant privilege escalation.\n */\n strictTenancy?: boolean;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/policy-builder.ts","../src/engine.ts","../src/role-hierarchy.ts","../src/serialization.ts","../src/types.ts"],"sourcesContent":["import type {\n SchemaDefinition,\n InferRole,\n InferAction,\n InferResource,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\nlet ruleCounter = 0;\n\nfunction nextRuleId(prefix: string): string {\n return `${prefix}-${++ruleCounter}`;\n}\n\nexport class RuleBuilder<S extends SchemaDefinition> {\n private _effect: PolicyEffect;\n private _roles: InferRole<S>[] | \"*\" = \"*\";\n private _actions: InferAction<S>[] | \"*\" = \"*\";\n private _resources: InferResource<S>[] | \"*\" = \"*\";\n private _conditions: Condition<S>[] = [];\n private _priority = 0;\n private _description?: string;\n private _id: string;\n\n constructor(effect: PolicyEffect) {\n this._effect = effect;\n this._id = nextRuleId(effect);\n }\n\n id(id: string): this {\n this._id = id;\n return this;\n }\n\n roles(...roles: InferRole<S>[]): this {\n this._roles = roles;\n return this;\n }\n\n anyRole(): this {\n this._roles = \"*\";\n return this;\n }\n\n actions(...actions: InferAction<S>[]): this {\n this._actions = actions;\n return this;\n }\n\n anyAction(): this {\n this._actions = \"*\";\n return this;\n }\n\n on(...resources: InferResource<S>[]): this {\n this._resources = resources;\n return this;\n }\n\n anyResource(): this {\n this._resources = \"*\";\n return this;\n }\n\n when(condition: Condition<S>): this {\n this._conditions.push(condition);\n return this;\n }\n\n priority(p: number): this {\n this._priority = p;\n return this;\n }\n\n describe(desc: string): this {\n this._description = desc;\n return this;\n }\n\n build(): PolicyRule<S> {\n return {\n id: this._id,\n effect: this._effect,\n roles: this._roles,\n actions: this._actions,\n resources: this._resources,\n conditions: this._conditions.length > 0 ? this._conditions : undefined,\n priority: this._priority,\n description: this._description,\n };\n }\n}\n\nexport function allow<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"allow\");\n}\n\nexport function deny<S extends SchemaDefinition>(): RuleBuilder<S> {\n return new RuleBuilder<S>(\"deny\");\n}\n\n/**\n * Creates schema-bound allow/deny factories so you don't need to pass\n * the generic parameter on every call.\n *\n * ```ts\n * const { allow, deny } = createPolicyFactory<MySchema>();\n * allow().roles(\"admin\").anyAction().anyResource().build();\n * ```\n */\nexport function createPolicyFactory<S extends SchemaDefinition>(): {\n allow: () => RuleBuilder<S>;\n deny: () => RuleBuilder<S>;\n} {\n return {\n allow: () => new RuleBuilder<S>(\"allow\"),\n deny: () => new RuleBuilder<S>(\"deny\"),\n };\n}\n","import type {\n SchemaDefinition,\n InferAction,\n InferResource,\n InferRole,\n PolicyRule,\n Decision,\n Subject,\n ResourceContext,\n EvaluationContext,\n DecisionListener,\n EngineOptions,\n ConditionErrorHandler,\n ExplainResult,\n RuleEvaluation,\n ConditionResult,\n} from \"./types.js\";\nimport type { RuleBuilder } from \"./policy-builder.js\";\nimport { allow as _allow, deny as _deny } from \"./policy-builder.js\";\nimport type { RoleHierarchy } from \"./role-hierarchy.js\";\n\n// ---------------------------------------------------------------------------\n// Compiled rule — internal representation with pre-compiled regex\n// ---------------------------------------------------------------------------\n\ninterface CompiledRule<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n actionPatterns: RegExp[] | null;\n}\n\nfunction escapeRegexMeta(s: string): string {\n return s.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n value != null &&\n typeof value === \"object\" &&\n typeof (value as PromiseLike<unknown>).then === \"function\"\n );\n}\n\nconst ASYNC_CONDITION_EVALUATE_MSG =\n \"Async condition encountered. Use evaluateAsync() instead.\";\nconst ASYNC_CONDITION_EXPLAIN_MSG =\n \"Async condition encountered. Use explainAsync() instead.\";\n\nfunction compileActionPatterns(actions: string[] | \"*\"): RegExp[] | null {\n if (actions === \"*\") return null;\n const patterns: RegExp[] = [];\n for (const action of actions) {\n if (action.includes(\"*\")) {\n const escaped = escapeRegexMeta(action).replace(/\\*/g, \"[^:]*\");\n patterns.push(new RegExp(\"^\" + escaped + \"$\"));\n }\n }\n return patterns.length > 0 ? patterns : null;\n}\n\n// ---------------------------------------------------------------------------\n// Engine\n// ---------------------------------------------------------------------------\n\nexport interface AccessEngineOptions<S extends SchemaDefinition> extends EngineOptions<S> {\n roleHierarchy?: RoleHierarchy<S>;\n /**\n * Enable LRU cache for evaluation results.\n * Only caches evaluations of rules WITHOUT conditions (context-independent).\n * Rules with conditions are never cached since their result depends on resourceContext.\n */\n cacheSize?: number;\n}\n\nexport class AccessEngine<S extends SchemaDefinition> {\n private compiled: CompiledRule<S>[] = [];\n private listeners: DecisionListener<S>[] = [];\n private asyncConditions: boolean;\n private _defaultDeny: boolean;\n private _strictTenancy: boolean;\n private hierarchy?: RoleHierarchy<S>;\n private cache?: LRUCache<Decision<S>>;\n private conditionErrorHandler?: ConditionErrorHandler;\n\n constructor(options: AccessEngineOptions<S>) {\n this.asyncConditions = options.asyncConditions ?? false;\n this._defaultDeny = (options.defaultEffect ?? \"deny\") === \"deny\";\n this._strictTenancy = options.strictTenancy ?? false;\n this.hierarchy = options.roleHierarchy;\n this.conditionErrorHandler = options.onConditionError;\n if (options.cacheSize && options.cacheSize > 0) {\n this.cache = new LRUCache(options.cacheSize);\n }\n if (options.onDecision) {\n this.listeners.push(options.onDecision);\n }\n }\n\n // -----------------------------------------------------------------------\n // Rule management\n // -----------------------------------------------------------------------\n\n addRule(rule: PolicyRule<S>): this {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n this.cache?.clear();\n return this;\n }\n\n addRules(...rules: PolicyRule<S>[]): this {\n for (const rule of rules) {\n const frozen = Object.freeze({ ...rule });\n this.compiled.push({\n rule: frozen,\n actionPatterns: compileActionPatterns(frozen.actions as string[] | \"*\"),\n });\n }\n this.cache?.clear();\n return this;\n }\n\n removeRule(ruleId: string): boolean {\n const idx = this.compiled.findIndex((c) => c.rule.id === ruleId);\n if (idx === -1) return false;\n this.compiled.splice(idx, 1);\n this.cache?.clear();\n return true;\n }\n\n getRules(): ReadonlyArray<PolicyRule<S>> {\n return this.compiled.map((c) => c.rule);\n }\n\n clearRules(): void {\n this.compiled = [];\n this.cache?.clear();\n }\n\n // -----------------------------------------------------------------------\n // Cache control\n // -----------------------------------------------------------------------\n\n clearCache(): void {\n this.cache?.clear();\n }\n\n get cacheStats(): { size: number; maxSize: number } | null {\n if (!this.cache) return null;\n return { size: this.cache.size, maxSize: this.cache.maxSize };\n }\n\n // -----------------------------------------------------------------------\n // Fluent rule builders bound to this engine's schema\n // -----------------------------------------------------------------------\n\n allow(): RuleBuilder<S> {\n return _allow<S>();\n }\n\n deny(): RuleBuilder<S> {\n return _deny<S>();\n }\n\n // -----------------------------------------------------------------------\n // Observability\n // -----------------------------------------------------------------------\n\n onDecision(listener: DecisionListener<S>): () => void {\n this.listeners.push(listener);\n return () => {\n const idx = this.listeners.indexOf(listener);\n if (idx !== -1) this.listeners.splice(idx, 1);\n };\n }\n\n private emit(decision: Decision<S>): void {\n for (const listener of this.listeners) {\n try {\n const result = listener(decision);\n if (result instanceof Promise) {\n result.catch(() => {});\n }\n } catch {\n // listeners must not break evaluation\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Evaluation\n // -----------------------------------------------------------------------\n\n evaluate(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use evaluateAsync() instead.\",\n );\n }\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n\n const cacheKey = this.cache\n ? buildCacheKey(subject.id, action as string, resource as string, tenantId)\n : undefined;\n if (cacheKey) {\n const cached = this.cache!.get(cacheKey);\n if (cached) {\n this.emit(cached);\n return cached;\n }\n }\n\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: CompiledRule<S> | null = null;\n let matchedHasConditions = false;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = compiled;\n matchedHasConditions = false;\n break;\n }\n const allMet = this.evaluateConditionsSync(rule, ctx);\n if (allMet) {\n matched = compiled;\n matchedHasConditions = true;\n break;\n }\n }\n\n const decision = this.buildDecision(matched?.rule ?? null, ctx, start);\n\n if (cacheKey && !matchedHasConditions) {\n this.cache!.set(cacheKey, decision);\n }\n\n this.emit(decision);\n return decision;\n }\n\n async evaluateAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n this.validateInput(subject, action, resource);\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const candidates = this.matchRules(subject, action, resource, tenantId);\n\n let matched: PolicyRule<S> | null = null;\n\n for (const compiled of candidates) {\n const { rule } = compiled;\n if (!rule.conditions || rule.conditions.length === 0) {\n matched = rule;\n break;\n }\n const results = await Promise.all(\n rule.conditions.map((c, i) =>\n Promise.resolve()\n .then(() => c(ctx))\n .catch((err) => {\n this.emitConditionError(rule.id, i, err);\n return false;\n }),\n ),\n );\n if (results.every(Boolean)) {\n matched = rule;\n break;\n }\n }\n\n const decision = this.buildDecision(matched, ctx, start);\n this.emit(decision);\n return decision;\n }\n\n // -----------------------------------------------------------------------\n // permitted() — list allowed actions on a resource for UI rendering\n // -----------------------------------------------------------------------\n\n permitted(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Set<InferAction<S>> {\n const allowed = new Set<InferAction<S>>();\n for (const action of actions) {\n const decision = this.asyncConditions\n ? (() => { throw new Error(\"Use permittedAsync() with asyncConditions enabled.\"); })()\n : this.evaluate(subject, action, resource, resourceContext, tenantId);\n if (decision.allowed) {\n allowed.add(action);\n }\n }\n return allowed;\n }\n\n async permittedAsync(\n subject: Subject<S>,\n resource: InferResource<S>,\n actions: InferAction<S>[],\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Set<InferAction<S>>> {\n const allowed = new Set<InferAction<S>>();\n const results = await Promise.all(\n actions.map((action) =>\n this.evaluateAsync(subject, action, resource, resourceContext, tenantId),\n ),\n );\n for (let i = 0; i < actions.length; i++) {\n if (results[i]!.allowed) {\n allowed.add(actions[i]!);\n }\n }\n return allowed;\n }\n\n // -----------------------------------------------------------------------\n // explain() — full evaluation trace for debugging\n // -----------------------------------------------------------------------\n\n explain(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): ExplainResult<S> {\n if (this.asyncConditions) {\n throw new Error(\n \"Engine has asyncConditions enabled. Use explainAsync() instead.\",\n );\n }\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (isThenable(result)) {\n throw new Error(ASYNC_CONDITION_EXPLAIN_MSG);\n }\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n if (\n err instanceof Error &&\n err.message === ASYNC_CONDITION_EXPLAIN_MSG\n ) {\n throw err;\n }\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n async explainAsync(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<ExplainResult<S>> {\n this.enforceTenancy(subject, tenantId);\n const start = performance.now();\n const ctx = this.buildContext(subject, action, resource, resourceContext, tenantId);\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const evaluatedRules: RuleEvaluation<S>[] = [];\n let firstMatch: PolicyRule<S> | null = null;\n\n const sorted = this.sortCandidates([...this.compiled]);\n\n for (const compiled of sorted) {\n const { rule } = compiled;\n const roleMatched = rule.roles === \"*\" || rule.roles.some((r) => subjectRoles.has(r));\n const actionMatched = rule.actions === \"*\" || this.matchesAction(compiled, action);\n const resourceMatched = rule.resources === \"*\" || rule.resources.includes(resource);\n\n const conditionResults: ConditionResult[] = [];\n let allConditionsPassed = true;\n\n if (roleMatched && actionMatched && resourceMatched && rule.conditions) {\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = await rule.conditions[i]!(ctx);\n if (result !== true) {\n conditionResults.push({ index: i, passed: false });\n allConditionsPassed = false;\n } else {\n conditionResults.push({ index: i, passed: true });\n }\n } catch (err) {\n conditionResults.push({\n index: i,\n passed: false,\n error: err instanceof Error ? err.message : String(err),\n });\n allConditionsPassed = false;\n }\n }\n }\n\n const matched =\n roleMatched && actionMatched && resourceMatched &&\n (!rule.conditions || rule.conditions.length === 0 || allConditionsPassed);\n\n evaluatedRules.push({\n rule,\n roleMatched,\n actionMatched,\n resourceMatched,\n conditionResults,\n matched,\n });\n\n if (matched && !firstMatch) {\n firstMatch = rule;\n }\n }\n\n const allowed = firstMatch != null ? firstMatch.effect === \"allow\" : !this._defaultDeny;\n const effect = firstMatch?.effect ?? \"default-deny\";\n const reason = firstMatch\n ? `Matched rule \"${firstMatch.id}\"${firstMatch.description ? `: ${firstMatch.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n reason,\n evaluatedRules,\n durationMs: performance.now() - start,\n };\n }\n\n // -----------------------------------------------------------------------\n // Fluent check API: can(subject).perform(action).on(resource)\n // -----------------------------------------------------------------------\n\n can(subject: Subject<S>): PerformStep<S> {\n return new PerformStep(this, subject);\n }\n\n // -----------------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------------\n\n private validateInput(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n ): void {\n if (!subject || typeof subject.id !== \"string\") {\n throw new Error(\"subject must be an object with a string id\");\n }\n if (!Array.isArray(subject.roles)) {\n throw new Error(\"subject.roles must be an array\");\n }\n if (!action || typeof action !== \"string\") {\n throw new Error(\"action must be a non-empty string\");\n }\n if (!resource || typeof resource !== \"string\") {\n throw new Error(\"resource must be a non-empty string\");\n }\n }\n\n private enforceTenancy(subject: Subject<S>, tenantId?: string): void {\n if (!this._strictTenancy || tenantId != null) return;\n const hasTenantScoped = subject.roles.some((r) => r.tenantId != null);\n if (hasTenantScoped) {\n throw new Error(\n \"strictTenancy is enabled and subject has tenant-scoped roles, \" +\n \"but no tenantId was provided to evaluate(). This could cause \" +\n \"cross-tenant privilege escalation. Pass an explicit tenantId.\",\n );\n }\n }\n\n private buildContext(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n resourceContext: ResourceContext,\n tenantId?: string,\n ): EvaluationContext<S> {\n return { subject, action, resource, resourceContext, tenantId };\n }\n\n private evaluateConditionsSync(\n rule: PolicyRule<S>,\n ctx: EvaluationContext<S>,\n ): boolean {\n if (!rule.conditions) return true;\n for (let i = 0; i < rule.conditions.length; i++) {\n try {\n const result = rule.conditions[i]!(ctx);\n if (isThenable(result)) {\n throw new Error(ASYNC_CONDITION_EVALUATE_MSG);\n }\n if (result !== true) return false;\n } catch (err) {\n if (\n err instanceof Error &&\n err.message === ASYNC_CONDITION_EVALUATE_MSG\n ) {\n throw err;\n }\n this.emitConditionError(rule.id, i, err);\n return false;\n }\n }\n return true;\n }\n\n private emitConditionError(ruleId: string, conditionIndex: number, error: unknown): void {\n if (this.conditionErrorHandler) {\n try {\n this.conditionErrorHandler({ ruleId, conditionIndex, error });\n } catch {\n // error handler must not break evaluation\n }\n }\n }\n\n private matchRules(\n subject: Subject<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n tenantId?: string,\n ): CompiledRule<S>[] {\n const subjectRoles = this.resolveRoles(subject, tenantId);\n\n const matched = this.compiled.filter((compiled) => {\n const { rule } = compiled;\n if (rule.roles !== \"*\" && !rule.roles.some((r) => subjectRoles.has(r))) return false;\n if (rule.actions !== \"*\" && !this.matchesAction(compiled, action)) return false;\n if (rule.resources !== \"*\" && !rule.resources.includes(resource)) return false;\n return true;\n });\n\n return this.sortCandidates(matched);\n }\n\n private sortCandidates(candidates: CompiledRule<S>[]): CompiledRule<S>[] {\n return candidates.sort((a, b) => {\n const pa = a.rule.priority ?? 0;\n const pb = b.rule.priority ?? 0;\n if (pb !== pa) return pb - pa;\n if (a.rule.effect === \"deny\" && b.rule.effect === \"allow\") return -1;\n if (a.rule.effect === \"allow\" && b.rule.effect === \"deny\") return 1;\n return 0;\n });\n }\n\n private matchesAction(\n compiled: CompiledRule<S>,\n action: InferAction<S>,\n ): boolean {\n const { rule, actionPatterns } = compiled;\n if (rule.actions === \"*\") return true;\n const actionStr = action as string;\n if ((rule.actions as string[]).includes(actionStr)) return true;\n if (actionPatterns) {\n for (const pattern of actionPatterns) {\n if (pattern.test(actionStr)) return true;\n }\n }\n return false;\n }\n\n private resolveRoles(\n subject: Subject<S>,\n tenantId?: string,\n ): Set<string> {\n const directRoles = new Set<string>();\n for (const assignment of subject.roles) {\n if (tenantId == null || assignment.tenantId == null || assignment.tenantId === tenantId) {\n directRoles.add(assignment.role);\n }\n }\n\n if (!this.hierarchy) return directRoles;\n\n const expanded = new Set<string>();\n for (const role of directRoles) {\n for (const r of this.hierarchy.resolve(role as InferRole<S>)) {\n expanded.add(r);\n }\n }\n return expanded;\n }\n\n private buildDecision(\n matched: PolicyRule<S> | null,\n ctx: EvaluationContext<S>,\n start: number,\n ): Decision<S> {\n const allowed =\n matched != null ? matched.effect === \"allow\" : !this._defaultDeny;\n const effect = matched?.effect ?? \"default-deny\";\n const reason = matched\n ? `Matched rule \"${matched.id}\"${matched.description ? `: ${matched.description}` : \"\"}`\n : \"No matching rule — default deny\";\n\n return {\n allowed,\n effect,\n matchedRule: matched,\n subject: ctx.subject,\n action: ctx.action,\n resource: ctx.resource,\n resourceContext: ctx.resourceContext,\n tenantId: ctx.tenantId,\n timestamp: Date.now(),\n durationMs: performance.now() - start,\n reason,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Fluent check chain: can(user).perform(action).on(resource)\n// ---------------------------------------------------------------------------\n\nclass PerformStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n ) {}\n\n perform(action: InferAction<S>): OnStep<S> {\n return new OnStep(this.engine, this.subject, action);\n }\n}\n\nclass OnStep<S extends SchemaDefinition> {\n constructor(\n private engine: AccessEngine<S>,\n private subject: Subject<S>,\n private action: InferAction<S>,\n ) {}\n\n on(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Decision<S> {\n return this.engine.evaluate(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n\n async onAsync(\n resource: InferResource<S>,\n resourceContext: ResourceContext = {},\n tenantId?: string,\n ): Promise<Decision<S>> {\n return this.engine.evaluateAsync(\n this.subject,\n this.action,\n resource,\n resourceContext,\n tenantId,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Simple LRU cache\n// ---------------------------------------------------------------------------\n\nfunction buildCacheKey(\n subjectId: string,\n action: string,\n resource: string,\n tenantId?: string,\n): string {\n return `${subjectId.length}:${subjectId}\\0${action}\\0${resource}\\0${tenantId ?? \"\"}`;\n}\n\nclass LRUCache<V> {\n private map = new Map<string, V>();\n readonly maxSize: number;\n\n constructor(maxSize: number) {\n this.maxSize = maxSize;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n get(key: string): V | undefined {\n const value = this.map.get(key);\n if (value !== undefined) {\n this.map.delete(key);\n this.map.set(key, value);\n }\n return value;\n }\n\n set(key: string, value: V): void {\n if (this.map.has(key)) {\n this.map.delete(key);\n } else if (this.map.size >= this.maxSize) {\n const oldest = this.map.keys().next().value;\n if (oldest !== undefined) this.map.delete(oldest);\n }\n this.map.set(key, value);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","import type { SchemaDefinition, InferRole } from \"./types.js\";\n\n/**\n * Defines a role inheritance hierarchy.\n *\n * When a role inherits from another, it gains all permissions of its parent roles.\n * Cycles are detected and rejected at definition time.\n *\n * ```ts\n * const hierarchy = new RoleHierarchy<MySchema>()\n * .define(\"admin\", [\"manager\", \"viewer\"])\n * .define(\"manager\", [\"member\"])\n * .define(\"member\", [\"viewer\"]);\n *\n * hierarchy.resolve(\"admin\");\n * // Set { \"admin\", \"manager\", \"member\", \"viewer\" }\n * ```\n */\nexport class RoleHierarchy<S extends SchemaDefinition> {\n private parents = new Map<string, string[]>();\n private cache = new Map<string, Set<string>>();\n\n /**\n * Define that `role` inherits permissions from `inheritsFrom` roles.\n * Clears the resolution cache.\n */\n define(role: InferRole<S>, inheritsFrom: InferRole<S>[]): this {\n this.parents.set(role, inheritsFrom as string[]);\n this.cache.clear();\n this.detectCycle(role as string, new Set());\n return this;\n }\n\n /**\n * Resolve the full set of roles a given role expands to,\n * including all inherited roles (transitive).\n */\n resolve(role: InferRole<S>): Set<string> {\n const roleStr = role as string;\n const cached = this.cache.get(roleStr);\n if (cached) return cached;\n\n const result = new Set<string>();\n this.walk(roleStr, result);\n this.cache.set(roleStr, result);\n return result;\n }\n\n /**\n * Resolve multiple roles at once, returning the merged set.\n */\n resolveAll(roles: Iterable<InferRole<S>>): Set<string> {\n const result = new Set<string>();\n for (const role of roles) {\n for (const r of this.resolve(role)) {\n result.add(r);\n }\n }\n return result;\n }\n\n /**\n * Get all defined roles that have inheritance rules.\n */\n definedRoles(): string[] {\n return [...this.parents.keys()];\n }\n\n private walk(role: string, visited: Set<string>): void {\n if (visited.has(role)) return;\n visited.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.walk(parent, visited);\n }\n }\n }\n\n private detectCycle(role: string, visiting: Set<string>): void {\n if (visiting.has(role)) {\n throw new Error(\n `Cycle detected in role hierarchy: ${[...visiting, role].join(\" → \")}`,\n );\n }\n visiting.add(role);\n const parents = this.parents.get(role);\n if (parents) {\n for (const parent of parents) {\n this.detectCycle(parent, visiting);\n }\n }\n visiting.delete(role);\n }\n}\n","import type {\n SchemaDefinition,\n PolicyRule,\n PolicyEffect,\n Condition,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSON-safe policy representation (conditions become named references)\n// ---------------------------------------------------------------------------\n\nexport interface JsonPolicyRule {\n id: string;\n effect: PolicyEffect;\n roles: string[] | \"*\";\n actions: string[] | \"*\";\n resources: string[] | \"*\";\n conditions?: string[];\n priority?: number;\n description?: string;\n}\n\nexport interface JsonPolicyDocument {\n version: 1;\n rules: JsonPolicyRule[];\n}\n\n/**\n * A registry that maps condition names to condition functions.\n * This allows JSON policies to reference conditions by name\n * while keeping the actual logic in code.\n *\n * ```ts\n * const conditions = new ConditionRegistry<MySchema>();\n * conditions.register(\"isOwner\", ctx => ctx.subject.id === ctx.resourceContext.ownerId);\n * conditions.register(\"isActive\", ctx => ctx.resourceContext.status === \"active\");\n * ```\n */\nexport class ConditionRegistry<S extends SchemaDefinition> {\n private conditions = new Map<string, Condition<S>>();\n\n register(name: string, condition: Condition<S>): this {\n if (!name || typeof name !== \"string\") {\n throw new Error(\"Condition name must be a non-empty string\");\n }\n if (typeof condition !== \"function\") {\n throw new Error(`Condition \"${name}\" must be a function`);\n }\n this.conditions.set(name, condition);\n return this;\n }\n\n get(name: string): Condition<S> | undefined {\n return this.conditions.get(name);\n }\n\n has(name: string): boolean {\n return this.conditions.has(name);\n }\n\n names(): string[] {\n return [...this.conditions.keys()];\n }\n}\n\n// ---------------------------------------------------------------------------\n// Export: PolicyRule[] → JSON\n// ---------------------------------------------------------------------------\n\n/**\n * Serialize rules to a JSON-safe document.\n * Conditions are stripped unless a reverse lookup map is provided.\n */\nexport function exportRules<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): JsonPolicyDocument {\n const jsonRules: JsonPolicyRule[] = rules.map((rule) => {\n const jr: JsonPolicyRule = {\n id: rule.id,\n effect: rule.effect,\n roles: rule.roles,\n actions: rule.actions as string[] | \"*\",\n resources: rule.resources as string[] | \"*\",\n priority: rule.priority,\n description: rule.description,\n };\n\n if (rule.conditions && conditionNames) {\n const names: string[] = [];\n for (const cond of rule.conditions) {\n const name = conditionNames.get(cond);\n if (name) names.push(name);\n }\n if (names.length > 0) jr.conditions = names;\n }\n\n return jr;\n });\n\n return { version: 1, rules: jsonRules };\n}\n\n/**\n * Serialize rules to a JSON string.\n */\nexport function exportRulesToJson<S extends SchemaDefinition>(\n rules: ReadonlyArray<PolicyRule<S>>,\n conditionNames?: Map<Condition<S>, string>,\n): string {\n return JSON.stringify(exportRules(rules, conditionNames), null, 2);\n}\n\n// ---------------------------------------------------------------------------\n// Import: JSON → PolicyRule[]\n// ---------------------------------------------------------------------------\n\n/**\n * Deserialize a JSON policy document into PolicyRule objects.\n * Condition names are resolved via the provided registry.\n */\nexport function importRules<S extends SchemaDefinition>(\n doc: JsonPolicyDocument,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n if (!doc || typeof doc !== \"object\") {\n throw new Error(\"Policy document must be a non-null object\");\n }\n if (doc.version !== 1) {\n throw new Error(`Unsupported policy document version: ${doc.version}`);\n }\n if (!Array.isArray(doc.rules)) {\n throw new Error(\"Policy document must have a 'rules' array\");\n }\n\n return doc.rules.map((jr, index) => {\n if (!jr || typeof jr !== \"object\") {\n throw new Error(`Rule at index ${index} must be a non-null object`);\n }\n\n if (!jr.id || typeof jr.id !== \"string\") {\n throw new Error(`Rule at index ${index} is missing a valid \"id\" field.`);\n }\n\n if (jr.effect !== \"allow\" && jr.effect !== \"deny\") {\n throw new Error(\n `Invalid effect \"${jr.effect}\" in rule \"${jr.id}\". Must be \"allow\" or \"deny\".`,\n );\n }\n\n if (jr.roles !== \"*\" && !Array.isArray(jr.roles)) {\n throw new Error(`Rule \"${jr.id}\": roles must be \"*\" or an array of strings`);\n }\n if (jr.actions !== \"*\" && !Array.isArray(jr.actions)) {\n throw new Error(`Rule \"${jr.id}\": actions must be \"*\" or an array of strings`);\n }\n if (jr.resources !== \"*\" && !Array.isArray(jr.resources)) {\n throw new Error(`Rule \"${jr.id}\": resources must be \"*\" or an array of strings`);\n }\n\n const conditions: Condition<S>[] = [];\n if (jr.conditions && registry) {\n for (const name of jr.conditions) {\n const cond = registry.get(name);\n if (!cond) {\n throw new Error(\n `Unknown condition \"${name}\" in rule \"${jr.id}\". ` +\n `Registered conditions: ${registry.names().join(\", \") || \"(none)\"}`,\n );\n }\n conditions.push(cond);\n }\n }\n\n return {\n id: jr.id,\n effect: jr.effect,\n roles: jr.roles,\n actions: jr.actions,\n resources: jr.resources,\n conditions: conditions.length > 0 ? conditions : undefined,\n priority: jr.priority,\n description: jr.description,\n } as PolicyRule<S>;\n });\n}\n\n/**\n * Parse a JSON string into PolicyRule objects.\n */\nexport function importRulesFromJson<S extends SchemaDefinition>(\n json: string,\n registry?: ConditionRegistry<S>,\n): PolicyRule<S>[] {\n let doc: JsonPolicyDocument;\n try {\n doc = JSON.parse(json) as JsonPolicyDocument;\n } catch (err) {\n throw new Error(\n `Failed to parse policy JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n return importRules(doc, registry);\n}\n","/**\n * Core type definitions for the authorization engine.\n *\n * The type system is designed so that defining a schema once\n * propagates full autocomplete through every API surface:\n * policies, checks, audits, and middleware.\n */\n\n// ---------------------------------------------------------------------------\n// Schema definition types — what users provide to configure the engine\n// ---------------------------------------------------------------------------\n\nexport type ActionString = `${string}:${string}`;\n\nexport interface SchemaDefinition {\n roles: string;\n resources: string;\n actions: ActionString;\n tenantId?: string;\n}\n\n/**\n * Infer concrete union types from a schema definition.\n * Used internally to thread type narrowing everywhere.\n */\nexport type InferRole<S extends SchemaDefinition> = S[\"roles\"];\nexport type InferResource<S extends SchemaDefinition> = S[\"resources\"];\nexport type InferAction<S extends SchemaDefinition> = S[\"actions\"];\nexport type InferTenantId<S extends SchemaDefinition> = S[\"tenantId\"] extends string\n ? S[\"tenantId\"]\n : string;\n\n// ---------------------------------------------------------------------------\n// User / Subject\n// ---------------------------------------------------------------------------\n\nexport interface RoleAssignment<S extends SchemaDefinition> {\n role: InferRole<S>;\n tenantId?: InferTenantId<S>;\n}\n\nexport interface Subject<S extends SchemaDefinition> {\n id: string;\n roles: RoleAssignment<S>[];\n attributes?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Resource context passed during evaluation\n// ---------------------------------------------------------------------------\n\nexport interface ResourceContext {\n id?: string;\n tenantId?: string;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Evaluation context — what conditions receive\n// ---------------------------------------------------------------------------\n\nexport interface EvaluationContext<S extends SchemaDefinition> {\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n environment?: Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Condition — a predicate attached to a policy rule\n// ---------------------------------------------------------------------------\n\nexport type Condition<S extends SchemaDefinition> = (\n ctx: EvaluationContext<S>,\n) => boolean | Promise<boolean>;\n\n// ---------------------------------------------------------------------------\n// Policy rule — the atomic unit of authorization\n// ---------------------------------------------------------------------------\n\nexport type PolicyEffect = \"allow\" | \"deny\";\n\nexport interface PolicyRule<S extends SchemaDefinition> {\n readonly id: string;\n readonly effect: PolicyEffect;\n readonly roles: InferRole<S>[] | \"*\";\n readonly actions: InferAction<S>[] | \"*\";\n readonly resources: InferResource<S>[] | \"*\";\n readonly conditions?: Condition<S>[];\n /**\n * Higher priority wins. Deny at equal priority wins over allow.\n * Default: 0\n */\n readonly priority?: number;\n readonly description?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Decision — the result of evaluating a request\n// ---------------------------------------------------------------------------\n\nexport interface Decision<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n matchedRule: PolicyRule<S> | null;\n subject: Subject<S>;\n action: InferAction<S>;\n resource: InferResource<S>;\n resourceContext: ResourceContext;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n// ---------------------------------------------------------------------------\n// Audit entry — serialization-safe version of Decision\n// ---------------------------------------------------------------------------\n\nexport interface AuditEntry {\n allowed: boolean;\n effect: string;\n matchedRuleId: string | null;\n matchedRuleDescription: string | null;\n subjectId: string;\n action: string;\n resource: string;\n tenantId?: string;\n timestamp: number;\n durationMs: number;\n reason: string;\n}\n\n/**\n * Convert a Decision to a serialization-safe AuditEntry\n * (strips functions, large objects, and condition references).\n */\nexport function toAuditEntry<S extends SchemaDefinition>(decision: Decision<S>): AuditEntry {\n return {\n allowed: decision.allowed,\n effect: decision.effect,\n matchedRuleId: decision.matchedRule?.id ?? null,\n matchedRuleDescription: decision.matchedRule?.description ?? null,\n subjectId: decision.subject.id,\n action: decision.action as string,\n resource: decision.resource as string,\n tenantId: decision.tenantId,\n timestamp: decision.timestamp,\n durationMs: decision.durationMs,\n reason: decision.reason,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Explain result — detailed evaluation trace for debugging\n// ---------------------------------------------------------------------------\n\nexport interface ConditionResult {\n index: number;\n passed: boolean;\n error?: string;\n}\n\nexport interface RuleEvaluation<S extends SchemaDefinition> {\n rule: PolicyRule<S>;\n roleMatched: boolean;\n actionMatched: boolean;\n resourceMatched: boolean;\n conditionResults: ConditionResult[];\n matched: boolean;\n}\n\nexport interface ExplainResult<S extends SchemaDefinition> {\n allowed: boolean;\n effect: PolicyEffect | \"default-deny\";\n reason: string;\n evaluatedRules: RuleEvaluation<S>[];\n durationMs: number;\n}\n\n// ---------------------------------------------------------------------------\n// Audit / Observability\n// ---------------------------------------------------------------------------\n\nexport type DecisionListener<S extends SchemaDefinition> = (\n decision: Decision<S>,\n) => void | Promise<void>;\n\nexport interface ConditionError {\n ruleId: string;\n conditionIndex: number;\n error: unknown;\n}\n\nexport type ConditionErrorHandler = (err: ConditionError) => void;\n\n// ---------------------------------------------------------------------------\n// Engine options\n// ---------------------------------------------------------------------------\n\nexport interface EngineOptions<S extends SchemaDefinition> {\n schema: S;\n defaultEffect?: PolicyEffect;\n onDecision?: DecisionListener<S>;\n onConditionError?: ConditionErrorHandler;\n /**\n * When true, sync methods (evaluate, explain, permitted) throw immediately\n * to force use of evaluateAsync, explainAsync, permittedAsync.\n * When false (default), async conditions are detected at runtime and throw\n * with a clear error pointing to the async API.\n *\n * @deprecated This option is deprecated and will be removed in v2. Async\n * conditions are now detected automatically. Use evaluateAsync(),\n * explainAsync(), or permittedAsync() when you have async conditions.\n */\n asyncConditions?: boolean;\n /**\n * When true, evaluate() throws if tenantId is omitted and the subject\n * has any tenant-scoped role assignments. Prevents accidental\n * cross-tenant privilege escalation.\n */\n strictTenancy?: boolean;\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-C6IASR5F.cjs';
1
+ import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.cjs';
2
2
 
3
3
  /**
4
4
  * Minimal Express-compatible types so we don't depend on @types/express at runtime.
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-C6IASR5F.js';
1
+ import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.js';
2
2
 
3
3
  /**
4
4
  * Minimal Express-compatible types so we don't depend on @types/express at runtime.
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-C6IASR5F.cjs';
1
+ import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.cjs';
2
2
 
3
3
  /**
4
4
  * Minimal Fastify-compatible types so we don't depend on fastify at runtime.
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-C6IASR5F.js';
1
+ import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.js';
2
2
 
3
3
  /**
4
4
  * Minimal Fastify-compatible types so we don't depend on fastify at runtime.
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/middleware/hono.ts
21
+ var hono_exports = {};
22
+ __export(hono_exports, {
23
+ honoGuard: () => honoGuard
24
+ });
25
+ module.exports = __toCommonJS(hono_exports);
26
+ function honoGuard(engine, action, resource, options) {
27
+ return async (c, next) => {
28
+ const subject = options.getSubject(c);
29
+ if (!subject) {
30
+ return c.json({ error: "Unauthorized \u2014 no subject" }, 401);
31
+ }
32
+ let decision;
33
+ try {
34
+ const resourceContext = options.getResourceContext?.(c) ?? {};
35
+ const tenantId = options.getTenantId?.(c);
36
+ decision = engine.evaluate(subject, action, resource, resourceContext, tenantId);
37
+ } catch {
38
+ return c.json({ error: "Internal authorization error" }, 500);
39
+ }
40
+ if (decision.allowed) {
41
+ await next();
42
+ return;
43
+ }
44
+ if (options.onDenied) {
45
+ return options.onDenied(c);
46
+ }
47
+ return c.json({
48
+ error: "Forbidden",
49
+ reason: decision.reason
50
+ }, 403);
51
+ };
52
+ }
53
+ // Annotate the CommonJS export names for ESM import in node:
54
+ 0 && (module.exports = {
55
+ honoGuard
56
+ });
57
+ //# sourceMappingURL=hono.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/middleware/hono.ts"],"sourcesContent":["import type { AccessEngine } from \"../engine.js\";\nimport type { SchemaDefinition, InferAction, InferResource, Subject, ResourceContext } from \"../types.js\";\n\n/**\n * Minimal Hono-compatible types so we don't depend on hono at runtime.\n */\ninterface HonoContext {\n req: {\n raw: Request;\n header(name: string): string | undefined;\n param(name: string): string | undefined;\n [key: string]: unknown;\n };\n json(data: unknown, status?: number): Response;\n get<T = unknown>(key: string): T;\n set(key: string, value: unknown): void;\n [key: string]: unknown;\n}\n\ntype HonoNext = () => Promise<void>;\ntype HonoMiddleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\nexport interface HonoGuardOptions<S extends SchemaDefinition> {\n /** Extract the subject from the Hono context. */\n getSubject: (c: HonoContext) => Subject<S> | undefined;\n /** Extract the resource context from the Hono context (optional). */\n getResourceContext?: (c: HonoContext) => ResourceContext;\n /** Extract the tenant ID from the Hono context (optional). */\n getTenantId?: (c: HonoContext) => string | undefined;\n /** Custom denial handler. Return a Response to override the default 403. */\n onDenied?: (c: HonoContext) => Response;\n}\n\n/**\n * Hono middleware factory.\n *\n * Usage:\n * app.post(\n * \"/invoices/:id/approve\",\n * honoGuard(engine, \"invoice:approve\", \"invoice\", {\n * getSubject: (c) => c.get(\"user\"),\n * getTenantId: (c) => c.req.header(\"x-tenant-id\"),\n * }),\n * handler,\n * );\n */\nexport function honoGuard<S extends SchemaDefinition>(\n engine: AccessEngine<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n options: HonoGuardOptions<S>,\n): HonoMiddleware {\n return async (c: HonoContext, next: HonoNext) => {\n const subject = options.getSubject(c);\n if (!subject) {\n return c.json({ error: \"Unauthorized — no subject\" }, 401);\n }\n\n let decision;\n try {\n const resourceContext = options.getResourceContext?.(c) ?? {};\n const tenantId = options.getTenantId?.(c);\n decision = engine.evaluate(subject, action, resource, resourceContext, tenantId);\n } catch {\n return c.json({ error: \"Internal authorization error\" }, 500);\n }\n\n if (decision.allowed) {\n await next();\n return;\n }\n\n if (options.onDenied) {\n return options.onDenied(c);\n }\n\n return c.json({\n error: \"Forbidden\",\n reason: decision.reason,\n }, 403);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8CO,SAAS,UACd,QACA,QACA,UACA,SACgB;AAChB,SAAO,OAAO,GAAgB,SAAmB;AAC/C,UAAM,UAAU,QAAQ,WAAW,CAAC;AACpC,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,KAAK,EAAE,OAAO,iCAA4B,GAAG,GAAG;AAAA,IAC3D;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,kBAAkB,QAAQ,qBAAqB,CAAC,KAAK,CAAC;AAC5D,YAAM,WAAW,QAAQ,cAAc,CAAC;AACxC,iBAAW,OAAO,SAAS,SAAS,QAAQ,UAAU,iBAAiB,QAAQ;AAAA,IACjF,QAAQ;AACN,aAAO,EAAE,KAAK,EAAE,OAAO,+BAA+B,GAAG,GAAG;AAAA,IAC9D;AAEA,QAAI,SAAS,SAAS;AACpB,YAAM,KAAK;AACX;AAAA,IACF;AAEA,QAAI,QAAQ,UAAU;AACpB,aAAO,QAAQ,SAAS,CAAC;AAAA,IAC3B;AAEA,WAAO,EAAE,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,QAAQ,SAAS;AAAA,IACnB,GAAG,GAAG;AAAA,EACR;AACF;","names":[]}
@@ -0,0 +1,45 @@
1
+ import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.cjs';
2
+
3
+ /**
4
+ * Minimal Hono-compatible types so we don't depend on hono at runtime.
5
+ */
6
+ interface HonoContext {
7
+ req: {
8
+ raw: Request;
9
+ header(name: string): string | undefined;
10
+ param(name: string): string | undefined;
11
+ [key: string]: unknown;
12
+ };
13
+ json(data: unknown, status?: number): Response;
14
+ get<T = unknown>(key: string): T;
15
+ set(key: string, value: unknown): void;
16
+ [key: string]: unknown;
17
+ }
18
+ type HonoNext = () => Promise<void>;
19
+ type HonoMiddleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;
20
+ interface HonoGuardOptions<S extends SchemaDefinition> {
21
+ /** Extract the subject from the Hono context. */
22
+ getSubject: (c: HonoContext) => Subject<S> | undefined;
23
+ /** Extract the resource context from the Hono context (optional). */
24
+ getResourceContext?: (c: HonoContext) => ResourceContext;
25
+ /** Extract the tenant ID from the Hono context (optional). */
26
+ getTenantId?: (c: HonoContext) => string | undefined;
27
+ /** Custom denial handler. Return a Response to override the default 403. */
28
+ onDenied?: (c: HonoContext) => Response;
29
+ }
30
+ /**
31
+ * Hono middleware factory.
32
+ *
33
+ * Usage:
34
+ * app.post(
35
+ * "/invoices/:id/approve",
36
+ * honoGuard(engine, "invoice:approve", "invoice", {
37
+ * getSubject: (c) => c.get("user"),
38
+ * getTenantId: (c) => c.req.header("x-tenant-id"),
39
+ * }),
40
+ * handler,
41
+ * );
42
+ */
43
+ declare function honoGuard<S extends SchemaDefinition>(engine: AccessEngine<S>, action: InferAction<S>, resource: InferResource<S>, options: HonoGuardOptions<S>): HonoMiddleware;
44
+
45
+ export { type HonoGuardOptions, honoGuard };
@@ -0,0 +1,45 @@
1
+ import { S as SchemaDefinition, r as Subject, R as ResourceContext, A as AccessEngine, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.js';
2
+
3
+ /**
4
+ * Minimal Hono-compatible types so we don't depend on hono at runtime.
5
+ */
6
+ interface HonoContext {
7
+ req: {
8
+ raw: Request;
9
+ header(name: string): string | undefined;
10
+ param(name: string): string | undefined;
11
+ [key: string]: unknown;
12
+ };
13
+ json(data: unknown, status?: number): Response;
14
+ get<T = unknown>(key: string): T;
15
+ set(key: string, value: unknown): void;
16
+ [key: string]: unknown;
17
+ }
18
+ type HonoNext = () => Promise<void>;
19
+ type HonoMiddleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;
20
+ interface HonoGuardOptions<S extends SchemaDefinition> {
21
+ /** Extract the subject from the Hono context. */
22
+ getSubject: (c: HonoContext) => Subject<S> | undefined;
23
+ /** Extract the resource context from the Hono context (optional). */
24
+ getResourceContext?: (c: HonoContext) => ResourceContext;
25
+ /** Extract the tenant ID from the Hono context (optional). */
26
+ getTenantId?: (c: HonoContext) => string | undefined;
27
+ /** Custom denial handler. Return a Response to override the default 403. */
28
+ onDenied?: (c: HonoContext) => Response;
29
+ }
30
+ /**
31
+ * Hono middleware factory.
32
+ *
33
+ * Usage:
34
+ * app.post(
35
+ * "/invoices/:id/approve",
36
+ * honoGuard(engine, "invoice:approve", "invoice", {
37
+ * getSubject: (c) => c.get("user"),
38
+ * getTenantId: (c) => c.req.header("x-tenant-id"),
39
+ * }),
40
+ * handler,
41
+ * );
42
+ */
43
+ declare function honoGuard<S extends SchemaDefinition>(engine: AccessEngine<S>, action: InferAction<S>, resource: InferResource<S>, options: HonoGuardOptions<S>): HonoMiddleware;
44
+
45
+ export { type HonoGuardOptions, honoGuard };
@@ -0,0 +1,32 @@
1
+ // src/middleware/hono.ts
2
+ function honoGuard(engine, action, resource, options) {
3
+ return async (c, next) => {
4
+ const subject = options.getSubject(c);
5
+ if (!subject) {
6
+ return c.json({ error: "Unauthorized \u2014 no subject" }, 401);
7
+ }
8
+ let decision;
9
+ try {
10
+ const resourceContext = options.getResourceContext?.(c) ?? {};
11
+ const tenantId = options.getTenantId?.(c);
12
+ decision = engine.evaluate(subject, action, resource, resourceContext, tenantId);
13
+ } catch {
14
+ return c.json({ error: "Internal authorization error" }, 500);
15
+ }
16
+ if (decision.allowed) {
17
+ await next();
18
+ return;
19
+ }
20
+ if (options.onDenied) {
21
+ return options.onDenied(c);
22
+ }
23
+ return c.json({
24
+ error: "Forbidden",
25
+ reason: decision.reason
26
+ }, 403);
27
+ };
28
+ }
29
+ export {
30
+ honoGuard
31
+ };
32
+ //# sourceMappingURL=hono.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/middleware/hono.ts"],"sourcesContent":["import type { AccessEngine } from \"../engine.js\";\nimport type { SchemaDefinition, InferAction, InferResource, Subject, ResourceContext } from \"../types.js\";\n\n/**\n * Minimal Hono-compatible types so we don't depend on hono at runtime.\n */\ninterface HonoContext {\n req: {\n raw: Request;\n header(name: string): string | undefined;\n param(name: string): string | undefined;\n [key: string]: unknown;\n };\n json(data: unknown, status?: number): Response;\n get<T = unknown>(key: string): T;\n set(key: string, value: unknown): void;\n [key: string]: unknown;\n}\n\ntype HonoNext = () => Promise<void>;\ntype HonoMiddleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\nexport interface HonoGuardOptions<S extends SchemaDefinition> {\n /** Extract the subject from the Hono context. */\n getSubject: (c: HonoContext) => Subject<S> | undefined;\n /** Extract the resource context from the Hono context (optional). */\n getResourceContext?: (c: HonoContext) => ResourceContext;\n /** Extract the tenant ID from the Hono context (optional). */\n getTenantId?: (c: HonoContext) => string | undefined;\n /** Custom denial handler. Return a Response to override the default 403. */\n onDenied?: (c: HonoContext) => Response;\n}\n\n/**\n * Hono middleware factory.\n *\n * Usage:\n * app.post(\n * \"/invoices/:id/approve\",\n * honoGuard(engine, \"invoice:approve\", \"invoice\", {\n * getSubject: (c) => c.get(\"user\"),\n * getTenantId: (c) => c.req.header(\"x-tenant-id\"),\n * }),\n * handler,\n * );\n */\nexport function honoGuard<S extends SchemaDefinition>(\n engine: AccessEngine<S>,\n action: InferAction<S>,\n resource: InferResource<S>,\n options: HonoGuardOptions<S>,\n): HonoMiddleware {\n return async (c: HonoContext, next: HonoNext) => {\n const subject = options.getSubject(c);\n if (!subject) {\n return c.json({ error: \"Unauthorized — no subject\" }, 401);\n }\n\n let decision;\n try {\n const resourceContext = options.getResourceContext?.(c) ?? {};\n const tenantId = options.getTenantId?.(c);\n decision = engine.evaluate(subject, action, resource, resourceContext, tenantId);\n } catch {\n return c.json({ error: \"Internal authorization error\" }, 500);\n }\n\n if (decision.allowed) {\n await next();\n return;\n }\n\n if (options.onDenied) {\n return options.onDenied(c);\n }\n\n return c.json({\n error: \"Forbidden\",\n reason: decision.reason,\n }, 403);\n };\n}\n"],"mappings":";AA8CO,SAAS,UACd,QACA,QACA,UACA,SACgB;AAChB,SAAO,OAAO,GAAgB,SAAmB;AAC/C,UAAM,UAAU,QAAQ,WAAW,CAAC;AACpC,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,KAAK,EAAE,OAAO,iCAA4B,GAAG,GAAG;AAAA,IAC3D;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,kBAAkB,QAAQ,qBAAqB,CAAC,KAAK,CAAC;AAC5D,YAAM,WAAW,QAAQ,cAAc,CAAC;AACxC,iBAAW,OAAO,SAAS,SAAS,QAAQ,UAAU,iBAAiB,QAAQ;AAAA,IACjF,QAAQ;AACN,aAAO,EAAE,KAAK,EAAE,OAAO,+BAA+B,GAAG,GAAG;AAAA,IAC9D;AAEA,QAAI,SAAS,SAAS;AACpB,YAAM,KAAK;AACX;AAAA,IACF;AAEA,QAAI,QAAQ,UAAU;AACpB,aAAO,QAAQ,SAAS,CAAC;AAAA,IAC3B;AAEA,WAAO,EAAE,KAAK;AAAA,MACZ,OAAO;AAAA,MACP,QAAQ,SAAS;AAAA,IACnB,GAAG,GAAG;AAAA,EACR;AACF;","names":[]}
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition, A as AccessEngine, r as Subject, R as ResourceContext, I as InferAction, k as InferResource } from '../engine-C6IASR5F.cjs';
1
+ import { S as SchemaDefinition, A as AccessEngine, r as Subject, R as ResourceContext, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.cjs';
2
2
 
3
3
  /**
4
4
  * NestJS-compatible guard and decorator factory.
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition, A as AccessEngine, r as Subject, R as ResourceContext, I as InferAction, k as InferResource } from '../engine-C6IASR5F.js';
1
+ import { S as SchemaDefinition, A as AccessEngine, r as Subject, R as ResourceContext, I as InferAction, k as InferResource } from '../engine-Ccws3LFj.js';
2
2
 
3
3
  /**
4
4
  * NestJS-compatible guard and decorator factory.
package/dist/server.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as http from 'http';
2
2
  import { IncomingMessage, ServerResponse } from 'node:http';
3
- import { R as ResourceContext, S as SchemaDefinition, A as AccessEngine, r as Subject } from './engine-C6IASR5F.cjs';
3
+ import { R as ResourceContext, S as SchemaDefinition, A as AccessEngine, r as Subject } from './engine-Ccws3LFj.cjs';
4
4
 
5
5
  interface ServerOptions<S extends SchemaDefinition> {
6
6
  engine: AccessEngine<S>;
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as http from 'http';
2
2
  import { IncomingMessage, ServerResponse } from 'node:http';
3
- import { R as ResourceContext, S as SchemaDefinition, A as AccessEngine, r as Subject } from './engine-C6IASR5F.js';
3
+ import { R as ResourceContext, S as SchemaDefinition, A as AccessEngine, r as Subject } from './engine-Ccws3LFj.js';
4
4
 
5
5
  interface ServerOptions<S extends SchemaDefinition> {
6
6
  engine: AccessEngine<S>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siremzam/sentinel",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "TypeScript-first, domain-driven authorization engine for modern SaaS apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,6 +30,12 @@
30
30
  "require": "./dist/middleware/nestjs.cjs",
31
31
  "default": "./dist/middleware/nestjs.js"
32
32
  },
33
+ "./middleware/hono": {
34
+ "types": "./dist/middleware/hono.d.ts",
35
+ "import": "./dist/middleware/hono.js",
36
+ "require": "./dist/middleware/hono.cjs",
37
+ "default": "./dist/middleware/hono.js"
38
+ },
33
39
  "./server": {
34
40
  "types": "./dist/server.d.ts",
35
41
  "import": "./dist/server.js",