@vielzeug/ward 2.3.0 → 3.0.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.
Files changed (62) hide show
  1. package/README.md +30 -35
  2. package/dist/_compile.cjs +1 -1
  3. package/dist/_compile.cjs.map +1 -1
  4. package/dist/_compile.d.ts +5 -23
  5. package/dist/_compile.d.ts.map +1 -1
  6. package/dist/_compile.js +40 -35
  7. package/dist/_compile.js.map +1 -1
  8. package/dist/_match.cjs +1 -1
  9. package/dist/_match.cjs.map +1 -1
  10. package/dist/_match.d.ts +9 -19
  11. package/dist/_match.d.ts.map +1 -1
  12. package/dist/_match.js +40 -37
  13. package/dist/_match.js.map +1 -1
  14. package/dist/errors.cjs +1 -1
  15. package/dist/errors.cjs.map +1 -1
  16. package/dist/errors.d.ts +3 -16
  17. package/dist/errors.d.ts.map +1 -1
  18. package/dist/errors.js +2 -3
  19. package/dist/errors.js.map +1 -1
  20. package/dist/factory.cjs +1 -1
  21. package/dist/factory.cjs.map +1 -1
  22. package/dist/factory.d.ts +14 -10
  23. package/dist/factory.d.ts.map +1 -1
  24. package/dist/factory.js +71 -119
  25. package/dist/factory.js.map +1 -1
  26. package/dist/index.cjs +1 -1
  27. package/dist/index.d.ts +3 -3
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +6 -6
  30. package/dist/rules.cjs +2 -0
  31. package/dist/rules.cjs.map +1 -0
  32. package/dist/rules.d.ts +15 -0
  33. package/dist/rules.d.ts.map +1 -0
  34. package/dist/rules.js +26 -0
  35. package/dist/rules.js.map +1 -0
  36. package/dist/types.d.ts +63 -132
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/ward.cjs +1 -1
  39. package/dist/ward.cjs.map +1 -1
  40. package/dist/ward.iife.js +1 -1
  41. package/dist/ward.iife.js.map +1 -1
  42. package/dist/ward.js +1 -1
  43. package/dist/ward.js.map +1 -1
  44. package/package.json +7 -7
  45. package/dist/_conflict.cjs +0 -2
  46. package/dist/_conflict.cjs.map +0 -1
  47. package/dist/_conflict.d.ts +0 -10
  48. package/dist/_conflict.d.ts.map +0 -1
  49. package/dist/_conflict.js +0 -51
  50. package/dist/_conflict.js.map +0 -1
  51. package/dist/_dev.cjs +0 -2
  52. package/dist/_dev.cjs.map +0 -1
  53. package/dist/_dev.d.ts +0 -8
  54. package/dist/_dev.d.ts.map +0 -1
  55. package/dist/_dev.js +0 -6
  56. package/dist/_dev.js.map +0 -1
  57. package/dist/builder.cjs +0 -2
  58. package/dist/builder.cjs.map +0 -1
  59. package/dist/builder.d.ts +0 -71
  60. package/dist/builder.d.ts.map +0 -1
  61. package/dist/builder.js +0 -39
  62. package/dist/builder.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"_match.js","names":[],"sources":["../src/_match.ts"],"sourcesContent":["import type { CompiledEntry } from './_compile';\nimport { ANONYMOUS, WILDCARD } from './constants';\nimport { WardConfigError, WardPredicateError } from './errors';\nimport { matchesPattern } from './resource';\nimport type { Principal, UserPrincipal, WardDecision } from './types';\n\n// ---------------------------------------------------------------------------\n// Principal validation\n// ---------------------------------------------------------------------------\n\n/** Asserts that `input` is a valid `UserPrincipal`. Throws with a clear message otherwise. */\nexport function assertUserPrincipal(input: unknown): asserts input is UserPrincipal {\n if (typeof input !== 'object' || !input) {\n throw new WardConfigError('Invalid principal: expected { id: string, roles: string[] }');\n }\n\n const p = input as Record<string, unknown>;\n\n if (typeof p.id !== 'string' || !p.id.trim()) {\n throw new WardConfigError('Invalid principal: id must be a non-empty string');\n }\n\n if (!Array.isArray(p.roles) || p.roles.some((r) => typeof r !== 'string' || !(r as string).trim())) {\n throw new WardConfigError('Invalid principal: roles must be an array of non-empty strings');\n }\n}\n\nexport function validatePrincipal(principal: Principal): void {\n if (principal !== null) {\n assertUserPrincipal(principal);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Role matching\n// ---------------------------------------------------------------------------\n\n/** Returns true if the principal's role set intersects with the rule's roles. */\nexport function principalMatchesRoles(roles: readonly string[], principal: Principal): boolean {\n if (principal === null) {\n return roles.includes(ANONYMOUS);\n }\n\n if (roles.every((r) => r === ANONYMOUS)) return false;\n\n if (roles.includes(WILDCARD)) return true;\n\n return roles.some((role) => principal.roles.includes(role));\n}\n\n// ---------------------------------------------------------------------------\n// Rule matching\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether a rule applies to a given request.\n *\n * @param action - Pass `undefined` to skip the action check (used by `rulesInScope`).\n * @param data - Data payload; passed to `when` predicates.\n * @param skipPredicate - When true, omit the `when` evaluation even when data is present.\n */\nexport function matchesRule<TAction extends string, TData>(\n entry: CompiledEntry<TAction, TData>,\n principal: Principal,\n resource: string,\n action: TAction | undefined,\n data: TData | undefined,\n skipPredicate = false,\n): boolean {\n if (!principalMatchesRoles(entry.roles, principal)) return false;\n\n if (!matchesPattern(entry.rule.resource as string, resource)) return false;\n\n if (action !== undefined && !matchesPattern(entry.rule.action as string, action)) return false;\n\n if (skipPredicate || !entry.rule.when) return true;\n\n if (principal === null) return false;\n\n try {\n const result: unknown = entry.rule.when({ data, principal });\n\n if (result instanceof Promise) {\n throw new TypeError(\n `Rule[${entry.index}] when() returned a Promise. Async predicates are not supported — use a synchronous predicate.`,\n );\n }\n\n return result as boolean;\n } catch (err) {\n throw new WardPredicateError(entry.index, err);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Winner selection\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if `challenger` would displace `current` as the pickWinner result.\n */\nexport function isOverriddenBy<TAction extends string, TData>(\n current: CompiledEntry<TAction, TData>,\n challenger: CompiledEntry<TAction, TData>,\n): boolean {\n return (\n challenger.priority > current.priority ||\n (challenger.priority === current.priority && challenger.score > current.score) ||\n (challenger.priority === current.priority &&\n challenger.score === current.score &&\n challenger.denyBonus > current.denyBonus)\n );\n}\n\nexport function pickWinner<TAction extends string, TData>(\n entries: CompiledEntry<TAction, TData>[],\n principal: Principal,\n resource: string,\n action: TAction,\n data: TData | undefined,\n): CompiledEntry<TAction, TData> | undefined {\n let winner: CompiledEntry<TAction, TData> | undefined;\n\n for (const entry of entries) {\n if (!matchesRule(entry, principal, resource, action, data)) continue;\n\n if (!winner || isOverriddenBy(winner, entry)) {\n winner = entry;\n }\n }\n\n return winner;\n}\n\nexport function toDecision<TAction extends string, TData>(\n winner: CompiledEntry<TAction, TData> | undefined,\n): WardDecision<TAction, TData> {\n if (!winner) return { allowed: false, reason: 'no-matching-rule' };\n\n if (winner.rule.effect === 'deny') {\n return {\n allowed: false,\n reason: 'explicit-deny',\n rule: winner.rule,\n };\n }\n\n return { allowed: true, rule: winner.rule };\n}\n"],"mappings":";;;;AAWA,SAAgB,EAAoB,GAAgD;CAClF,IAAI,OAAO,KAAU,YAAY,CAAC,GAChC,MAAM,IAAI,EAAgB,6DAA6D;CAGzF,IAAM,IAAI;CAEV,IAAI,OAAO,EAAE,MAAO,YAAY,CAAC,EAAE,GAAG,KAAK,GACzC,MAAM,IAAI,EAAgB,kDAAkD;CAG9E,IAAI,CAAC,MAAM,QAAQ,EAAE,KAAK,KAAK,EAAE,MAAM,MAAM,MAAM,OAAO,KAAM,YAAY,CAAE,EAAa,KAAK,CAAC,GAC/F,MAAM,IAAI,EAAgB,gEAAgE;AAE9F;AAEA,SAAgB,EAAkB,GAA4B;CAC5D,AAAI,MAAc,QAChB,EAAoB,CAAS;AAEjC;AAOA,SAAgB,EAAsB,GAA0B,GAA+B;CAS7F,OARI,MAAc,OACT,EAAM,SAAS,CAAS,IAG7B,EAAM,OAAO,MAAM,MAAA,WAAe,IAAU,KAE5C,EAAM,SAAA,GAAiB,IAAU,KAE9B,EAAM,MAAM,MAAS,EAAU,MAAM,SAAS,CAAI,CAAC;AAC5D;AAaA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA,IAAgB,IACP;CAKT,IAJI,CAAC,EAAsB,EAAM,OAAO,CAAS,KAE7C,CAAC,EAAe,EAAM,KAAK,UAAoB,CAAQ,KAEvD,MAAW,KAAA,KAAa,CAAC,EAAe,EAAM,KAAK,QAAkB,CAAM,GAAG,OAAO;CAEzF,IAAI,KAAiB,CAAC,EAAM,KAAK,MAAM,OAAO;CAE9C,IAAI,MAAc,MAAM,OAAO;CAE/B,IAAI;EACF,IAAM,IAAkB,EAAM,KAAK,KAAK;GAAE;GAAM;EAAU,CAAC;EAE3D,IAAI,aAAkB,SACpB,MAAU,UACR,QAAQ,EAAM,MAAM,+FACtB;EAGF,OAAO;CACT,SAAS,GAAK;EACZ,MAAM,IAAI,EAAmB,EAAM,OAAO,CAAG;CAC/C;AACF;AASA,SAAgB,EACd,GACA,GACS;CACT,OACE,EAAW,WAAW,EAAQ,YAC7B,EAAW,aAAa,EAAQ,YAAY,EAAW,QAAQ,EAAQ,SACvE,EAAW,aAAa,EAAQ,YAC/B,EAAW,UAAU,EAAQ,SAC7B,EAAW,YAAY,EAAQ;AAErC;AAEA,SAAgB,EACd,GACA,GACA,GACA,GACA,GAC2C;CAC3C,IAAI;CAEJ,KAAK,IAAM,KAAS,GACb,EAAY,GAAO,GAAW,GAAU,GAAQ,CAAI,MAErD,CAAC,KAAU,EAAe,GAAQ,CAAK,OACzC,IAAS;CAIb,OAAO;AACT;AAEA,SAAgB,EACd,GAC8B;CAW9B,OAVK,IAED,EAAO,KAAK,WAAW,SAClB;EACL,SAAS;EACT,QAAQ;EACR,MAAM,EAAO;CACf,IAGK;EAAE,SAAS;EAAM,MAAM,EAAO;CAAK,IAVtB;EAAE,SAAS;EAAO,QAAQ;CAAmB;AAWnE"}
