@vielzeug/ward 2.2.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 +32 -37
  2. package/dist/_compile.cjs +1 -1
  3. package/dist/_compile.cjs.map +1 -1
  4. package/dist/_compile.d.ts +5 -33
  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 +70 -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 -133
  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 -90
  60. package/dist/builder.d.ts.map +0 -1
  61. package/dist/builder.js +0 -42
  62. package/dist/builder.js.map +0 -1
package/dist/_conflict.js DELETED
@@ -1,51 +0,0 @@
1
- import { ANONYMOUS as e } from "./constants.js";
2
- import { patternCovers as t } from "./resource.js";
3
- import { isOverriddenBy as n } from "./_match.js";
4
- //#region src/_conflict.ts
5
- function r(t, n) {
6
- let r = n.includes(e), i = n.some((t) => t !== e);
7
- return r && !t.includes("anonymous") ? !1 : i ? t.includes("*") ? !0 : n.filter((t) => t !== e).every((e) => t.includes(e)) : !0;
8
- }
9
- function i(e, t) {
10
- if (e.length !== t.length) return !1;
11
- let n = new Set(e);
12
- return t.every((e) => n.has(e));
13
- }
14
- function a(e, n) {
15
- return r(e.roles, n.roles) && t(e.rule.resource, n.rule.resource) && t(e.rule.action, n.rule.action);
16
- }
17
- function o(e, t) {
18
- let r = [];
19
- for (let o = 0; o < e.length && r.length < t; o++) for (let s = o + 1; s < e.length && r.length < t; s++) {
20
- let t = e[o], c = e[s];
21
- if (!t.rule.when && !c.rule.when && i(t.roles, c.roles) && t.rule.resource === c.rule.resource && t.rule.action === c.rule.action) {
22
- r.push({
23
- indexA: t.index,
24
- indexB: c.index,
25
- kind: "duplicate",
26
- ruleA: t.rule,
27
- ruleB: c.rule
28
- });
29
- continue;
30
- }
31
- let l = n(t, c);
32
- !l && !t.rule.when && a(t, c) ? r.push({
33
- kind: "shadowed",
34
- shadowedIndex: c.index,
35
- shadowedRule: c.rule,
36
- shadowingIndex: t.index,
37
- shadowingRule: t.rule
38
- }) : l && !c.rule.when && a(c, t) && r.push({
39
- kind: "shadowed",
40
- shadowedIndex: t.index,
41
- shadowedRule: t.rule,
42
- shadowingIndex: c.index,
43
- shadowingRule: c.rule
44
- });
45
- }
46
- return r;
47
- }
48
- //#endregion
49
- export { o as computeConflicts };
50
-
51
- //# sourceMappingURL=_conflict.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_conflict.js","names":[],"sources":["../src/_conflict.ts"],"sourcesContent":["import type { CompiledEntry } from './_compile';\nimport { isOverriddenBy } from './_match';\nimport { ANONYMOUS, WILDCARD } from './constants';\nimport { patternCovers } from './resource';\nimport type { WardConflict } from './types';\n\n// ---------------------------------------------------------------------------\n// Role coverage helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Returns true if every principal that satisfies `narrowRoles` also satisfies `broadRoles`.\n */\nfunction roleCoversRoles(broadRoles: readonly string[], narrowRoles: readonly string[]): boolean {\n const narrowHasAnonymous = narrowRoles.includes(ANONYMOUS);\n const narrowHasAuthenticated = narrowRoles.some((r) => r !== ANONYMOUS);\n\n if (narrowHasAnonymous && !broadRoles.includes(ANONYMOUS)) return false;\n\n if (narrowHasAuthenticated) {\n if (broadRoles.includes(WILDCARD)) return true;\n\n return narrowRoles.filter((r) => r !== ANONYMOUS).every((r) => broadRoles.includes(r));\n }\n\n return true;\n}\n\nfunction rolesSetsEqual(a: readonly string[], b: readonly string[]): boolean {\n if (a.length !== b.length) return false;\n\n const setA = new Set(a);\n\n return b.every((r) => setA.has(r));\n}\n\n/** Returns true if every request that triggers `narrow` would also trigger `broad`. */\nfunction ruleCovers<TAction extends string, TData>(\n broad: CompiledEntry<TAction, TData>,\n narrow: CompiledEntry<TAction, TData>,\n): boolean {\n return (\n roleCoversRoles(broad.roles, narrow.roles) &&\n patternCovers(broad.rule.resource as string, narrow.rule.resource as string) &&\n patternCovers(broad.rule.action as string, narrow.rule.action as string)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Conflict detection\n// ---------------------------------------------------------------------------\n\n/**\n * Detects conflicts between compiled entries.\n *\n * @complexity O(n²) in the number of rules. Scales to hundreds of rules\n * with negligible overhead; for very large auto-generated policies, use `maxConflicts`.\n */\nexport function computeConflicts<TAction extends string, TData>(\n entries: CompiledEntry<TAction, TData>[],\n maxConflicts: number,\n): WardConflict<TAction, TData>[] {\n const conflicts: WardConflict<TAction, TData>[] = [];\n\n for (let i = 0; i < entries.length && conflicts.length < maxConflicts; i++) {\n for (let j = i + 1; j < entries.length && conflicts.length < maxConflicts; j++) {\n const a = entries[i];\n const b = entries[j];\n\n if (\n !a.rule.when &&\n !b.rule.when &&\n rolesSetsEqual(a.roles, b.roles) &&\n a.rule.resource === b.rule.resource &&\n a.rule.action === b.rule.action\n ) {\n conflicts.push({\n indexA: a.index,\n indexB: b.index,\n kind: 'duplicate',\n ruleA: a.rule,\n ruleB: b.rule,\n });\n continue;\n }\n\n const bCanOverrideA = isOverriddenBy(a, b);\n\n if (!bCanOverrideA && !a.rule.when && ruleCovers(a, b)) {\n conflicts.push({\n kind: 'shadowed',\n shadowedIndex: b.index,\n shadowedRule: b.rule,\n shadowingIndex: a.index,\n shadowingRule: a.rule,\n });\n } else if (bCanOverrideA && !b.rule.when && ruleCovers(b, a)) {\n conflicts.push({\n kind: 'shadowed',\n shadowedIndex: a.index,\n shadowedRule: a.rule,\n shadowingIndex: b.index,\n shadowingRule: b.rule,\n });\n }\n }\n }\n\n return conflicts;\n}\n"],"mappings":";;;;AAaA,SAAS,EAAgB,GAA+B,GAAyC;CAC/F,IAAM,IAAqB,EAAY,SAAS,CAAS,GACnD,IAAyB,EAAY,MAAM,MAAM,MAAM,CAAS;CAUtE,OARI,KAAsB,CAAC,EAAW,SAAA,WAAkB,IAAU,KAE9D,IACE,EAAW,SAAA,GAAiB,IAAU,KAEnC,EAAY,QAAQ,MAAM,MAAM,CAAS,CAAC,CAAC,OAAO,MAAM,EAAW,SAAS,CAAC,CAAC,IAGhF;AACT;AAEA,SAAS,EAAe,GAAsB,GAA+B;CAC3E,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAElC,IAAM,IAAO,IAAI,IAAI,CAAC;CAEtB,OAAO,EAAE,OAAO,MAAM,EAAK,IAAI,CAAC,CAAC;AACnC;AAGA,SAAS,EACP,GACA,GACS;CACT,OACE,EAAgB,EAAM,OAAO,EAAO,KAAK,KACzC,EAAc,EAAM,KAAK,UAAoB,EAAO,KAAK,QAAkB,KAC3E,EAAc,EAAM,KAAK,QAAkB,EAAO,KAAK,MAAgB;AAE3E;AAYA,SAAgB,EACd,GACA,GACgC;CAChC,IAAM,IAA4C,CAAC;CAEnD,KAAK,IAAI,IAAI,GAAG,IAAI,EAAQ,UAAU,EAAU,SAAS,GAAc,KACrE,KAAK,IAAI,IAAI,IAAI,GAAG,IAAI,EAAQ,UAAU,EAAU,SAAS,GAAc,KAAK;EAC9E,IAAM,IAAI,EAAQ,IACZ,IAAI,EAAQ;EAElB,IACE,CAAC,EAAE,KAAK,QACR,CAAC,EAAE,KAAK,QACR,EAAe,EAAE,OAAO,EAAE,KAAK,KAC/B,EAAE,KAAK,aAAa,EAAE,KAAK,YAC3B,EAAE,KAAK,WAAW,EAAE,KAAK,QACzB;GACA,EAAU,KAAK;IACb,QAAQ,EAAE;IACV,QAAQ,EAAE;IACV,MAAM;IACN,OAAO,EAAE;IACT,OAAO,EAAE;GACX,CAAC;GACD;EACF;EAEA,IAAM,IAAgB,EAAe,GAAG,CAAC;EAEzC,AAAI,CAAC,KAAiB,CAAC,EAAE,KAAK,QAAQ,EAAW,GAAG,CAAC,IACnD,EAAU,KAAK;GACb,MAAM;GACN,eAAe,EAAE;GACjB,cAAc,EAAE;GAChB,gBAAgB,EAAE;GAClB,eAAe,EAAE;EACnB,CAAC,IACQ,KAAiB,CAAC,EAAE,KAAK,QAAQ,EAAW,GAAG,CAAC,KACzD,EAAU,KAAK;GACb,MAAM;GACN,eAAe,EAAE;GACjB,cAAc,EAAE;GAChB,gBAAgB,EAAE;GAClB,eAAe,EAAE;EACnB,CAAC;CAEL;CAGF,OAAO;AACT"}
package/dist/_dev.cjs DELETED
@@ -1,2 +0,0 @@
1
- function e(e){}exports.warnAnonymousPredicates=e;
2
- //# sourceMappingURL=_dev.cjs.map
package/dist/_dev.cjs.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["import type { CompiledEntry } from './_compile';\n\nimport { ANONYMOUS } from './constants';\n\nconst isDev = !(globalThis as { __WARD_PROD__?: boolean }).__WARD_PROD__;\n\n/** @internal @security Messages may include user-supplied role names. */\nfunction warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/ward] ${msg}`);\n}\n\n/**\n * Emit a development-only warning when an ANONYMOUS-role rule has a `when` predicate.\n * The predicate is skipped for unauthenticated principals, so the rule can never match\n * anonymous requests — likely a policy authoring mistake.\n */\nexport function warnAnonymousPredicates<TAction extends string, TData>(\n entries: readonly CompiledEntry<TAction, TData>[],\n): void {\n if (!isDev) return;\n\n for (const entry of entries) {\n if (!entry.rule.when) continue;\n\n const hasAnonymous = entry.roles.includes(ANONYMOUS);\n const hasOnlyAnonymous = hasAnonymous && entry.roles.length === 1;\n\n if (hasOnlyAnonymous) {\n warn(\n `Rule[${entry.index}] pairs role '${ANONYMOUS}' with a \\`when\\` predicate. ` +\n 'Predicates are skipped for unauthenticated principals, so this rule can never match anonymous requests.',\n );\n }\n }\n}\n"],"mappings":"AAgBA,SAAgB,EACd,EACM,CAgBR"}
package/dist/_dev.d.ts DELETED
@@ -1,8 +0,0 @@
1
- import type { CompiledEntry } from './_compile';
2
- /**
3
- * Emit a development-only warning when an ANONYMOUS-role rule has a `when` predicate.
4
- * The predicate is skipped for unauthenticated principals, so the rule can never match
5
- * anonymous requests — likely a policy authoring mistake.
6
- */
7
- export declare function warnAnonymousPredicates<TAction extends string, TData>(entries: readonly CompiledEntry<TAction, TData>[]): void;
8
- //# sourceMappingURL=_dev.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_dev.d.ts","sourceRoot":"","sources":["../src/_dev.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAWhD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,SAAS,MAAM,EAAE,KAAK,EACnE,OAAO,EAAE,SAAS,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,GAChD,IAAI,CAgBN"}
package/dist/_dev.js DELETED
@@ -1,6 +0,0 @@
1
- //#region src/_dev.ts
2
- function e(e) {}
3
- //#endregion
4
- export { e as warnAnonymousPredicates };
5
-
6
- //# sourceMappingURL=_dev.js.map
package/dist/_dev.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["import type { CompiledEntry } from './_compile';\n\nimport { ANONYMOUS } from './constants';\n\nconst isDev = !(globalThis as { __WARD_PROD__?: boolean }).__WARD_PROD__;\n\n/** @internal @security Messages may include user-supplied role names. */\nfunction warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/ward] ${msg}`);\n}\n\n/**\n * Emit a development-only warning when an ANONYMOUS-role rule has a `when` predicate.\n * The predicate is skipped for unauthenticated principals, so the rule can never match\n * anonymous requests — likely a policy authoring mistake.\n */\nexport function warnAnonymousPredicates<TAction extends string, TData>(\n entries: readonly CompiledEntry<TAction, TData>[],\n): void {\n if (!isDev) return;\n\n for (const entry of entries) {\n if (!entry.rule.when) continue;\n\n const hasAnonymous = entry.roles.includes(ANONYMOUS);\n const hasOnlyAnonymous = hasAnonymous && entry.roles.length === 1;\n\n if (hasOnlyAnonymous) {\n warn(\n `Rule[${entry.index}] pairs role '${ANONYMOUS}' with a \\`when\\` predicate. ` +\n 'Predicates are skipped for unauthenticated principals, so this rule can never match anonymous requests.',\n );\n }\n }\n}\n"],"mappings":";AAgBA,SAAgB,EACd,GACM,CAgBR"}
package/dist/builder.cjs DELETED
@@ -1,2 +0,0 @@
1
- function e(e,t,n,r,i){return r.map(r=>({action:r,effect:e,...i?.priority===void 0?{}:{priority:i.priority},resource:n,role:t,...i?.when===void 0?{}:{when:i.when}}))}function t(t,n,r,i){return e(`allow`,t,n,r,i)}function n(t,n,r,i){return e(`deny`,t,n,r,i)}var r={and(...e){return t=>e.every(e=>e(t))},not(e){return t=>!e(t)},or(...e){return t=>e.some(e=>e(t))},owns(e){return({data:t,principal:n})=>{if(!t||typeof t!=`object`)return!1;let r=t;return Object.hasOwn(r,e)?r[e]===n.id:!1}}};function i(e){return r.owns(e)}exports.allow=t,exports.deny=n,exports.owns=i,exports.predicate=r,exports.ruleFor=e;
2
- //# sourceMappingURL=builder.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"builder.cjs","names":[],"sources":["../src/builder.ts"],"sourcesContent":["import type { WILDCARD } from './constants';\nimport type { WardPredicate, WardRule } from './types';\n\n// ---------------------------------------------------------------------------\n// RuleOptions — shared options for allow/deny/ruleFor\n// ---------------------------------------------------------------------------\n\ntype RuleOptions<TData> = {\n priority?: number;\n when?: WardPredicate<TData>;\n};\n\n// ---------------------------------------------------------------------------\n// ruleFor — multi-action rule factory (low-level, effect as first arg)\n// ---------------------------------------------------------------------------\n\n/**\n * Creates one `WardRule` per action for a given effect, role(s), and resource.\n *\n * Prefer `allow()` or `deny()` for ergonomic rule authoring.\n *\n * @example\n * ```ts\n * ruleFor('allow', 'viewer', 'posts', ['read'])\n * ruleFor('deny', 'blocked', 'posts', ['read', 'update'])\n * ```\n */\nexport function ruleFor<TAction extends string = string, TData = unknown>(\n effect: 'allow' | 'deny',\n role: string | readonly string[],\n resource: string | typeof WILDCARD,\n actions: readonly (TAction | NoInfer<typeof WILDCARD>)[],\n options?: RuleOptions<TData>,\n): WardRule<TAction, TData>[] {\n return actions.map((action) => ({\n action,\n effect,\n ...(options?.priority !== undefined ? { priority: options.priority } : {}),\n resource,\n role,\n ...(options?.when !== undefined ? { when: options.when } : {}),\n }));\n}\n\n// ---------------------------------------------------------------------------\n// allow / deny — ergonomic factories (R12)\n// ---------------------------------------------------------------------------\n\n/**\n * Creates one `WardRule` per action with `effect: 'allow'`.\n *\n * Reads naturally: \"allow editor to read/update posts\".\n *\n * @example\n * ```ts\n * allow('editor', 'posts', ['read', 'update'])\n * allow(['editor', 'admin'], 'posts:*', ['read', 'update'], { when: predicate.owns('authorId') })\n * ```\n */\nexport function allow<TAction extends string = string, TData = unknown>(\n role: string | readonly string[],\n resource: string | typeof WILDCARD,\n actions: readonly (TAction | NoInfer<typeof WILDCARD>)[],\n options?: RuleOptions<TData>,\n): WardRule<TAction, TData>[] {\n return ruleFor('allow', role, resource, actions, options);\n}\n\n/**\n * Creates one `WardRule` per action with `effect: 'deny'`.\n *\n * Reads naturally: \"deny blocked from reading posts\".\n *\n * @example\n * ```ts\n * deny('blocked', 'posts', ['read', 'update'])\n * deny('guest', WILDCARD, [WILDCARD], { priority: 10 })\n * ```\n */\nexport function deny<TAction extends string = string, TData = unknown>(\n role: string | readonly string[],\n resource: string | typeof WILDCARD,\n actions: readonly (TAction | NoInfer<typeof WILDCARD>)[],\n options?: RuleOptions<TData>,\n): WardRule<TAction, TData>[] {\n return ruleFor('deny', role, resource, actions, options);\n}\n\n// ---------------------------------------------------------------------------\n// predicate — grouped predicate helpers (R10)\n// ---------------------------------------------------------------------------\n\n/**\n * Grouped predicate factories. Import as a namespace to avoid name collisions:\n * ```ts\n * import { predicate } from '@vielzeug/ward';\n * allow('editor', 'posts:*', ['update'], { when: predicate.owns('authorId') })\n * allow('user', 'posts:*', ['read'], { when: predicate.and(predicate.owns('authorId'), myPred) })\n * ```\n */\nexport const predicate = {\n /**\n * Returns a `WardPredicate` that combines all given predicates with AND semantics —\n * all must return `true` for the rule to match.\n */\n and<TData = unknown>(...preds: WardPredicate<TData>[]): WardPredicate<TData> {\n return (ctx) => preds.every((p) => p(ctx));\n },\n\n /**\n * Returns a `WardPredicate` that inverts the given predicate.\n */\n not<TData = unknown>(pred: WardPredicate<TData>): WardPredicate<TData> {\n return (ctx) => !pred(ctx);\n },\n\n /**\n * Returns a `WardPredicate` that combines all given predicates with OR semantics —\n * at least one must return `true` for the rule to match.\n */\n or<TData = unknown>(...preds: WardPredicate<TData>[]): WardPredicate<TData> {\n return (ctx) => preds.some((p) => p(ctx));\n },\n\n /**\n * Returns a `WardPredicate` that checks whether the data object's `attributeKey` field\n * matches the principal's `id`. Use to express ownership constraints.\n *\n * Must be used with a rule that requires authentication (non-`ANONYMOUS` role).\n * Predicates only execute for authenticated principals — pairing `owns` with an\n * `ANONYMOUS`-role rule produces a rule that can never match because the predicate\n * is skipped for unauthenticated requests.\n *\n * @example\n * ```ts\n * allow('editor', 'posts:*', ['update'], { when: predicate.owns('authorId') })\n * ```\n */\n owns<TData = unknown>(\n attributeKey: [keyof TData] extends [never] ? string : keyof TData & string,\n ): WardPredicate<TData> {\n return ({ data, principal }) => {\n if (!data || typeof data !== 'object') return false;\n\n const record = data as Record<string, unknown>;\n\n if (!Object.hasOwn(record, attributeKey)) return false;\n\n return record[attributeKey] === principal.id;\n };\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// owns — top-level re-export for backward-compatible usage\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a `WardPredicate` that checks whether the data object's `attributeKey` field\n * matches the principal's `id`.\n *\n * Also available as `predicate.owns()` when using the grouped namespace.\n */\nexport function owns<TData = unknown>(\n attributeKey: [keyof TData] extends [never] ? string : keyof TData & string,\n): WardPredicate<TData> {\n return predicate.owns(attributeKey);\n}\n"],"mappings":"AA2BA,SAAgB,EACd,EACA,EACA,EACA,EACA,EAC4B,CAC5B,OAAO,EAAQ,IAAK,IAAY,CAC9B,SACA,SACA,GAAI,GAAS,WAAa,IAAA,GAA6C,CAAC,EAAlC,CAAE,SAAU,EAAQ,QAAS,EACnE,WACA,OACA,GAAI,GAAS,OAAS,IAAA,GAAqC,CAAC,EAA1B,CAAE,KAAM,EAAQ,IAAK,CACzD,EAAE,CACJ,CAiBA,SAAgB,EACd,EACA,EACA,EACA,EAC4B,CAC5B,OAAO,EAAQ,QAAS,EAAM,EAAU,EAAS,CAAO,CAC1D,CAaA,SAAgB,EACd,EACA,EACA,EACA,EAC4B,CAC5B,OAAO,EAAQ,OAAQ,EAAM,EAAU,EAAS,CAAO,CACzD,CAcA,IAAa,EAAY,CAKvB,IAAqB,GAAG,EAAqD,CAC3E,MAAQ,IAAQ,EAAM,MAAO,GAAM,EAAE,CAAG,CAAC,CAC3C,EAKA,IAAqB,EAAkD,CACrE,MAAQ,IAAQ,CAAC,EAAK,CAAG,CAC3B,EAMA,GAAoB,GAAG,EAAqD,CAC1E,MAAQ,IAAQ,EAAM,KAAM,GAAM,EAAE,CAAG,CAAC,CAC1C,EAgBA,KACE,EACsB,CACtB,OAAQ,CAAE,OAAM,eAAgB,CAC9B,GAAI,CAAC,GAAQ,OAAO,GAAS,SAAU,MAAO,GAE9C,IAAM,EAAS,EAIf,OAFK,OAAO,OAAO,EAAQ,CAAY,EAEhC,EAAO,KAAkB,EAAU,GAFO,EAGnD,CACF,CACF,EAYA,SAAgB,EACd,EACsB,CACtB,OAAO,EAAU,KAAK,CAAY,CACpC"}
package/dist/builder.d.ts DELETED
@@ -1,90 +0,0 @@
1
- import type { WILDCARD } from './constants';
2
- import type { WardPredicate, WardRule } from './types';
3
- type RuleOptions<TData> = {
4
- priority?: number;
5
- when?: WardPredicate<TData>;
6
- };
7
- /**
8
- * Creates one `WardRule` per action for a given effect, role(s), and resource.
9
- *
10
- * Prefer `allow()` or `deny()` for ergonomic rule authoring.
11
- *
12
- * @example
13
- * ```ts
14
- * ruleFor('allow', 'viewer', 'posts', ['read'])
15
- * ruleFor('deny', 'blocked', 'posts', ['read', 'update'])
16
- * ```
17
- */
18
- export declare function ruleFor<TAction extends string = string, TData = unknown>(effect: 'allow' | 'deny', role: string | readonly string[], resource: string | typeof WILDCARD, actions: readonly (TAction | NoInfer<typeof WILDCARD>)[], options?: RuleOptions<TData>): WardRule<TAction, TData>[];
19
- /**
20
- * Creates one `WardRule` per action with `effect: 'allow'`.
21
- *
22
- * Reads naturally: "allow editor to read/update posts".
23
- *
24
- * @example
25
- * ```ts
26
- * allow('editor', 'posts', ['read', 'update'])
27
- * allow(['editor', 'admin'], 'posts:*', ['read', 'update'], { when: predicate.owns('authorId') })
28
- * ```
29
- */
30
- export declare function allow<TAction extends string = string, TData = unknown>(role: string | readonly string[], resource: string | typeof WILDCARD, actions: readonly (TAction | NoInfer<typeof WILDCARD>)[], options?: RuleOptions<TData>): WardRule<TAction, TData>[];
31
- /**
32
- * Creates one `WardRule` per action with `effect: 'deny'`.
33
- *
34
- * Reads naturally: "deny blocked from reading posts".
35
- *
36
- * @example
37
- * ```ts
38
- * deny('blocked', 'posts', ['read', 'update'])
39
- * deny('guest', WILDCARD, [WILDCARD], { priority: 10 })
40
- * ```
41
- */
42
- export declare function deny<TAction extends string = string, TData = unknown>(role: string | readonly string[], resource: string | typeof WILDCARD, actions: readonly (TAction | NoInfer<typeof WILDCARD>)[], options?: RuleOptions<TData>): WardRule<TAction, TData>[];
43
- /**
44
- * Grouped predicate factories. Import as a namespace to avoid name collisions:
45
- * ```ts
46
- * import { predicate } from '@vielzeug/ward';
47
- * allow('editor', 'posts:*', ['update'], { when: predicate.owns('authorId') })
48
- * allow('user', 'posts:*', ['read'], { when: predicate.and(predicate.owns('authorId'), myPred) })
49
- * ```
50
- */
51
- export declare const predicate: {
52
- /**
53
- * Returns a `WardPredicate` that combines all given predicates with AND semantics —
54
- * all must return `true` for the rule to match.
55
- */
56
- readonly and: <TData = unknown>(...preds: WardPredicate<TData>[]) => WardPredicate<TData>;
57
- /**
58
- * Returns a `WardPredicate` that inverts the given predicate.
59
- */
60
- readonly not: <TData = unknown>(pred: WardPredicate<TData>) => WardPredicate<TData>;
61
- /**
62
- * Returns a `WardPredicate` that combines all given predicates with OR semantics —
63
- * at least one must return `true` for the rule to match.
64
- */
65
- readonly or: <TData = unknown>(...preds: WardPredicate<TData>[]) => WardPredicate<TData>;
66
- /**
67
- * Returns a `WardPredicate` that checks whether the data object's `attributeKey` field
68
- * matches the principal's `id`. Use to express ownership constraints.
69
- *
70
- * Must be used with a rule that requires authentication (non-`ANONYMOUS` role).
71
- * Predicates only execute for authenticated principals — pairing `owns` with an
72
- * `ANONYMOUS`-role rule produces a rule that can never match because the predicate
73
- * is skipped for unauthenticated requests.
74
- *
75
- * @example
76
- * ```ts
77
- * allow('editor', 'posts:*', ['update'], { when: predicate.owns('authorId') })
78
- * ```
79
- */
80
- readonly owns: <TData = unknown>(attributeKey: [keyof TData] extends [never] ? string : keyof TData & string) => WardPredicate<TData>;
81
- };
82
- /**
83
- * Returns a `WardPredicate` that checks whether the data object's `attributeKey` field
84
- * matches the principal's `id`.
85
- *
86
- * Also available as `predicate.owns()` when using the grouped namespace.
87
- */
88
- export declare function owns<TData = unknown>(attributeKey: [keyof TData] extends [never] ? string : keyof TData & string): WardPredicate<TData>;
89
- export {};
90
- //# sourceMappingURL=builder.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAMvD,KAAK,WAAW,CAAC,KAAK,IAAI;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;CAC7B,CAAC;AAMF;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,GAAG,OAAO,EACtE,MAAM,EAAE,OAAO,GAAG,MAAM,EACxB,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EAChC,QAAQ,EAAE,MAAM,GAAG,OAAO,QAAQ,EAClC,OAAO,EAAE,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,QAAQ,CAAC,CAAC,EAAE,EACxD,OAAO,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,GAC3B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAS5B;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,GAAG,OAAO,EACpE,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EAChC,QAAQ,EAAE,MAAM,GAAG,OAAO,QAAQ,EAClC,OAAO,EAAE,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,QAAQ,CAAC,CAAC,EAAE,EACxD,OAAO,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,GAC3B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAE5B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,GAAG,OAAO,EACnE,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EAChC,QAAQ,EAAE,MAAM,GAAG,OAAO,QAAQ,EAClC,OAAO,EAAE,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,QAAQ,CAAC,CAAC,EAAE,EACxD,OAAO,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,GAC3B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAE5B;AAMD;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS;IACpB;;;OAGG;mBACC,KAAK,sBAAsB,aAAa,CAAC,KAAK,CAAC,EAAE,KAAG,aAAa,CAAC,KAAK,CAAC;IAI5E;;OAEG;mBACC,KAAK,kBAAkB,aAAa,CAAC,KAAK,CAAC,KAAG,aAAa,CAAC,KAAK,CAAC;IAItE;;;OAGG;kBACA,KAAK,sBAAsB,aAAa,CAAC,KAAK,CAAC,EAAE,KAAG,aAAa,CAAC,KAAK,CAAC;IAI3E;;;;;;;;;;;;;OAaG;oBACE,KAAK,0BACM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,MAAM,GAAG,MAAM,KAAK,GAAG,MAAM,KAC1E,aAAa,CAAC,KAAK,CAAC;CAWf,CAAC;AAMX;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,KAAK,GAAG,OAAO,EAClC,YAAY,EAAE,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,MAAM,GAAG,MAAM,KAAK,GAAG,MAAM,GAC1E,aAAa,CAAC,KAAK,CAAC,CAEtB"}
package/dist/builder.js DELETED
@@ -1,42 +0,0 @@
1
- //#region src/builder.ts
2
- function e(e, t, n, r, i) {
3
- return r.map((r) => ({
4
- action: r,
5
- effect: e,
6
- ...i?.priority === void 0 ? {} : { priority: i.priority },
7
- resource: n,
8
- role: t,
9
- ...i?.when === void 0 ? {} : { when: i.when }
10
- }));
11
- }
12
- function t(t, n, r, i) {
13
- return e("allow", t, n, r, i);
14
- }
15
- function n(t, n, r, i) {
16
- return e("deny", t, n, r, i);
17
- }
18
- var r = {
19
- and(...e) {
20
- return (t) => e.every((e) => e(t));
21
- },
22
- not(e) {
23
- return (t) => !e(t);
24
- },
25
- or(...e) {
26
- return (t) => e.some((e) => e(t));
27
- },
28
- owns(e) {
29
- return ({ data: t, principal: n }) => {
30
- if (!t || typeof t != "object") return !1;
31
- let r = t;
32
- return Object.hasOwn(r, e) ? r[e] === n.id : !1;
33
- };
34
- }
35
- };
36
- function i(e) {
37
- return r.owns(e);
38
- }
39
- //#endregion
40
- export { t as allow, n as deny, i as owns, r as predicate, e as ruleFor };
41
-
42
- //# sourceMappingURL=builder.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"builder.js","names":[],"sources":["../src/builder.ts"],"sourcesContent":["import type { WILDCARD } from './constants';\nimport type { WardPredicate, WardRule } from './types';\n\n// ---------------------------------------------------------------------------\n// RuleOptions — shared options for allow/deny/ruleFor\n// ---------------------------------------------------------------------------\n\ntype RuleOptions<TData> = {\n priority?: number;\n when?: WardPredicate<TData>;\n};\n\n// ---------------------------------------------------------------------------\n// ruleFor — multi-action rule factory (low-level, effect as first arg)\n// ---------------------------------------------------------------------------\n\n/**\n * Creates one `WardRule` per action for a given effect, role(s), and resource.\n *\n * Prefer `allow()` or `deny()` for ergonomic rule authoring.\n *\n * @example\n * ```ts\n * ruleFor('allow', 'viewer', 'posts', ['read'])\n * ruleFor('deny', 'blocked', 'posts', ['read', 'update'])\n * ```\n */\nexport function ruleFor<TAction extends string = string, TData = unknown>(\n effect: 'allow' | 'deny',\n role: string | readonly string[],\n resource: string | typeof WILDCARD,\n actions: readonly (TAction | NoInfer<typeof WILDCARD>)[],\n options?: RuleOptions<TData>,\n): WardRule<TAction, TData>[] {\n return actions.map((action) => ({\n action,\n effect,\n ...(options?.priority !== undefined ? { priority: options.priority } : {}),\n resource,\n role,\n ...(options?.when !== undefined ? { when: options.when } : {}),\n }));\n}\n\n// ---------------------------------------------------------------------------\n// allow / deny — ergonomic factories (R12)\n// ---------------------------------------------------------------------------\n\n/**\n * Creates one `WardRule` per action with `effect: 'allow'`.\n *\n * Reads naturally: \"allow editor to read/update posts\".\n *\n * @example\n * ```ts\n * allow('editor', 'posts', ['read', 'update'])\n * allow(['editor', 'admin'], 'posts:*', ['read', 'update'], { when: predicate.owns('authorId') })\n * ```\n */\nexport function allow<TAction extends string = string, TData = unknown>(\n role: string | readonly string[],\n resource: string | typeof WILDCARD,\n actions: readonly (TAction | NoInfer<typeof WILDCARD>)[],\n options?: RuleOptions<TData>,\n): WardRule<TAction, TData>[] {\n return ruleFor('allow', role, resource, actions, options);\n}\n\n/**\n * Creates one `WardRule` per action with `effect: 'deny'`.\n *\n * Reads naturally: \"deny blocked from reading posts\".\n *\n * @example\n * ```ts\n * deny('blocked', 'posts', ['read', 'update'])\n * deny('guest', WILDCARD, [WILDCARD], { priority: 10 })\n * ```\n */\nexport function deny<TAction extends string = string, TData = unknown>(\n role: string | readonly string[],\n resource: string | typeof WILDCARD,\n actions: readonly (TAction | NoInfer<typeof WILDCARD>)[],\n options?: RuleOptions<TData>,\n): WardRule<TAction, TData>[] {\n return ruleFor('deny', role, resource, actions, options);\n}\n\n// ---------------------------------------------------------------------------\n// predicate — grouped predicate helpers (R10)\n// ---------------------------------------------------------------------------\n\n/**\n * Grouped predicate factories. Import as a namespace to avoid name collisions:\n * ```ts\n * import { predicate } from '@vielzeug/ward';\n * allow('editor', 'posts:*', ['update'], { when: predicate.owns('authorId') })\n * allow('user', 'posts:*', ['read'], { when: predicate.and(predicate.owns('authorId'), myPred) })\n * ```\n */\nexport const predicate = {\n /**\n * Returns a `WardPredicate` that combines all given predicates with AND semantics —\n * all must return `true` for the rule to match.\n */\n and<TData = unknown>(...preds: WardPredicate<TData>[]): WardPredicate<TData> {\n return (ctx) => preds.every((p) => p(ctx));\n },\n\n /**\n * Returns a `WardPredicate` that inverts the given predicate.\n */\n not<TData = unknown>(pred: WardPredicate<TData>): WardPredicate<TData> {\n return (ctx) => !pred(ctx);\n },\n\n /**\n * Returns a `WardPredicate` that combines all given predicates with OR semantics —\n * at least one must return `true` for the rule to match.\n */\n or<TData = unknown>(...preds: WardPredicate<TData>[]): WardPredicate<TData> {\n return (ctx) => preds.some((p) => p(ctx));\n },\n\n /**\n * Returns a `WardPredicate` that checks whether the data object's `attributeKey` field\n * matches the principal's `id`. Use to express ownership constraints.\n *\n * Must be used with a rule that requires authentication (non-`ANONYMOUS` role).\n * Predicates only execute for authenticated principals — pairing `owns` with an\n * `ANONYMOUS`-role rule produces a rule that can never match because the predicate\n * is skipped for unauthenticated requests.\n *\n * @example\n * ```ts\n * allow('editor', 'posts:*', ['update'], { when: predicate.owns('authorId') })\n * ```\n */\n owns<TData = unknown>(\n attributeKey: [keyof TData] extends [never] ? string : keyof TData & string,\n ): WardPredicate<TData> {\n return ({ data, principal }) => {\n if (!data || typeof data !== 'object') return false;\n\n const record = data as Record<string, unknown>;\n\n if (!Object.hasOwn(record, attributeKey)) return false;\n\n return record[attributeKey] === principal.id;\n };\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// owns — top-level re-export for backward-compatible usage\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a `WardPredicate` that checks whether the data object's `attributeKey` field\n * matches the principal's `id`.\n *\n * Also available as `predicate.owns()` when using the grouped namespace.\n */\nexport function owns<TData = unknown>(\n attributeKey: [keyof TData] extends [never] ? string : keyof TData & string,\n): WardPredicate<TData> {\n return predicate.owns(attributeKey);\n}\n"],"mappings":";AA2BA,SAAgB,EACd,GACA,GACA,GACA,GACA,GAC4B;CAC5B,OAAO,EAAQ,KAAK,OAAY;EAC9B;EACA;EACA,GAAI,GAAS,aAAa,KAAA,IAA6C,CAAC,IAAlC,EAAE,UAAU,EAAQ,SAAS;EACnE;EACA;EACA,GAAI,GAAS,SAAS,KAAA,IAAqC,CAAC,IAA1B,EAAE,MAAM,EAAQ,KAAK;CACzD,EAAE;AACJ;AAiBA,SAAgB,EACd,GACA,GACA,GACA,GAC4B;CAC5B,OAAO,EAAQ,SAAS,GAAM,GAAU,GAAS,CAAO;AAC1D;AAaA,SAAgB,EACd,GACA,GACA,GACA,GAC4B;CAC5B,OAAO,EAAQ,QAAQ,GAAM,GAAU,GAAS,CAAO;AACzD;AAcA,IAAa,IAAY;CAKvB,IAAqB,GAAG,GAAqD;EAC3E,QAAQ,MAAQ,EAAM,OAAO,MAAM,EAAE,CAAG,CAAC;CAC3C;CAKA,IAAqB,GAAkD;EACrE,QAAQ,MAAQ,CAAC,EAAK,CAAG;CAC3B;CAMA,GAAoB,GAAG,GAAqD;EAC1E,QAAQ,MAAQ,EAAM,MAAM,MAAM,EAAE,CAAG,CAAC;CAC1C;CAgBA,KACE,GACsB;EACtB,QAAQ,EAAE,SAAM,mBAAgB;GAC9B,IAAI,CAAC,KAAQ,OAAO,KAAS,UAAU,OAAO;GAE9C,IAAM,IAAS;GAIf,OAFK,OAAO,OAAO,GAAQ,CAAY,IAEhC,EAAO,OAAkB,EAAU,KAFO;EAGnD;CACF;AACF;AAYA,SAAgB,EACd,GACsB;CACtB,OAAO,EAAU,KAAK,CAAY;AACpC"}