@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.
- package/README.md +32 -37
- package/dist/_compile.cjs +1 -1
- package/dist/_compile.cjs.map +1 -1
- package/dist/_compile.d.ts +5 -33
- package/dist/_compile.d.ts.map +1 -1
- package/dist/_compile.js +40 -35
- package/dist/_compile.js.map +1 -1
- package/dist/_match.cjs +1 -1
- package/dist/_match.cjs.map +1 -1
- package/dist/_match.d.ts +9 -19
- package/dist/_match.d.ts.map +1 -1
- package/dist/_match.js +40 -37
- package/dist/_match.js.map +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +3 -16
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -3
- package/dist/errors.js.map +1 -1
- package/dist/factory.cjs +1 -1
- package/dist/factory.cjs.map +1 -1
- package/dist/factory.d.ts +14 -10
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +70 -119
- package/dist/factory.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/rules.cjs +2 -0
- package/dist/rules.cjs.map +1 -0
- package/dist/rules.d.ts +15 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +26 -0
- package/dist/rules.js.map +1 -0
- package/dist/types.d.ts +63 -133
- package/dist/types.d.ts.map +1 -1
- package/dist/ward.cjs +1 -1
- package/dist/ward.cjs.map +1 -1
- package/dist/ward.iife.js +1 -1
- package/dist/ward.iife.js.map +1 -1
- package/dist/ward.js +1 -1
- package/dist/ward.js.map +1 -1
- package/package.json +7 -7
- package/dist/_conflict.cjs +0 -2
- package/dist/_conflict.cjs.map +0 -1
- package/dist/_conflict.d.ts +0 -10
- package/dist/_conflict.d.ts.map +0 -1
- package/dist/_conflict.js +0 -51
- package/dist/_conflict.js.map +0 -1
- package/dist/_dev.cjs +0 -2
- package/dist/_dev.cjs.map +0 -1
- package/dist/_dev.d.ts +0 -8
- package/dist/_dev.d.ts.map +0 -1
- package/dist/_dev.js +0 -6
- package/dist/_dev.js.map +0 -1
- package/dist/builder.cjs +0 -2
- package/dist/builder.cjs.map +0 -1
- package/dist/builder.d.ts +0 -90
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js +0 -42
- 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
|
package/dist/_conflict.js.map
DELETED
|
@@ -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
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
|
package/dist/_dev.d.ts.map
DELETED
|
@@ -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
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
|
package/dist/builder.cjs.map
DELETED
|
@@ -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
|
package/dist/builder.d.ts.map
DELETED
|
@@ -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
|
package/dist/builder.js.map
DELETED
|
@@ -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"}
|