1
+ {"version":3,"file":"_match.js","names":[],"sources":["../src/_match.ts"],"sourcesContent":["import { snapshotAttributes } from './_compile';\nimport { WardConditionError, WardConfigError } from './errors';\nimport { matchesPattern } from './resource';\nimport type {\n Principal,\n UserPrincipal,\n WardAttributes,\n WardAttributeValue,\n WardDecisionInput,\n WardRule,\n} from './types';\n\n/** Asserts that `input` is a valid `UserPrincipal`. Throws with a clear message otherwise. */\nexport function assertUserPrincipal(input: unknown): asserts input is UserPrincipal {\n if (typeof input !== 'object' || !input) {\n throw new WardConfigError('Invalid principal: expected { id: string, roles: string[] }');\n }\n\n const principal = input as Record<string, unknown>;\n\n if (typeof principal.id !== 'string' || !principal.id.trim()) {\n throw new WardConfigError('Invalid principal: id must be a non-empty string');\n }\n\n if (!Array.isArray(principal.roles) || principal.roles.some((role) => typeof role !== 'string' || !role.trim())) {\n throw new WardConfigError('Invalid principal: roles must be an array of non-empty strings');\n }\n}\n\nexport function snapshotPrincipal(principal: Principal | undefined): Principal {\n if (principal === undefined || principal === null) return null;\n\n assertUserPrincipal(principal);\n\n return Object.freeze({\n ...(principal.attributes === undefined\n ? {}\n : { attributes: snapshotAttributes(principal.attributes, 'Principal.attributes') }),\n id: principal.id,\n roles: Object.freeze([...principal.roles]),\n });\n}\n\nexport function snapshotDecisionInput<\n TAction extends string,\n TResource extends string,\n TAttributes extends WardAttributes,\n>(\n input: WardDecisionInput<TAction, TResource, TAttributes>,\n): WardDecisionInput<TAction, TResource, TAttributes> & {\n principal: Principal;\n} {\n if (typeof input.action !== 'string' || !input.action.trim()) {\n throw new WardConfigError('Decision.action must be a non-empty string');\n }\n if (typeof input.resource !== 'string' || !input.resource.trim()) {\n throw new WardConfigError('Decision.resource must be a non-empty string');\n }\n\n return Object.freeze({\n action: input.action,\n ...(input.attributes === undefined\n ? {}\n : { attributes: snapshotAttributes(input.attributes, 'Decision.attributes') as TAttributes }),\n principal: snapshotPrincipal(input.principal),\n resource: input.resource,\n });\n}\n\nfunction deepEqual(left: WardAttributeValue, right: WardAttributeValue): boolean {\n if (Object.is(left, right)) return true;\n if (typeof left !== 'object' || typeof right !== 'object' || left === null || right === null) return false;\n if (Array.isArray(left) || Array.isArray(right)) {\n return (\n Array.isArray(left) &&\n Array.isArray(right) &&\n left.length === right.length &&\n left.every((value, index) => deepEqual(value, right[index]))\n );\n }\n\n const leftRecord = left as Record<string, WardAttributeValue>;\n const rightRecord = right as Record<string, WardAttributeValue>;\n const leftKeys = Object.keys(leftRecord);\n const rightKeys = Object.keys(rightRecord);\n\n return (\n leftKeys.length === rightKeys.length &&\n leftKeys.every((key) => Object.hasOwn(rightRecord, key) && deepEqual(leftRecord[key], rightRecord[key]))\n );\n}\n\nexport function attributesMatch(expected: WardAttributes | undefined, actual: WardAttributes | undefined): boolean {\n if (!expected) return true;\n if (!actual) return false;\n\n return Object.keys(expected).every((key) => Object.hasOwn(actual, key) && deepEqual(expected[key], actual[key]));\n}\n\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n (typeof value === 'object' && value !== null && 'then' in value && typeof value.then === 'function') ||\n (typeof value === 'function' && 'then' in value && typeof value.then === 'function')\n );\n}\n\nexport function ruleMatches<TAction extends string, TResource extends string, TAttributes extends WardAttributes>(\n rule: WardRule<TAction, TResource, TAttributes>,\n index: number,\n input: WardDecisionInput<TAction, TResource, TAttributes> & { principal: Principal },\n): boolean {\n if (!matchesPattern(rule.action, input.action)) return false;\n if (!matchesPattern(rule.resource, input.resource)) return false;\n if (!attributesMatch(rule.attributes, input.attributes)) return false;\n if (!rule.condition) return true;\n\n try {\n const result: unknown = rule.condition({ attributes: input.attributes, principal: input.principal });\n\n if (isThenable(result)) {\n throw new TypeError('Async conditions are not supported; return a boolean');\n }\n if (typeof result !== 'boolean') {\n throw new TypeError('Conditions must return a boolean');\n }\n\n return result;\n } catch (error) {\n throw new WardConditionError(index, error);\n }\n}\n"],"mappings":"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"}
package/dist/errors.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{},n=class extends e{ruleIndex;constructor(e,t){let n=t instanceof Error?t.message:String(t);super(`Rule[${e}] threw: ${n}`,{cause:t}),this.ruleIndex=e}};exports.WardConfigError=t,exports.WardError=e,exports.WardPredicateError=n;
1
+ var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{},n=class extends e{ruleIndex;constructor(e,t){super(`Rule[${e}].condition failed`,{cause:t}),this.ruleIndex=e}};exports.WardConditionError=n,exports.WardConfigError=t,exports.WardError=e;
2
2
  //# sourceMappingURL=errors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all ward errors. Use `instanceof WardError` to catch any ward-originated error. */\nexport class WardError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a rule definition or principal is malformed. */\nexport class WardConfigError extends WardError {}\n\n/**\n * Thrown when a `when` predicate in a ward rule throws an exception.\n *\n * Catch this to distinguish predicate failures from other errors:\n * ```ts\n * try {\n * ward.explain(principal, resource, action, data);\n * } catch (e) {\n * if (e instanceof WardPredicateError) {\n * console.error(`Predicate in Rule[${e.ruleIndex}] threw`, e.cause);\n * }\n * }\n * ```\n */\nexport class WardPredicateError extends WardError {\n readonly ruleIndex: number;\n\n constructor(ruleIndex: number, cause: unknown) {\n const msg = cause instanceof Error ? cause.message : String(cause);\n\n super(`Rule[${ruleIndex}] threw: ${msg}`, { cause });\n this.ruleIndex = ruleIndex;\n }\n}\n"],"mappings":"AACA,IAAa,EAAb,cAA+B,KAAM,CACnC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,EAAb,cAAqC,CAAU,CAAC,EAgBnC,EAAb,cAAwC,CAAU,CAChD,UAEA,YAAY,EAAmB,EAAgB,CAC7C,IAAM,EAAM,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAEjE,MAAM,QAAQ,EAAU,WAAW,IAAO,CAAE,OAAM,CAAC,EACnD,KAAK,UAAY,CACnB,CACF"}
1
+ {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all ward errors. Use `instanceof WardError` to catch any ward-originated error. */\nexport class WardError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a rule definition, decision attributes, or principal is malformed. */\nexport class WardConfigError extends WardError {}\n\n/** Thrown when an application-provided rule condition fails. */\nexport class WardConditionError extends WardError {\n readonly ruleIndex: number;\n\n constructor(ruleIndex: number, cause: unknown) {\n super(`Rule[${ruleIndex}].condition failed`, { cause });\n this.ruleIndex = ruleIndex;\n }\n}\n"],"mappings":"AACA,IAAa,EAAb,cAA+B,KAAM,CACnC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,EAAb,cAAqC,CAAU,CAAC,EAGnC,EAAb,cAAwC,CAAU,CAChD,UAEA,YAAY,EAAmB,EAAgB,CAC7C,MAAM,QAAQ,EAAU,oBAAqB,CAAE,OAAM,CAAC,EACtD,KAAK,UAAY,CACnB,CACF"}
package/dist/errors.d.ts CHANGED
@@ -2,24 +2,11 @@
2
2
  export declare class WardError extends Error {
3
3
  constructor(message: string, opts?: ErrorOptions);
4
4
  }
5
- /** Thrown when a rule definition or principal is malformed. */
5
+ /** Thrown when a rule definition, decision attributes, or principal is malformed. */
6
6
  export declare class WardConfigError extends WardError {
7
7
  }
8
- /**
9
- * Thrown when a `when` predicate in a ward rule throws an exception.
10
- *
11
- * Catch this to distinguish predicate failures from other errors:
12
- * ```ts
13
- * try {
14
- * ward.explain(principal, resource, action, data);
15
- * } catch (e) {
16
- * if (e instanceof WardPredicateError) {
17
- * console.error(`Predicate in Rule[${e.ruleIndex}] threw`, e.cause);
18
- * }
19
- * }
20
- * ```
21
- */
22
- export declare class WardPredicateError extends WardError {
8
+ /** Thrown when an application-provided rule condition fails. */
9
+ export declare class WardConditionError extends WardError {
23
10
  readonly ruleIndex: number;
24
11
  constructor(ruleIndex: number, cause: unknown);
25
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAKjD;AAED,+DAA+D;AAC/D,qBAAa,eAAgB,SAAQ,SAAS;CAAG;AAEjD;;;;;;;;;;;;;GAaG;AACH,qBAAa,kBAAmB,SAAQ,SAAS;IAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAM9C"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAKjD;AAED,qFAAqF;AACrF,qBAAa,eAAgB,SAAQ,SAAS;CAAG;AAEjD,gEAAgE;AAChE,qBAAa,kBAAmB,SAAQ,SAAS;IAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAI9C"}
package/dist/errors.js CHANGED
@@ -6,11 +6,10 @@ var e = class extends Error {
6
6
  }, t = class extends e {}, n = class extends e {
7
7
  ruleIndex;
8
8
  constructor(e, t) {
9
- let n = t instanceof Error ? t.message : String(t);
10
- super(`Rule[${e}] threw: ${n}`, { cause: t }), this.ruleIndex = e;
9
+ super(`Rule[${e}].condition failed`, { cause: t }), this.ruleIndex = e;
11
10
  }
12
11
  };
13
12
  //#endregion
14
- export { t as WardConfigError, e as WardError, n as WardPredicateError };
13
+ export { n as WardConditionError, t as WardConfigError, e as WardError };
15
14
 
16
15
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all ward errors. Use `instanceof WardError` to catch any ward-originated error. */\nexport class WardError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a rule definition or principal is malformed. */\nexport class WardConfigError extends WardError {}\n\n/**\n * Thrown when a `when` predicate in a ward rule throws an exception.\n *\n * Catch this to distinguish predicate failures from other errors:\n * ```ts\n * try {\n * ward.explain(principal, resource, action, data);\n * } catch (e) {\n * if (e instanceof WardPredicateError) {\n * console.error(`Predicate in Rule[${e.ruleIndex}] threw`, e.cause);\n * }\n * }\n * ```\n */\nexport class WardPredicateError extends WardError {\n readonly ruleIndex: number;\n\n constructor(ruleIndex: number, cause: unknown) {\n const msg = cause instanceof Error ? cause.message : String(cause);\n\n super(`Rule[${ruleIndex}] threw: ${msg}`, { cause });\n this.ruleIndex = ruleIndex;\n }\n}\n"],"mappings":";AACA,IAAa,IAAb,cAA+B,MAAM;CACnC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAGa,IAAb,cAAqC,EAAU,CAAC,GAgBnC,IAAb,cAAwC,EAAU;CAChD;CAEA,YAAY,GAAmB,GAAgB;EAC7C,IAAM,IAAM,aAAiB,QAAQ,EAAM,UAAU,OAAO,CAAK;EAGjE,AADA,MAAM,QAAQ,EAAU,WAAW,KAAO,EAAE,SAAM,CAAC,GACnD,KAAK,YAAY;CACnB;AACF"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all ward errors. Use `instanceof WardError` to catch any ward-originated error. */\nexport class WardError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a rule definition, decision attributes, or principal is malformed. */\nexport class WardConfigError extends WardError {}\n\n/** Thrown when an application-provided rule condition fails. */\nexport class WardConditionError extends WardError {\n readonly ruleIndex: number;\n\n constructor(ruleIndex: number, cause: unknown) {\n super(`Rule[${ruleIndex}].condition failed`, { cause });\n this.ruleIndex = ruleIndex;\n }\n}\n"],"mappings":";AACA,IAAa,IAAb,cAA+B,MAAM;CACnC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAGa,IAAb,cAAqC,EAAU,CAAC,GAGnC,IAAb,cAAwC,EAAU;CAChD;CAEA,YAAY,GAAmB,GAAgB;EAE7C,AADA,MAAM,QAAQ,EAAU,qBAAqB,EAAE,SAAM,CAAC,GACtD,KAAK,YAAY;CACnB;AACF"}
package/dist/factory.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./_compile.cjs"),n=require("./_match.cjs"),r=require("./_conflict.cjs");var i=require("./_dev.cjs").warnAnonymousPredicates;function a(e,t,r,i,a){let o=new Set,s=[];for(let c of i)o.has(c)||(o.add(c),n.pickWinner(e,t,r,c,a)?.rule.effect===`allow`&&s.push(c));return s}function o(e,t,r,i){let a=i===void 0,o=[];for(let s of e)n.matchesRule(s,t,r,void 0,i,a)&&o.push(s.rule);return o}function s(s=[],c={}){if(c.maxConflicts!==void 0&&(!Number.isFinite(c.maxConflicts)||c.maxConflicts<0))throw new e.WardConfigError(`maxConflicts must be a finite non-negative number.`);if(c.onConflict!==void 0&&typeof c.onConflict!=`function`)throw new e.WardConfigError(`onConflict must be a function.`);let{maxConflicts:l=1/0}=c,u=new Set,d=[];for(let e of s)if(Array.isArray(e))for(let t of e)d.push(t);else d.push(e);let f=d.map((e,n)=>t.compileEntry(e,n));i(f);function p(e){if(u.size!==0)for(let t of u)try{t(e)}catch{}}function m(e,t,r,i){let a=n.pickWinner(f,e,t,r,i),o=n.toDecision(a);return p({action:r,data:i,decision:o,principal:e,resource:t}),o}function h(e){let{action:t,data:r,principal:i,resource:a}=e;return n.validatePrincipal(i),m(i,a,t,r)}function g(e,t){return t.map(t=>({...m(e,t.resource,t.action,t.data),action:t.action,resource:t.resource}))}function _(e,t){return t.length===0?[]:(n.validatePrincipal(e),g(e,t))}function v(e){let{data:t,knownActions:r,principal:i,resource:o}=e;return n.validatePrincipal(i),a(f,i,o,r,t)}function y(e){let{data:t,principal:r,resource:i}=e;return n.validatePrincipal(r),o(f,r,i,t)}function b(e){let{action:t,data:r,principal:i,resource:a}=e;n.validatePrincipal(i);let o=[];for(let e of f)n.matchesRule(e,i,a,t,r)&&o.push(e);let s;for(let e of o)(!s||n.isOverriddenBy(s,e))&&(s=e);let c=n.toDecision(s);return{candidates:o.map(e=>({index:e.index,priority:e.priority,rule:e.rule,score:e.score,won:e===s})),decision:c}}function x(e){n.assertUserPrincipal(e);let t={attributes:e.attributes?structuredClone(e.attributes):void 0,id:e.id,roles:[...e.roles]};return{allowedActions:e=>a(f,t,e.resource,e.knownActions,e.data),checkAll:e=>e.length===0?[]:g(t,e),explain:e=>m(t,e.resource,e.action,e.data),rulesInScope:e=>o(f,t,e.resource,e.data),trace:e=>b({action:e.action,data:e.data,principal:t,resource:e.resource})}}let S;function C(){return S??=Object.freeze(r.computeConflicts(f,l))}if(c.strict||c.onConflict){let t=C();if(t.length>0&&(c.onConflict&&t.forEach(c.onConflict),c.strict)){let n=t.map(e=>e.kind===`duplicate`?`Rule[${e.indexB}] ${e.kind} of Rule[${e.indexA}]`:`Rule[${e.shadowedIndex}] ${e.kind} by Rule[${e.shadowingIndex}]`).join(`; `);throw new e.WardConfigError(`${t.length} rule conflict(s) detected: ${n}`)}}function w(e,t){u.add(e);let n=()=>u.delete(e);if(t?.signal){if(t.signal.aborted)return u.delete(e),()=>{};t.signal.addEventListener(`abort`,n,{once:!0})}return()=>{u.delete(e),t?.signal?.removeEventListener(`abort`,n)}}return{allowedActions:v,checkAll:_,detectConflicts:C,explain:h,forUser:x,rulesInScope:y,tap:w,trace:b}}exports.createWard=s;
1
+ const e=require("./errors.cjs"),t=require("./_compile.cjs"),n=require("./_match.cjs");function r(r=[]){let i=Object.freeze(r.flat().map((e,n)=>t.compileRule(e,n))),a=new Set;function o(e,t){let r=n.snapshotDecisionInput(e);for(let e=0;e<i.length;e++){let a=i[e];if(n.ruleMatches(a,e,r)){let n=Object.freeze({effect:a.effect,matched:!0,reason:`rule[${e}] ${a.effect===`allow`?`allows`:`denies`} '${r.action}' on '${r.resource}'`,rule:a});return t&&s(r,n),n}}let a=Object.freeze({effect:`deny`,matched:!1,reason:`no matching rule for '${r.action}' on '${r.resource}' (default deny)`});return t&&s(r,a),a}function s(e,t){if(a.size===0)return;let n=Object.freeze({decision:t,input:e,type:`decision`});for(let e of a)try{e(n)}catch{}}function c(e){return o(e,!0)}function l(e){return e.map(c)}function u(e){let t=new Set;return e.knownActions.filter(n=>!t.has(n)&&(t.add(n),o({...e,action:n},!1).effect===`allow`))}function d(e){let t=n.snapshotPrincipal(e);return{allowedActions:e=>u({...e,principal:t}),checkAll:e=>l(e.map(e=>({...e,principal:t}))),decide:e=>c({...e,principal:t})}}function f(t,n){if(typeof t!=`function`)throw new e.WardConfigError(`tap handler must be a function`);if(n?.signal?.aborted)return()=>{};a.add(t);let r=()=>a.delete(t);return n?.signal?.addEventListener(`abort`,r,{once:!0}),()=>{a.delete(t),n?.signal?.removeEventListener(`abort`,r)}}return{allowedActions:u,checkAll:l,decide:c,forPrincipal:d,rules:i,tap:f}}exports.createWard=r;
2
2
  //# sourceMappingURL=factory.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory.cjs","names":[],"sources":["../src/factory.ts"],"sourcesContent":["import type { CompiledEntry } from './_compile';\nimport { compileEntry } from './_compile';\nimport { computeConflicts } from './_conflict';\nimport { warnAnonymousPredicates } from './_dev';\nimport { assertUserPrincipal, isOverriddenBy, matchesRule, pickWinner, toDecision, validatePrincipal } from './_match';\nimport { WardConfigError } from './errors';\nimport type {\n BoundWard,\n BoundWardAllowedActionsInput,\n BoundWardDecisionInput,\n BoundWardRulesInScopeInput,\n NormalizedWardRule,\n Principal,\n UserPrincipal,\n Ward,\n WardAllowedActionsInput,\n WardCheck,\n WardConflict,\n WardDecision,\n WardDecisionInput,\n WardDecisionResult,\n WardEvent,\n WardOptions,\n WardRule,\n WardRulesInScopeInput,\n WardTrace,\n WardTraceCandidate,\n} from './types';\n\nconst warn = warnAnonymousPredicates;\n\n// ---------------------------------------------------------------------------\n// Shared loop cores (validation-free; used by both public API and forUser)\n// ---------------------------------------------------------------------------\n\nfunction coreAllowedActions<TAction extends string, TData>(\n entries: CompiledEntry<TAction, TData>[],\n principal: Principal,\n resource: string,\n knownActions: readonly TAction[],\n data: TData | undefined,\n): TAction[] {\n const seen = new Set<TAction>();\n const result: TAction[] = [];\n\n for (const action of knownActions) {\n if (seen.has(action)) continue;\n\n seen.add(action);\n\n const winner = pickWinner(entries, principal, resource, action, data);\n\n if (winner?.rule.effect === 'allow') result.push(action);\n }\n\n return result;\n}\n\nfunction coreRulesInScope<TAction extends string, TData>(\n entries: CompiledEntry<TAction, TData>[],\n principal: Principal,\n resource: string,\n data: TData | undefined,\n): NormalizedWardRule<TAction, TData>[] {\n const skipPredicate = data === undefined;\n const result: NormalizedWardRule<TAction, TData>[] = [];\n\n for (const entry of entries) {\n if (!matchesRule(entry, principal, resource, undefined, data, skipPredicate)) continue;\n\n result.push(entry.rule as NormalizedWardRule<TAction, TData>);\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates an authorization ward from a set of rules.\n *\n * **Winner selection** when multiple rules match a request:\n * 1. Higher `priority` wins.\n * 2. On priority tie, higher specificity score wins (exact > namespace-wildcard > global-wildcard,\n * applied independently to role, resource, and action).\n * 3. On specificity tie, `deny` beats `allow` (denyBonus tiebreaker).\n * 4. On absolute tie (identical priority, specificity, and effect), the rule declared\n * **first in the input array** wins.\n */\nexport function createWard<TAction extends string = string, TData = unknown>(\n rules: readonly (WardRule<TAction, TData> | readonly WardRule<TAction, TData>[])[] = [],\n options: WardOptions<TAction, TData> = {},\n): Ward<TAction, TData> {\n if (options.maxConflicts !== undefined) {\n if (!Number.isFinite(options.maxConflicts) || options.maxConflicts < 0) {\n throw new WardConfigError('maxConflicts must be a finite non-negative number.');\n }\n }\n\n if (options.onConflict !== undefined && typeof options.onConflict !== 'function') {\n throw new WardConfigError('onConflict must be a function.');\n }\n\n const { maxConflicts = Infinity } = options;\n const tappers = new Set<(event: WardEvent<TAction, TData>) => void>();\n const flat: WardRule<TAction, TData>[] = [];\n\n for (const entry of rules) {\n if (Array.isArray(entry)) {\n for (const rule of entry) flat.push(rule);\n } else {\n flat.push(entry as WardRule<TAction, TData>);\n }\n }\n\n const entries = flat.map((rule, i) => compileEntry(rule, i));\n\n // Warn in development when an ANONYMOUS-role rule has a predicate.\n warn(entries);\n\n // -------------------------------------------------------------------------\n // Core decision + logging\n // -------------------------------------------------------------------------\n\n function emitTap(event: WardEvent<TAction, TData>): void {\n if (tappers.size === 0) return;\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {\n // Observability must not affect ward behavior.\n }\n }\n }\n\n function evaluateAndLog(\n principal: Principal,\n resource: string,\n action: TAction,\n data: TData | undefined,\n ): WardDecision<TAction, TData> {\n const winner = pickWinner(entries, principal, resource, action, data);\n const decision = toDecision(winner);\n\n emitTap({ action, data, decision, principal, resource });\n\n return decision;\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n // Request objects avoid call-site ambiguity between resource/action/data and\n // make later API growth additive instead of positional-breaking.\n\n function explain(input: WardDecisionInput<TAction, TData>): WardDecision<TAction, TData> {\n const { action, data, principal, resource } = input;\n\n validatePrincipal(principal);\n\n return evaluateAndLog(principal, resource, action, data);\n }\n\n function runCheckAll(\n principal: Principal,\n checks: readonly WardCheck<TAction, TData>[],\n ): WardDecisionResult<TAction, TData>[] {\n return checks.map((check) => ({\n ...evaluateAndLog(principal, check.resource, check.action, check.data),\n action: check.action,\n resource: check.resource,\n }));\n }\n\n function checkAll(\n principal: Principal,\n checks: readonly WardCheck<TAction, TData>[],\n ): WardDecisionResult<TAction, TData>[] {\n if (checks.length === 0) return [];\n\n validatePrincipal(principal);\n\n return runCheckAll(principal, checks);\n }\n\n function allowedActions(input: WardAllowedActionsInput<TAction, TData>): TAction[] {\n const { data, knownActions, principal, resource } = input;\n\n validatePrincipal(principal);\n\n return coreAllowedActions(entries, principal, resource, knownActions, data);\n }\n\n function rulesInScope(\n input: WardRulesInScopeInput<TData>,\n ): ReadonlyArray<Readonly<NormalizedWardRule<TAction, TData>>> {\n const { data, principal, resource } = input;\n\n validatePrincipal(principal);\n\n return coreRulesInScope(entries, principal, resource, data);\n }\n\n function trace(input: WardDecisionInput<TAction, TData>): WardTrace<TAction, TData> {\n const { action, data, principal, resource } = input;\n\n validatePrincipal(principal);\n\n const matching: CompiledEntry<TAction, TData>[] = [];\n\n for (const entry of entries) {\n if (matchesRule(entry, principal, resource, action, data)) {\n matching.push(entry);\n }\n }\n\n let winner: CompiledEntry<TAction, TData> | undefined;\n\n for (const entry of matching) {\n if (!winner || isOverriddenBy(winner, entry)) winner = entry;\n }\n\n const decision = toDecision(winner);\n\n const candidates: WardTraceCandidate<TAction, TData>[] = matching.map((entry) => ({\n index: entry.index,\n priority: entry.priority,\n rule: entry.rule,\n score: entry.score,\n won: entry === winner,\n }));\n\n return { candidates, decision };\n }\n\n function forUser(principal: UserPrincipal): BoundWard<TAction, TData> {\n assertUserPrincipal(principal);\n\n const snap: UserPrincipal = {\n attributes: principal.attributes ? structuredClone(principal.attributes) : undefined,\n id: principal.id,\n roles: [...principal.roles],\n };\n\n return {\n allowedActions: (input: BoundWardAllowedActionsInput<TAction, TData>) =>\n coreAllowedActions(entries, snap, input.resource, input.knownActions, input.data),\n checkAll: (checks) => (checks.length === 0 ? [] : runCheckAll(snap, checks)),\n explain: (input: BoundWardDecisionInput<TAction, TData>) =>\n evaluateAndLog(snap, input.resource, input.action, input.data),\n rulesInScope: (input: BoundWardRulesInScopeInput<TData>) =>\n coreRulesInScope(entries, snap, input.resource, input.data),\n trace: (input: BoundWardDecisionInput<TAction, TData>) =>\n trace({ action: input.action, data: input.data, principal: snap, resource: input.resource }),\n };\n }\n\n // -------------------------------------------------------------------------\n // Conflict detection (lazy, cached)\n // -------------------------------------------------------------------------\n\n let conflictsCache: readonly WardConflict<TAction, TData>[] | undefined;\n\n function detectConflicts(): readonly WardConflict<TAction, TData>[] {\n return (conflictsCache ??= Object.freeze(computeConflicts(entries, maxConflicts)));\n }\n\n if (options.strict || options.onConflict) {\n const conflicts = detectConflicts();\n\n if (conflicts.length > 0) {\n if (options.onConflict) conflicts.forEach(options.onConflict);\n\n if (options.strict) {\n const details = conflicts\n .map((c) =>\n c.kind === 'duplicate'\n ? `Rule[${c.indexB}] ${c.kind} of Rule[${c.indexA}]`\n : `Rule[${c.shadowedIndex}] ${c.kind} by Rule[${c.shadowingIndex}]`,\n )\n .join('; ');\n\n throw new WardConfigError(`${conflicts.length} rule conflict(s) detected: ${details}`);\n }\n }\n }\n\n function tap(handler: (event: WardEvent<TAction, TData>) => void, opts?: { signal?: AbortSignal }): () => void {\n tappers.add(handler);\n\n const onAbort = () => tappers.delete(handler);\n\n if (opts?.signal) {\n if (opts.signal.aborted) {\n tappers.delete(handler);\n return () => {};\n }\n opts.signal.addEventListener('abort', onAbort, { once: true });\n }\n\n return () => {\n tappers.delete(handler);\n opts?.signal?.removeEventListener('abort', onAbort);\n };\n }\n\n return { allowedActions, checkAll, detectConflicts, explain, forUser, rulesInScope, tap, trace };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"factory.cjs","names":[],"sources":["../src/factory.ts"],"sourcesContent":["import { compileRule } from './_compile';\nimport { ruleMatches, snapshotDecisionInput, snapshotPrincipal } from './_match';\nimport { WardConfigError } from './errors';\nimport type {\n BoundWard,\n Principal,\n Ward,\n WardAttributes,\n WardDecision,\n WardDecisionInput,\n WardEvent,\n WardRule,\n} from './types';\n\n/**\n * Creates an authorization ward from an ordered list of rules.\n *\n * **Decision model**: rules are evaluated in declaration order; the first rule\n * that matches the request wins. If no rule matches, the decision is **deny**\n * (default deny). There is no priority, specificity scoring, or conflict\n * detection — order is the only tiebreaker.\n *\n * A rule matches when:\n * 1. Its `action` pattern matches the requested action (`*` or `ns:*` or exact).\n * 2. Its `resource` pattern matches the requested resource.\n * 3. Every key in its declarative `attributes` is present and equal in the\n * request's `attributes` (deep equality).\n * 4. Its `condition` escape-hatch callback (if any) returns `true`.\n */\nexport function createWard<\n TAction extends string = string,\n TResource extends string = string,\n TAttributes extends WardAttributes = WardAttributes,\n>(\n rules: readonly (\n | WardRule<NoInfer<TAction>, NoInfer<TResource>, NoInfer<TAttributes>>\n | readonly WardRule<NoInfer<TAction>, NoInfer<TResource>, NoInfer<TAttributes>>[]\n )[] = [],\n): Ward<TAction, TResource, TAttributes> {\n const compiled = Object.freeze(rules.flat().map((rule, index) => compileRule(rule, index)));\n const tappers = new Set<(event: WardEvent<TAction, TResource, TAttributes>) => void>();\n\n function evaluate(\n input: WardDecisionInput<TAction, TResource, TAttributes>,\n emit: boolean,\n ): WardDecision<TAction, TResource, TAttributes> {\n const normalized = snapshotDecisionInput(input);\n\n for (let index = 0; index < compiled.length; index++) {\n const rule = compiled[index];\n\n if (ruleMatches(rule, index, normalized)) {\n const decision = Object.freeze({\n effect: rule.effect,\n matched: true,\n reason: `rule[${index}] ${rule.effect === 'allow' ? 'allows' : 'denies'} '${normalized.action}' on '${normalized.resource}'`,\n rule,\n }) as WardDecision<TAction, TResource, TAttributes>;\n\n if (emit) emitDecision(normalized, decision);\n return decision;\n }\n }\n\n const decision = Object.freeze({\n effect: 'deny',\n matched: false,\n reason: `no matching rule for '${normalized.action}' on '${normalized.resource}' (default deny)`,\n }) as WardDecision<TAction, TResource, TAttributes>;\n\n if (emit) emitDecision(normalized, decision);\n return decision;\n }\n\n function emitDecision(\n input: WardDecisionInput<TAction, TResource, TAttributes> & { principal: Principal },\n decision: WardDecision<TAction, TResource, TAttributes>,\n ): void {\n if (tappers.size === 0) return;\n\n const event = Object.freeze({ decision, input, type: 'decision' as const });\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {}\n }\n }\n\n function decide(\n input: WardDecisionInput<TAction, TResource, TAttributes>,\n ): WardDecision<TAction, TResource, TAttributes> {\n return evaluate(input, true);\n }\n\n function checkAll(\n inputs: readonly WardDecisionInput<TAction, TResource, TAttributes>[],\n ): WardDecision<TAction, TResource, TAttributes>[] {\n return inputs.map(decide);\n }\n\n function allowedActions(input: {\n attributes?: TAttributes;\n knownActions: readonly TAction[];\n principal?: Principal;\n resource: TResource;\n }): TAction[] {\n const seen = new Set<TAction>();\n\n return input.knownActions.filter((action) => {\n if (seen.has(action)) return false;\n seen.add(action);\n return evaluate({ ...input, action }, false).effect === 'allow';\n });\n }\n\n function forPrincipal(principal?: Principal): BoundWard<TAction, TResource, TAttributes> {\n const snapshot = snapshotPrincipal(principal);\n\n return {\n allowedActions: (input) => allowedActions({ ...input, principal: snapshot }),\n checkAll: (inputs) => checkAll(inputs.map((input) => ({ ...input, principal: snapshot }))),\n decide: (input) => decide({ ...input, principal: snapshot }),\n };\n }\n\n function tap(\n handler: (event: WardEvent<TAction, TResource, TAttributes>) => void,\n options?: { readonly signal?: AbortSignal },\n ): () => void {\n if (typeof handler !== 'function') throw new WardConfigError('tap handler must be a function');\n if (options?.signal?.aborted) return () => {};\n\n tappers.add(handler);\n const onAbort = () => tappers.delete(handler);\n options?.signal?.addEventListener('abort', onAbort, { once: true });\n\n return () => {\n tappers.delete(handler);\n options?.signal?.removeEventListener('abort', onAbort);\n };\n }\n\n return { allowedActions, checkAll, decide, forPrincipal, rules: compiled, tap };\n}\n"],"mappings":"sFA6BA,SAAgB,EAKd,EAGM,CAAC,EACgC,CACvC,IAAM,EAAW,OAAO,OAAO,EAAM,KAAK,CAAC,CAAC,KAAK,EAAM,IAAU,EAAA,YAAY,EAAM,CAAK,CAAC,CAAC,EACpF,EAAU,IAAI,IAEpB,SAAS,EACP,EACA,EAC+C,CAC/C,IAAM,EAAa,EAAA,sBAAsB,CAAK,EAE9C,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAS,OAAQ,IAAS,CACpD,IAAM,EAAO,EAAS,GAEtB,GAAI,EAAA,YAAY,EAAM,EAAO,CAAU,EAAG,CACxC,IAAM,EAAW,OAAO,OAAO,CAC7B,OAAQ,EAAK,OACb,QAAS,GACT,OAAQ,QAAQ,EAAM,IAAI,EAAK,SAAW,QAAU,SAAW,SAAS,IAAI,EAAW,OAAO,QAAQ,EAAW,SAAS,GAC1H,MACF,CAAC,EAGD,OADI,GAAM,EAAa,EAAY,CAAQ,EACpC,CACT,CACF,CAEA,IAAM,EAAW,OAAO,OAAO,CAC7B,OAAQ,OACR,QAAS,GACT,OAAQ,yBAAyB,EAAW,OAAO,QAAQ,EAAW,SAAS,iBACjF,CAAC,EAGD,OADI,GAAM,EAAa,EAAY,CAAQ,EACpC,CACT,CAEA,SAAS,EACP,EACA,EACM,CACN,GAAI,EAAQ,OAAS,EAAG,OAExB,IAAM,EAAQ,OAAO,OAAO,CAAE,WAAU,QAAO,KAAM,UAAoB,CAAC,EAC1E,IAAK,IAAM,KAAU,EACnB,GAAI,CACF,EAAO,CAAK,CACd,MAAQ,CAAC,CAEb,CAEA,SAAS,EACP,EAC+C,CAC/C,OAAO,EAAS,EAAO,EAAI,CAC7B,CAEA,SAAS,EACP,EACiD,CACjD,OAAO,EAAO,IAAI,CAAM,CAC1B,CAEA,SAAS,EAAe,EAKV,CACZ,IAAM,EAAO,IAAI,IAEjB,OAAO,EAAM,aAAa,OAAQ,GAChC,CAAI,EAAK,IAAI,CAAM,IACnB,EAAK,IAAI,CAAM,EACR,EAAS,CAAE,GAAG,EAAO,QAAO,EAAG,EAAK,CAAC,CAAC,SAAW,QACzD,CACH,CAEA,SAAS,EAAa,EAAmE,CACvF,IAAM,EAAW,EAAA,kBAAkB,CAAS,EAE5C,MAAO,CACL,eAAiB,GAAU,EAAe,CAAE,GAAG,EAAO,UAAW,CAAS,CAAC,EAC3E,SAAW,GAAW,EAAS,EAAO,IAAK,IAAW,CAAE,GAAG,EAAO,UAAW,CAAS,EAAE,CAAC,EACzF,OAAS,GAAU,EAAO,CAAE,GAAG,EAAO,UAAW,CAAS,CAAC,CAC7D,CACF,CAEA,SAAS,EACP,EACA,EACY,CACZ,GAAI,OAAO,GAAY,WAAY,MAAM,IAAI,EAAA,gBAAgB,gCAAgC,EAC7F,GAAI,GAAS,QAAQ,QAAS,UAAa,CAAC,EAE5C,EAAQ,IAAI,CAAO,EACnB,IAAM,MAAgB,EAAQ,OAAO,CAAO,EAG5C,OAFA,GAAS,QAAQ,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,MAErD,CACX,EAAQ,OAAO,CAAO,EACtB,GAAS,QAAQ,oBAAoB,QAAS,CAAO,CACvD,CACF,CAEA,MAAO,CAAE,iBAAgB,WAAU,SAAQ,eAAc,MAAO,EAAU,KAAI,CAChF"}
package/dist/factory.d.ts CHANGED
@@ -1,14 +1,18 @@
1
- import type { Ward, WardOptions, WardRule } from './types';
1
+ import type { Ward, WardAttributes, WardRule } from './types';
2
2
  /**
3
- * Creates an authorization ward from a set of rules.
3
+ * Creates an authorization ward from an ordered list of rules.
4
4
  *
5
- * **Winner selection** when multiple rules match a request:
6
- * 1. Higher `priority` wins.
7
- * 2. On priority tie, higher specificity score wins (exact > namespace-wildcard > global-wildcard,
8
- * applied independently to role, resource, and action).
9
- * 3. On specificity tie, `deny` beats `allow` (denyBonus tiebreaker).
10
- * 4. On absolute tie (identical priority, specificity, and effect), the rule declared
11
- * **first in the input array** wins.
5
+ * **Decision model**: rules are evaluated in declaration order; the first rule
6
+ * that matches the request wins. If no rule matches, the decision is **deny**
7
+ * (default deny). There is no priority, specificity scoring, or conflict
8
+ * detection — order is the only tiebreaker.
9
+ *
10
+ * A rule matches when:
11
+ * 1. Its `action` pattern matches the requested action (`*` or `ns:*` or exact).
12
+ * 2. Its `resource` pattern matches the requested resource.
13
+ * 3. Every key in its declarative `attributes` is present and equal in the
14
+ * request's `attributes` (deep equality).
15
+ * 4. Its `condition` escape-hatch callback (if any) returns `true`.
12
16
  */
13
- export declare function createWard<TAction extends string = string, TData = unknown>(rules?: readonly (WardRule<TAction, TData> | readonly WardRule<TAction, TData>[])[], options?: WardOptions<TAction, TData>): Ward<TAction, TData>;
17
+ export declare function createWard<TAction extends string = string, TResource extends string = string, TAttributes extends WardAttributes = WardAttributes>(rules?: readonly (WardRule<NoInfer<TAction>, NoInfer<TResource>, NoInfer<TAttributes>> | readonly WardRule<NoInfer<TAction>, NoInfer<TResource>, NoInfer<TAttributes>>[])[]): Ward<TAction, TResource, TAttributes>;
14
18
  //# sourceMappingURL=factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAQV,IAAI,EAQJ,WAAW,EACX,QAAQ,EAIT,MAAM,SAAS,CAAC;AAqDjB;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,GAAG,OAAO,EACzE,KAAK,GAAE,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,SAAS,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,EAAO,EACvF,OAAO,GAAE,WAAW,CAAC,OAAO,EAAE,KAAK,CAAM,GACxC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAuNtB"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,IAAI,EACJ,cAAc,EAId,QAAQ,EACT,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CACxB,OAAO,SAAS,MAAM,GAAG,MAAM,EAC/B,SAAS,SAAS,MAAM,GAAG,MAAM,EACjC,WAAW,SAAS,cAAc,GAAG,cAAc,EAEnD,KAAK,GAAE,SAAS,CACZ,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,GACpE,SAAS,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,CAClF,EAAO,GACP,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,CAyGvC"}
package/dist/factory.js CHANGED
@@ -1,138 +1,90 @@
1
1
  import { WardConfigError as e } from "./errors.js";
2
- import { compileEntry as t } from "./_compile.js";
3
- import { assertUserPrincipal as n, isOverriddenBy as r, matchesRule as i, pickWinner as a, toDecision as o, validatePrincipal as s } from "./_match.js";
4
- import { computeConflicts as c } from "./_conflict.js";
5
- import { warnAnonymousPredicates as l } from "./_dev.js";
2
+ import { compileRule as t } from "./_compile.js";
3
+ import { ruleMatches as n, snapshotDecisionInput as r, snapshotPrincipal as i } from "./_match.js";
6
4
  //#region src/factory.ts
7
- var u = l;
8
- function d(e, t, n, r, i) {
9
- let o = /* @__PURE__ */ new Set(), s = [];
10
- for (let c of r) o.has(c) || (o.add(c), a(e, t, n, c, i)?.rule.effect === "allow" && s.push(c));
11
- return s;
12
- }
13
- function f(e, t, n, r) {
14
- let a = r === void 0, o = [];
15
- for (let s of e) i(s, t, n, void 0, r, a) && o.push(s.rule);
16
- return o;
17
- }
18
- function p(l = [], p = {}) {
19
- if (p.maxConflicts !== void 0 && (!Number.isFinite(p.maxConflicts) || p.maxConflicts < 0)) throw new e("maxConflicts must be a finite non-negative number.");
20
- if (p.onConflict !== void 0 && typeof p.onConflict != "function") throw new e("onConflict must be a function.");
21
- let { maxConflicts: m = Infinity } = p, h = /* @__PURE__ */ new Set(), g = [];
22
- for (let e of l) if (Array.isArray(e)) for (let t of e) g.push(t);
23
- else g.push(e);
24
- let _ = g.map((e, n) => t(e, n));
25
- u(_);
26
- function v(e) {
27
- if (h.size !== 0) for (let t of h) try {
28
- t(e);
29
- } catch {}
30
- }
31
- function y(e, t, n, r) {
32
- let i = a(_, e, t, n, r), s = o(i);
33
- return v({
34
- action: n,
35
- data: r,
36
- decision: s,
37
- principal: e,
38
- resource: t
39
- }), s;
40
- }
41
- function b(e) {
42
- let { action: t, data: n, principal: r, resource: i } = e;
43
- return s(r), y(r, i, t, n);
5
+ function a(a = []) {
6
+ let o = Object.freeze(a.flat().map((e, n) => t(e, n))), s = /* @__PURE__ */ new Set();
7
+ function c(e, t) {
8
+ let i = r(e);
9
+ for (let e = 0; e < o.length; e++) {
10
+ let r = o[e];
11
+ if (n(r, e, i)) {
12
+ let n = Object.freeze({
13
+ effect: r.effect,
14
+ matched: !0,
15
+ reason: `rule[${e}] ${r.effect === "allow" ? "allows" : "denies"} '${i.action}' on '${i.resource}'`,
16
+ rule: r
17
+ });
18
+ return t && l(i, n), n;
19
+ }
20
+ }
21
+ let a = Object.freeze({
22
+ effect: "deny",
23
+ matched: !1,
24
+ reason: `no matching rule for '${i.action}' on '${i.resource}' (default deny)`
25
+ });
26
+ return t && l(i, a), a;
44
27
  }
45
- function x(e, t) {
46
- return t.map((t) => ({
47
- ...y(e, t.resource, t.action, t.data),
48
- action: t.action,
49
- resource: t.resource
50
- }));
28
+ function l(e, t) {
29
+ if (s.size === 0) return;
30
+ let n = Object.freeze({
31
+ decision: t,
32
+ input: e,
33
+ type: "decision"
34
+ });
35
+ for (let e of s) try {
36
+ e(n);
37
+ } catch {}
51
38
  }
52
- function S(e, t) {
53
- return t.length === 0 ? [] : (s(e), x(e, t));
39
+ function u(e) {
40
+ return c(e, !0);
54
41
  }
55
- function C(e) {
56
- let { data: t, knownActions: n, principal: r, resource: i } = e;
57
- return s(r), d(_, r, i, n, t);
42
+ function d(e) {
43
+ return e.map(u);
58
44
  }
59
- function w(e) {
60
- let { data: t, principal: n, resource: r } = e;
61
- return s(n), f(_, n, r, t);
45
+ function f(e) {
46
+ let t = /* @__PURE__ */ new Set();
47
+ return e.knownActions.filter((n) => !t.has(n) && (t.add(n), c({
48
+ ...e,
49
+ action: n
50
+ }, !1).effect === "allow"));
62
51
  }
63
- function T(e) {
64
- let { action: t, data: n, principal: a, resource: c } = e;
65
- s(a);
66
- let l = [];
67
- for (let e of _) i(e, a, c, t, n) && l.push(e);
68
- let u;
69
- for (let e of l) (!u || r(u, e)) && (u = e);
70
- let d = o(u);
52
+ function p(e) {
53
+ let t = i(e);
71
54
  return {
72
- candidates: l.map((e) => ({
73
- index: e.index,
74
- priority: e.priority,
75
- rule: e.rule,
76
- score: e.score,
77
- won: e === u
78
- })),
79
- decision: d
80
- };
81
- }
82
- function E(e) {
83
- n(e);
84
- let t = {
85
- attributes: e.attributes ? structuredClone(e.attributes) : void 0,
86
- id: e.id,
87
- roles: [...e.roles]
88
- };
89
- return {
90
- allowedActions: (e) => d(_, t, e.resource, e.knownActions, e.data),
91
- checkAll: (e) => e.length === 0 ? [] : x(t, e),
92
- explain: (e) => y(t, e.resource, e.action, e.data),
93
- rulesInScope: (e) => f(_, t, e.resource, e.data),
94
- trace: (e) => T({
95
- action: e.action,
96
- data: e.data,
97
- principal: t,
98
- resource: e.resource
55
+ allowedActions: (e) => f({
56
+ ...e,
57
+ principal: t
58
+ }),
59
+ checkAll: (e) => d(e.map((e) => ({
60
+ ...e,
61
+ principal: t
62
+ }))),
63
+ decide: (e) => u({
64
+ ...e,
65
+ principal: t
99
66
  })
100
67
  };
101
68
  }
102
- let D;
103
- function O() {
104
- return D ??= Object.freeze(c(_, m));
105
- }
106
- if (p.strict || p.onConflict) {
107
- let t = O();
108
- if (t.length > 0 && (p.onConflict && t.forEach(p.onConflict), p.strict)) {
109
- let n = t.map((e) => e.kind === "duplicate" ? `Rule[${e.indexB}] ${e.kind} of Rule[${e.indexA}]` : `Rule[${e.shadowedIndex}] ${e.kind} by Rule[${e.shadowingIndex}]`).join("; ");
110
- throw new e(`${t.length} rule conflict(s) detected: ${n}`);
111
- }
112
- }
113
- function k(e, t) {
114
- h.add(e);
115
- let n = () => h.delete(e);
116
- if (t?.signal) {
117
- if (t.signal.aborted) return h.delete(e), () => {};
118
- t.signal.addEventListener("abort", n, { once: !0 });
119
- }
120
- return () => {
121
- h.delete(e), t?.signal?.removeEventListener("abort", n);
69
+ function m(t, n) {
70
+ if (typeof t != "function") throw new e("tap handler must be a function");
71
+ if (n?.signal?.aborted) return () => {};
72
+ s.add(t);
73
+ let r = () => s.delete(t);
74
+ return n?.signal?.addEventListener("abort", r, { once: !0 }), () => {
75
+ s.delete(t), n?.signal?.removeEventListener("abort", r);
122
76
  };
123
77
  }
124
78
  return {
125
- allowedActions: C,
126
- checkAll: S,
127
- detectConflicts: O,
128
- explain: b,
129
- forUser: E,
130
- rulesInScope: w,
131
- tap: k,
132
- trace: T
79
+ allowedActions: f,
80
+ checkAll: d,
81
+ decide: u,
82
+ forPrincipal: p,
83
+ rules: o,
84
+ tap: m
133
85
  };
134
86
  }
135
87
  //#endregion
136
- export { p as createWard };
88
+ export { a as createWard };
137
89
 
138
90
  //# sourceMappingURL=factory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","names":[],"sources":["../src/factory.ts"],"sourcesContent":["import type { CompiledEntry } from './_compile';\nimport { compileEntry } from './_compile';\nimport { computeConflicts } from './_conflict';\nimport { warnAnonymousPredicates } from './_dev';\nimport { assertUserPrincipal, isOverriddenBy, matchesRule, pickWinner, toDecision, validatePrincipal } from './_match';\nimport { WardConfigError } from './errors';\nimport type {\n BoundWard,\n BoundWardAllowedActionsInput,\n BoundWardDecisionInput,\n BoundWardRulesInScopeInput,\n NormalizedWardRule,\n Principal,\n UserPrincipal,\n Ward,\n WardAllowedActionsInput,\n WardCheck,\n WardConflict,\n WardDecision,\n WardDecisionInput,\n WardDecisionResult,\n WardEvent,\n WardOptions,\n WardRule,\n WardRulesInScopeInput,\n WardTrace,\n WardTraceCandidate,\n} from './types';\n\nconst warn = warnAnonymousPredicates;\n\n// ---------------------------------------------------------------------------\n// Shared loop cores (validation-free; used by both public API and forUser)\n// ---------------------------------------------------------------------------\n\nfunction coreAllowedActions<TAction extends string, TData>(\n entries: CompiledEntry<TAction, TData>[],\n principal: Principal,\n resource: string,\n knownActions: readonly TAction[],\n data: TData | undefined,\n): TAction[] {\n const seen = new Set<TAction>();\n const result: TAction[] = [];\n\n for (const action of knownActions) {\n if (seen.has(action)) continue;\n\n seen.add(action);\n\n const winner = pickWinner(entries, principal, resource, action, data);\n\n if (winner?.rule.effect === 'allow') result.push(action);\n }\n\n return result;\n}\n\nfunction coreRulesInScope<TAction extends string, TData>(\n entries: CompiledEntry<TAction, TData>[],\n principal: Principal,\n resource: string,\n data: TData | undefined,\n): NormalizedWardRule<TAction, TData>[] {\n const skipPredicate = data === undefined;\n const result: NormalizedWardRule<TAction, TData>[] = [];\n\n for (const entry of entries) {\n if (!matchesRule(entry, principal, resource, undefined, data, skipPredicate)) continue;\n\n result.push(entry.rule as NormalizedWardRule<TAction, TData>);\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates an authorization ward from a set of rules.\n *\n * **Winner selection** when multiple rules match a request:\n * 1. Higher `priority` wins.\n * 2. On priority tie, higher specificity score wins (exact > namespace-wildcard > global-wildcard,\n * applied independently to role, resource, and action).\n * 3. On specificity tie, `deny` beats `allow` (denyBonus tiebreaker).\n * 4. On absolute tie (identical priority, specificity, and effect), the rule declared\n * **first in the input array** wins.\n */\nexport function createWard<TAction extends string = string, TData = unknown>(\n rules: readonly (WardRule<TAction, TData> | readonly WardRule<TAction, TData>[])[] = [],\n options: WardOptions<TAction, TData> = {},\n): Ward<TAction, TData> {\n if (options.maxConflicts !== undefined) {\n if (!Number.isFinite(options.maxConflicts) || options.maxConflicts < 0) {\n throw new WardConfigError('maxConflicts must be a finite non-negative number.');\n }\n }\n\n if (options.onConflict !== undefined && typeof options.onConflict !== 'function') {\n throw new WardConfigError('onConflict must be a function.');\n }\n\n const { maxConflicts = Infinity } = options;\n const tappers = new Set<(event: WardEvent<TAction, TData>) => void>();\n const flat: WardRule<TAction, TData>[] = [];\n\n for (const entry of rules) {\n if (Array.isArray(entry)) {\n for (const rule of entry) flat.push(rule);\n } else {\n flat.push(entry as WardRule<TAction, TData>);\n }\n }\n\n const entries = flat.map((rule, i) => compileEntry(rule, i));\n\n // Warn in development when an ANONYMOUS-role rule has a predicate.\n warn(entries);\n\n // -------------------------------------------------------------------------\n // Core decision + logging\n // -------------------------------------------------------------------------\n\n function emitTap(event: WardEvent<TAction, TData>): void {\n if (tappers.size === 0) return;\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {\n // Observability must not affect ward behavior.\n }\n }\n }\n\n function evaluateAndLog(\n principal: Principal,\n resource: string,\n action: TAction,\n data: TData | undefined,\n ): WardDecision<TAction, TData> {\n const winner = pickWinner(entries, principal, resource, action, data);\n const decision = toDecision(winner);\n\n emitTap({ action, data, decision, principal, resource });\n\n return decision;\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n // Request objects avoid call-site ambiguity between resource/action/data and\n // make later API growth additive instead of positional-breaking.\n\n function explain(input: WardDecisionInput<TAction, TData>): WardDecision<TAction, TData> {\n const { action, data, principal, resource } = input;\n\n validatePrincipal(principal);\n\n return evaluateAndLog(principal, resource, action, data);\n }\n\n function runCheckAll(\n principal: Principal,\n checks: readonly WardCheck<TAction, TData>[],\n ): WardDecisionResult<TAction, TData>[] {\n return checks.map((check) => ({\n ...evaluateAndLog(principal, check.resource, check.action, check.data),\n action: check.action,\n resource: check.resource,\n }));\n }\n\n function checkAll(\n principal: Principal,\n checks: readonly WardCheck<TAction, TData>[],\n ): WardDecisionResult<TAction, TData>[] {\n if (checks.length === 0) return [];\n\n validatePrincipal(principal);\n\n return runCheckAll(principal, checks);\n }\n\n function allowedActions(input: WardAllowedActionsInput<TAction, TData>): TAction[] {\n const { data, knownActions, principal, resource } = input;\n\n validatePrincipal(principal);\n\n return coreAllowedActions(entries, principal, resource, knownActions, data);\n }\n\n function rulesInScope(\n input: WardRulesInScopeInput<TData>,\n ): ReadonlyArray<Readonly<NormalizedWardRule<TAction, TData>>> {\n const { data, principal, resource } = input;\n\n validatePrincipal(principal);\n\n return coreRulesInScope(entries, principal, resource, data);\n }\n\n function trace(input: WardDecisionInput<TAction, TData>): WardTrace<TAction, TData> {\n const { action, data, principal, resource } = input;\n\n validatePrincipal(principal);\n\n const matching: CompiledEntry<TAction, TData>[] = [];\n\n for (const entry of entries) {\n if (matchesRule(entry, principal, resource, action, data)) {\n matching.push(entry);\n }\n }\n\n let winner: CompiledEntry<TAction, TData> | undefined;\n\n for (const entry of matching) {\n if (!winner || isOverriddenBy(winner, entry)) winner = entry;\n }\n\n const decision = toDecision(winner);\n\n const candidates: WardTraceCandidate<TAction, TData>[] = matching.map((entry) => ({\n index: entry.index,\n priority: entry.priority,\n rule: entry.rule,\n score: entry.score,\n won: entry === winner,\n }));\n\n return { candidates, decision };\n }\n\n function forUser(principal: UserPrincipal): BoundWard<TAction, TData> {\n assertUserPrincipal(principal);\n\n const snap: UserPrincipal = {\n attributes: principal.attributes ? structuredClone(principal.attributes) : undefined,\n id: principal.id,\n roles: [...principal.roles],\n };\n\n return {\n allowedActions: (input: BoundWardAllowedActionsInput<TAction, TData>) =>\n coreAllowedActions(entries, snap, input.resource, input.knownActions, input.data),\n checkAll: (checks) => (checks.length === 0 ? [] : runCheckAll(snap, checks)),\n explain: (input: BoundWardDecisionInput<TAction, TData>) =>\n evaluateAndLog(snap, input.resource, input.action, input.data),\n rulesInScope: (input: BoundWardRulesInScopeInput<TData>) =>\n coreRulesInScope(entries, snap, input.resource, input.data),\n trace: (input: BoundWardDecisionInput<TAction, TData>) =>\n trace({ action: input.action, data: input.data, principal: snap, resource: input.resource }),\n };\n }\n\n // -------------------------------------------------------------------------\n // Conflict detection (lazy, cached)\n // -------------------------------------------------------------------------\n\n let conflictsCache: readonly WardConflict<TAction, TData>[] | undefined;\n\n function detectConflicts(): readonly WardConflict<TAction, TData>[] {\n return (conflictsCache ??= Object.freeze(computeConflicts(entries, maxConflicts)));\n }\n\n if (options.strict || options.onConflict) {\n const conflicts = detectConflicts();\n\n if (conflicts.length > 0) {\n if (options.onConflict) conflicts.forEach(options.onConflict);\n\n if (options.strict) {\n const details = conflicts\n .map((c) =>\n c.kind === 'duplicate'\n ? `Rule[${c.indexB}] ${c.kind} of Rule[${c.indexA}]`\n : `Rule[${c.shadowedIndex}] ${c.kind} by Rule[${c.shadowingIndex}]`,\n )\n .join('; ');\n\n throw new WardConfigError(`${conflicts.length} rule conflict(s) detected: ${details}`);\n }\n }\n }\n\n function tap(handler: (event: WardEvent<TAction, TData>) => void, opts?: { signal?: AbortSignal }): () => void {\n tappers.add(handler);\n\n const onAbort = () => tappers.delete(handler);\n\n if (opts?.signal) {\n if (opts.signal.aborted) {\n tappers.delete(handler);\n return () => {};\n }\n opts.signal.addEventListener('abort', onAbort, { once: true });\n }\n\n return () => {\n tappers.delete(handler);\n opts?.signal?.removeEventListener('abort', onAbort);\n };\n }\n\n return { allowedActions, checkAll, detectConflicts, explain, forUser, rulesInScope, tap, trace };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"factory.js","names":[],"sources":["../src/factory.ts"],"sourcesContent":["import { compileRule } from './_compile';\nimport { ruleMatches, snapshotDecisionInput, snapshotPrincipal } from './_match';\nimport { WardConfigError } from './errors';\nimport type {\n BoundWard,\n Principal,\n Ward,\n WardAttributes,\n WardDecision,\n WardDecisionInput,\n WardEvent,\n WardRule,\n} from './types';\n\n/**\n * Creates an authorization ward from an ordered list of rules.\n *\n * **Decision model**: rules are evaluated in declaration order; the first rule\n * that matches the request wins. If no rule matches, the decision is **deny**\n * (default deny). There is no priority, specificity scoring, or conflict\n * detection — order is the only tiebreaker.\n *\n * A rule matches when:\n * 1. Its `action` pattern matches the requested action (`*` or `ns:*` or exact).\n * 2. Its `resource` pattern matches the requested resource.\n * 3. Every key in its declarative `attributes` is present and equal in the\n * request's `attributes` (deep equality).\n * 4. Its `condition` escape-hatch callback (if any) returns `true`.\n */\nexport function createWard<\n TAction extends string = string,\n TResource extends string = string,\n TAttributes extends WardAttributes = WardAttributes,\n>(\n rules: readonly (\n | WardRule<NoInfer<TAction>, NoInfer<TResource>, NoInfer<TAttributes>>\n | readonly WardRule<NoInfer<TAction>, NoInfer<TResource>, NoInfer<TAttributes>>[]\n )[] = [],\n): Ward<TAction, TResource, TAttributes> {\n const compiled = Object.freeze(rules.flat().map((rule, index) => compileRule(rule, index)));\n const tappers = new Set<(event: WardEvent<TAction, TResource, TAttributes>) => void>();\n\n function evaluate(\n input: WardDecisionInput<TAction, TResource, TAttributes>,\n emit: boolean,\n ): WardDecision<TAction, TResource, TAttributes> {\n const normalized = snapshotDecisionInput(input);\n\n for (let index = 0; index < compiled.length; index++) {\n const rule = compiled[index];\n\n if (ruleMatches(rule, index, normalized)) {\n const decision = Object.freeze({\n effect: rule.effect,\n matched: true,\n reason: `rule[${index}] ${rule.effect === 'allow' ? 'allows' : 'denies'} '${normalized.action}' on '${normalized.resource}'`,\n rule,\n }) as WardDecision<TAction, TResource, TAttributes>;\n\n if (emit) emitDecision(normalized, decision);\n return decision;\n }\n }\n\n const decision = Object.freeze({\n effect: 'deny',\n matched: false,\n reason: `no matching rule for '${normalized.action}' on '${normalized.resource}' (default deny)`,\n }) as WardDecision<TAction, TResource, TAttributes>;\n\n if (emit) emitDecision(normalized, decision);\n return decision;\n }\n\n function emitDecision(\n input: WardDecisionInput<TAction, TResource, TAttributes> & { principal: Principal },\n decision: WardDecision<TAction, TResource, TAttributes>,\n ): void {\n if (tappers.size === 0) return;\n\n const event = Object.freeze({ decision, input, type: 'decision' as const });\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {}\n }\n }\n\n function decide(\n input: WardDecisionInput<TAction, TResource, TAttributes>,\n ): WardDecision<TAction, TResource, TAttributes> {\n return evaluate(input, true);\n }\n\n function checkAll(\n inputs: readonly WardDecisionInput<TAction, TResource, TAttributes>[],\n ): WardDecision<TAction, TResource, TAttributes>[] {\n return inputs.map(decide);\n }\n\n function allowedActions(input: {\n attributes?: TAttributes;\n knownActions: readonly TAction[];\n principal?: Principal;\n resource: TResource;\n }): TAction[] {\n const seen = new Set<TAction>();\n\n return input.knownActions.filter((action) => {\n if (seen.has(action)) return false;\n seen.add(action);\n return evaluate({ ...input, action }, false).effect === 'allow';\n });\n }\n\n function forPrincipal(principal?: Principal): BoundWard<TAction, TResource, TAttributes> {\n const snapshot = snapshotPrincipal(principal);\n\n return {\n allowedActions: (input) => allowedActions({ ...input, principal: snapshot }),\n checkAll: (inputs) => checkAll(inputs.map((input) => ({ ...input, principal: snapshot }))),\n decide: (input) => decide({ ...input, principal: snapshot }),\n };\n }\n\n function tap(\n handler: (event: WardEvent<TAction, TResource, TAttributes>) => void,\n options?: { readonly signal?: AbortSignal },\n ): () => void {\n if (typeof handler !== 'function') throw new WardConfigError('tap handler must be a function');\n if (options?.signal?.aborted) return () => {};\n\n tappers.add(handler);\n const onAbort = () => tappers.delete(handler);\n options?.signal?.addEventListener('abort', onAbort, { once: true });\n\n return () => {\n tappers.delete(handler);\n options?.signal?.removeEventListener('abort', onAbort);\n };\n }\n\n return { allowedActions, checkAll, decide, forPrincipal, rules: compiled, tap };\n}\n"],"mappings":";;;;AA6BA,SAAgB,EAKd,IAGM,CAAC,GACgC;CACvC,IAAM,IAAW,OAAO,OAAO,EAAM,KAAK,CAAC,CAAC,KAAK,GAAM,MAAU,EAAY,GAAM,CAAK,CAAC,CAAC,GACpF,oBAAU,IAAI,IAAiE;CAErF,SAAS,EACP,GACA,GAC+C;EAC/C,IAAM,IAAa,EAAsB,CAAK;EAE9C,KAAK,IAAI,IAAQ,GAAG,IAAQ,EAAS,QAAQ,KAAS;GACpD,IAAM,IAAO,EAAS;GAEtB,IAAI,EAAY,GAAM,GAAO,CAAU,GAAG;IACxC,IAAM,IAAW,OAAO,OAAO;KAC7B,QAAQ,EAAK;KACb,SAAS;KACT,QAAQ,QAAQ,EAAM,IAAI,EAAK,WAAW,UAAU,WAAW,SAAS,IAAI,EAAW,OAAO,QAAQ,EAAW,SAAS;KAC1H;IACF,CAAC;IAGD,OADI,KAAM,EAAa,GAAY,CAAQ,GACpC;GACT;EACF;EAEA,IAAM,IAAW,OAAO,OAAO;GAC7B,QAAQ;GACR,SAAS;GACT,QAAQ,yBAAyB,EAAW,OAAO,QAAQ,EAAW,SAAS;EACjF,CAAC;EAGD,OADI,KAAM,EAAa,GAAY,CAAQ,GACpC;CACT;CAEA,SAAS,EACP,GACA,GACM;EACN,IAAI,EAAQ,SAAS,GAAG;EAExB,IAAM,IAAQ,OAAO,OAAO;GAAE;GAAU;GAAO,MAAM;EAAoB,CAAC;EAC1E,KAAK,IAAM,KAAU,GACnB,IAAI;GACF,EAAO,CAAK;EACd,QAAQ,CAAC;CAEb;CAEA,SAAS,EACP,GAC+C;EAC/C,OAAO,EAAS,GAAO,EAAI;CAC7B;CAEA,SAAS,EACP,GACiD;EACjD,OAAO,EAAO,IAAI,CAAM;CAC1B;CAEA,SAAS,EAAe,GAKV;EACZ,IAAM,oBAAO,IAAI,IAAa;EAE9B,OAAO,EAAM,aAAa,QAAQ,MAChC,CAAI,EAAK,IAAI,CAAM,MACnB,EAAK,IAAI,CAAM,GACR,EAAS;GAAE,GAAG;GAAO;EAAO,GAAG,EAAK,CAAC,CAAC,WAAW,QACzD;CACH;CAEA,SAAS,EAAa,GAAmE;EACvF,IAAM,IAAW,EAAkB,CAAS;EAE5C,OAAO;GACL,iBAAiB,MAAU,EAAe;IAAE,GAAG;IAAO,WAAW;GAAS,CAAC;GAC3E,WAAW,MAAW,EAAS,EAAO,KAAK,OAAW;IAAE,GAAG;IAAO,WAAW;GAAS,EAAE,CAAC;GACzF,SAAS,MAAU,EAAO;IAAE,GAAG;IAAO,WAAW;GAAS,CAAC;EAC7D;CACF;CAEA,SAAS,EACP,GACA,GACY;EACZ,IAAI,OAAO,KAAY,YAAY,MAAM,IAAI,EAAgB,gCAAgC;EAC7F,IAAI,GAAS,QAAQ,SAAS,aAAa,CAAC;EAE5C,EAAQ,IAAI,CAAO;EACnB,IAAM,UAAgB,EAAQ,OAAO,CAAO;EAG5C,OAFA,GAAS,QAAQ,iBAAiB,SAAS,GAAS,EAAE,MAAM,GAAK,CAAC,SAErD;GAEX,AADA,EAAQ,OAAO,CAAO,GACtB,GAAS,QAAQ,oBAAoB,SAAS,CAAO;EACvD;CACF;CAEA,OAAO;EAAE;EAAgB;EAAU;EAAQ;EAAc,OAAO;EAAU;CAAI;AAChF"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./builder.cjs"),t=require("./constants.cjs"),n=require("./errors.cjs"),r=require("./resource.cjs"),i=require("./factory.cjs");exports.ANONYMOUS=t.ANONYMOUS,exports.WILDCARD=t.WILDCARD,exports.WardConfigError=n.WardConfigError,exports.WardError=n.WardError,exports.WardPredicateError=n.WardPredicateError,exports.allow=e.allow,exports.createWard=i.createWard,exports.deny=e.deny,exports.matchesPattern=r.matchesPattern,exports.patternCovers=r.patternCovers,exports.predicate=e.predicate;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./constants.cjs"),t=require("./errors.cjs"),n=require("./resource.cjs"),r=require("./factory.cjs"),i=require("./rules.cjs");exports.ANONYMOUS=e.ANONYMOUS,exports.WILDCARD=e.WILDCARD,exports.WardConditionError=t.WardConditionError,exports.WardConfigError=t.WardConfigError,exports.WardError=t.WardError,exports.allow=i.allow,exports.createWard=r.createWard,exports.deny=i.deny,exports.matchesPattern=n.matchesPattern,exports.patternCovers=n.patternCovers,exports.predicate=i.predicate;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { allow, deny, predicate } from './builder';
2
1
  export { ANONYMOUS, WILDCARD } from './constants';
3
- export { WardConfigError, WardError, WardPredicateError } from './errors';
2
+ export { WardConditionError, WardConfigError, WardError } from './errors';
4
3
  export { createWard } from './factory';
5
4
  export { matchesPattern, patternCovers } from './resource';
6
- export type { BoundWard, BoundWardAllowedActionsInput, BoundWardDecisionInput, BoundWardRulesInScopeInput, ConflictKind, NormalizedWardRule, Principal, RuleContext, UserPrincipal, Ward, WardAllowedActionsInput, WardCheck, WardConflict, WardDecision, WardDecisionInput, WardDecisionResult, WardEvent, WardOptions, WardPredicate, WardRule, WardRulesInScopeInput, WardTrace, WardTraceCandidate, } from './types';
5
+ export { allow, deny, predicate } from './rules';
6
+ export type { BoundWard, BoundWardAllowedActionsInput, BoundWardDecisionInput, Principal, UserPrincipal, Ward, WardAllowedActionsInput, WardAttributes, WardAttributeValue, WardCondition, WardConditionInput, WardDecision, WardDecisionInput, WardEvent, WardPattern, WardRule, } from './types';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3D,YAAY,EACV,SAAS,EACT,4BAA4B,EAC5B,sBAAsB,EACtB,0BAA0B,EAC1B,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,aAAa,EACb,IAAI,EACJ,uBAAuB,EACvB,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,aAAa,EACb,QAAQ,EACR,qBAAqB,EACrB,SAAS,EACT,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACjD,YAAY,EACV,SAAS,EACT,4BAA4B,EAC5B,sBAAsB,EACtB,SAAS,EACT,aAAa,EACb,IAAI,EACJ,uBAAuB,EACvB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,SAAS,EACT,WAAW,EACX,QAAQ,GACT,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { allow as e, deny as t, predicate as n } from "./builder.js";
2
- import { ANONYMOUS as r, WILDCARD as i } from "./constants.js";
3
- import { WardConfigError as a, WardError as o, WardPredicateError as s } from "./errors.js";
4
- import { matchesPattern as c, patternCovers as l } from "./resource.js";
5
- import { createWard as u } from "./factory.js";
6
- export { r as ANONYMOUS, i as WILDCARD, a as WardConfigError, o as WardError, s as WardPredicateError, e as allow, u as createWard, t as deny, c as matchesPattern, l as patternCovers, n as predicate };
1
+ import { ANONYMOUS as e, WILDCARD as t } from "./constants.js";
2
+ import { WardConditionError as n, WardConfigError as r, WardError as i } from "./errors.js";
3
+ import { matchesPattern as a, patternCovers as o } from "./resource.js";
4
+ import { createWard as s } from "./factory.js";
5
+ import { allow as c, deny as l, predicate as u } from "./rules.js";
6
+ export { e as ANONYMOUS, t as WILDCARD, n as WardConditionError, r as WardConfigError, i as WardError, c as allow, s as createWard, l as deny, a as matchesPattern, o as patternCovers, u as predicate };
package/dist/rules.cjs ADDED
@@ -0,0 +1,2 @@
1
+ require("./constants.cjs");function e(e){let t=Object.freeze(Array.isArray(e)?[...e]:[e]);return({principal:e})=>t.some(t=>t===`anonymous`?e===null:t===`*`?e!==null:e?.roles.includes(t))}function t(t,n,r,i,a){let o=e(n),s=a?.when?e=>o(e)&&a.when(e):o;return i.map(e=>({action:e,condition:s,effect:t,resource:r}))}var n=(e,n,r,i)=>t(`allow`,e,n,r,i),r=(e,n,r,i)=>t(`deny`,e,n,r,i),i={and:(...e)=>t=>e.every(e=>e(t)),hasRole:e=>({principal:t})=>t?.roles.includes(e)??!1,not:e=>t=>!e(t),or:(...e)=>t=>e.some(e=>e(t)),owns:e=>({attributes:t,principal:n})=>n!==null&&t?.[e]===n.id};exports.allow=n,exports.deny=r,exports.predicate=i;
2
+ //# sourceMappingURL=rules.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.cjs","names":[],"sources":["../src/rules.ts"],"sourcesContent":["import { ANONYMOUS, WILDCARD } from './constants';\nimport type { WardAttributes, WardCondition, WardPattern, WardRule } from './types';\n\ntype RuleOptions<TAttributes extends WardAttributes> = Readonly<{\n when?: WardCondition<TAttributes>;\n}>;\n\nfunction roleCondition<TAttributes extends WardAttributes>(\n role: string | readonly string[],\n): WardCondition<TAttributes> {\n const roles = Object.freeze(Array.isArray(role) ? [...role] : [role]);\n\n return ({ principal }) =>\n roles.some((candidate) =>\n candidate === ANONYMOUS\n ? principal === null\n : candidate === WILDCARD\n ? principal !== null\n : principal?.roles.includes(candidate),\n );\n}\n\nfunction buildRules<TAction extends string, TResource extends string, TAttributes extends WardAttributes>(\n effect: 'allow' | 'deny',\n role: string | readonly string[],\n resource: WardPattern<TResource>,\n actions: readonly WardPattern<TAction>[],\n options?: RuleOptions<TAttributes>,\n): WardRule<TAction, TResource, TAttributes>[] {\n const matchesRole = roleCondition<TAttributes>(role);\n const condition = options?.when\n ? (input: Parameters<WardCondition<TAttributes>>[0]) => matchesRole(input) && options.when!(input)\n : matchesRole;\n\n return actions.map((action) => ({ action, condition, effect, resource }));\n}\n\nexport const allow = <\n TAction extends string = string,\n TResource extends string = string,\n TAttributes extends WardAttributes = WardAttributes,\n>(\n role: string | readonly string[],\n resource: WardPattern<TResource>,\n actions: readonly WardPattern<TAction>[],\n options?: RuleOptions<TAttributes>,\n): WardRule<TAction, TResource, TAttributes>[] => buildRules('allow', role, resource, actions, options);\n\nexport const deny = <\n TAction extends string = string,\n TResource extends string = string,\n TAttributes extends WardAttributes = WardAttributes,\n>(\n role: string | readonly string[],\n resource: WardPattern<TResource>,\n actions: readonly WardPattern<TAction>[],\n options?: RuleOptions<TAttributes>,\n): WardRule<TAction, TResource, TAttributes>[] => buildRules('deny', role, resource, actions, options);\n\nconst hasRole =\n <TAttributes extends WardAttributes = WardAttributes>(role: string): WardCondition<TAttributes> =>\n ({ principal }) =>\n principal?.roles.includes(role) ?? false;\nconst owns =\n <TAttributes extends WardAttributes = WardAttributes>(\n attribute: keyof TAttributes & string,\n ): WardCondition<TAttributes> =>\n ({ attributes, principal }) =>\n principal !== null && attributes?.[attribute] === principal.id;\nconst and =\n <TAttributes extends WardAttributes = WardAttributes>(\n ...conditions: readonly WardCondition<TAttributes>[]\n ): WardCondition<TAttributes> =>\n (input) =>\n conditions.every((condition) => condition(input));\nconst or =\n <TAttributes extends WardAttributes = WardAttributes>(\n ...conditions: readonly WardCondition<TAttributes>[]\n ): WardCondition<TAttributes> =>\n (input) =>\n conditions.some((condition) => condition(input));\nconst not =\n <TAttributes extends WardAttributes = WardAttributes>(\n condition: WardCondition<TAttributes>,\n ): WardCondition<TAttributes> =>\n (input) =>\n !condition(input);\n\nexport const predicate = { and, hasRole, not, or, owns } as const;\n"],"mappings":"2BAOA,SAAS,EACP,EAC4B,CAC5B,IAAM,EAAQ,OAAO,OAAO,MAAM,QAAQ,CAAI,EAAI,CAAC,GAAG,CAAI,EAAI,CAAC,CAAI,CAAC,EAEpE,OAAQ,CAAE,eACR,EAAM,KAAM,GACV,IAAA,YACI,IAAc,KACd,IAAA,IACE,IAAc,KACd,GAAW,MAAM,SAAS,CAAS,CAC3C,CACJ,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EAC6C,CAC7C,IAAM,EAAc,EAA2B,CAAI,EAC7C,EAAY,GAAS,KACtB,GAAqD,EAAY,CAAK,GAAK,EAAQ,KAAM,CAAK,EAC/F,EAEJ,OAAO,EAAQ,IAAK,IAAY,CAAE,SAAQ,YAAW,SAAQ,UAAS,EAAE,CAC1E,CAEA,IAAa,GAKX,EACA,EACA,EACA,IACgD,EAAW,QAAS,EAAM,EAAU,EAAS,CAAO,EAEzF,GAKX,EACA,EACA,EACA,IACgD,EAAW,OAAQ,EAAM,EAAU,EAAS,CAAO,EA+BxF,EAAY,CAAE,KAjBvB,GAAG,IAEJ,GACC,EAAW,MAAO,GAAc,EAAU,CAAK,CAAC,EAcpB,QA5BwB,IACrD,CAAE,eACD,GAAW,MAAM,SAAS,CAAI,GAAK,GA0BE,IALrC,GAED,GACC,CAAC,EAAU,CAAK,EAE0B,IAX1C,GAAG,IAEJ,GACC,EAAW,KAAM,GAAc,EAAU,CAAK,CAAC,EAQD,KAvB9C,IAED,CAAE,aAAY,eACb,IAAc,MAAQ,IAAa,KAAe,EAAU,EAoBT"}
@@ -0,0 +1,15 @@
1
+ import type { WardAttributes, WardCondition, WardPattern, WardRule } from './types';
2
+ type RuleOptions<TAttributes extends WardAttributes> = Readonly<{
3
+ when?: WardCondition<TAttributes>;
4
+ }>;
5
+ export declare const allow: <TAction extends string = string, TResource extends string = string, TAttributes extends WardAttributes = WardAttributes>(role: string | readonly string[], resource: WardPattern<TResource>, actions: readonly WardPattern<TAction>[], options?: RuleOptions<TAttributes>) => WardRule<TAction, TResource, TAttributes>[];
6
+ export declare const deny: <TAction extends string = string, TResource extends string = string, TAttributes extends WardAttributes = WardAttributes>(role: string | readonly string[], resource: WardPattern<TResource>, actions: readonly WardPattern<TAction>[], options?: RuleOptions<TAttributes>) => WardRule<TAction, TResource, TAttributes>[];
7
+ export declare const predicate: {
8
+ readonly and: <TAttributes extends WardAttributes = Readonly<Record<string, import("./types").WardAttributeValue>>>(...conditions: readonly WardCondition<TAttributes>[]) => WardCondition<TAttributes>;
9
+ readonly hasRole: <TAttributes extends WardAttributes = Readonly<Record<string, import("./types").WardAttributeValue>>>(role: string) => WardCondition<TAttributes>;
10
+ readonly not: <TAttributes extends WardAttributes = Readonly<Record<string, import("./types").WardAttributeValue>>>(condition: WardCondition<TAttributes>) => WardCondition<TAttributes>;
11
+ readonly or: <TAttributes extends WardAttributes = Readonly<Record<string, import("./types").WardAttributeValue>>>(...conditions: readonly WardCondition<TAttributes>[]) => WardCondition<TAttributes>;
12
+ readonly owns: <TAttributes extends WardAttributes = Readonly<Record<string, import("./types").WardAttributeValue>>>(attribute: keyof TAttributes & string) => WardCondition<TAttributes>;
13
+ };
14
+ export {};
15
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEpF,KAAK,WAAW,CAAC,WAAW,SAAS,cAAc,IAAI,QAAQ,CAAC;IAC9D,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;CACnC,CAAC,CAAC;AAgCH,eAAO,MAAM,KAAK,GAChB,OAAO,SAAS,MAAM,GAAG,MAAM,EAC/B,SAAS,SAAS,MAAM,GAAG,MAAM,EACjC,WAAW,SAAS,cAAc,GAAG,cAAc,EAEnD,MAAM,MAAM,GAAG,SAAS,MAAM,EAAE,EAChC,UAAU,WAAW,CAAC,SAAS,CAAC,EAChC,SAAS,SAAS,WAAW,CAAC,OAAO,CAAC,EAAE,EACxC,UAAU,WAAW,CAAC,WAAW,CAAC,KACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,EAA2D,CAAC;AAExG,eAAO,MAAM,IAAI,GACf,OAAO,SAAS,MAAM,GAAG,MAAM,EAC/B,SAAS,SAAS,MAAM,GAAG,MAAM,EACjC,WAAW,SAAS,cAAc,GAAG,cAAc,EAEnD,MAAM,MAAM,GAAG,SAAS,MAAM,EAAE,EAChC,UAAU,WAAW,CAAC,SAAS,CAAC,EAChC,SAAS,SAAS,WAAW,CAAC,OAAO,CAAC,EAAE,EACxC,UAAU,WAAW,CAAC,WAAW,CAAC,KACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,EAA0D,CAAC;AA+BvG,eAAO,MAAM,SAAS;mBAlBnB,WAAW,SAAS,cAAc,kFAClB,SAAS,aAAa,CAAC,WAAW,CAAC,EAAE,KACnD,aAAa,CAAC,WAAW,CAAC;uBAZ5B,WAAW,SAAS,cAAc,yEAAyB,MAAM,KAAG,aAAa,CAAC,WAAW,CAAC;mBAsB9F,WAAW,SAAS,cAAc,8EACtB,aAAa,CAAC,WAAW,CAAC,KACpC,aAAa,CAAC,WAAW,CAAC;kBAR5B,WAAW,SAAS,cAAc,kFAClB,SAAS,aAAa,CAAC,WAAW,CAAC,EAAE,KACnD,aAAa,CAAC,WAAW,CAAC;oBAd5B,WAAW,SAAS,cAAc,8EACtB,MAAM,WAAW,GAAG,MAAM,KACpC,aAAa,CAAC,WAAW,CAAC;CAsBkC,CAAC"}