@zap-studio/permit 0.1.3 → 0.2.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/CHANGELOG.md +17 -0
- package/README.md +3 -3
- package/dist/errors.mjs +14 -2
- package/dist/errors.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +15 -14
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.mts +134 -2
- package/dist/types.d.mts.map +1 -0
- package/package.json +5 -5
- package/dist/errors-CgD70cJR.mjs +0 -15
- package/dist/errors-CgD70cJR.mjs.map +0 -1
- package/dist/types-BgG1Aq6K.d.mts +0 -134
- package/dist/types-BgG1Aq6K.d.mts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @zap-studio/permit
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f0f503e: Make policy evaluation async by default.
|
|
8
|
+
|
|
9
|
+
`policy.can(...)` now returns a `Promise<boolean>` and must be awaited.
|
|
10
|
+
|
|
11
|
+
`createPolicy` now uses async-safe Standard Schema validation internally, so resource schemas with async validation are supported.
|
|
12
|
+
|
|
13
|
+
`mergePolicies` and `mergePoliciesAny` are also async through the shared `Policy` interface.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [f75b984]
|
|
18
|
+
- @zap-studio/validation@0.3.0
|
|
19
|
+
|
|
3
20
|
## 0.1.3
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -72,9 +72,9 @@ const policy = createPolicy<AppContext>({
|
|
|
72
72
|
const ctx: AppContext = { user: { id: "user-1", role: "user" } };
|
|
73
73
|
const post = { id: "1", authorId: "user-1", visibility: "public" as const };
|
|
74
74
|
|
|
75
|
-
policy.can(ctx, "read", "post", post); // true
|
|
76
|
-
policy.can(ctx, "write", "post", post); // true (user is author)
|
|
77
|
-
policy.can(ctx, "delete", "post", post); // false (always denied)
|
|
75
|
+
await policy.can(ctx, "read", "post", post); // true
|
|
76
|
+
await policy.can(ctx, "write", "post", post); // true (user is author)
|
|
77
|
+
await policy.can(ctx, "delete", "post", post); // false (always denied)
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
## API Reference
|
package/dist/errors.mjs
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Represents an error that occurs during policy evaluation or enforcement.
|
|
4
|
+
* Use this error to indicate issues related to policy logic, configuration, or execution.
|
|
5
|
+
*/
|
|
6
|
+
var PolicyError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "PolicyError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
2
12
|
|
|
3
|
-
|
|
13
|
+
//#endregion
|
|
14
|
+
export { PolicyError };
|
|
15
|
+
//# sourceMappingURL=errors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Represents an error that occurs during policy evaluation or enforcement.\n * Use this error to indicate issues related to policy logic, configuration, or execution.\n */\nexport class PolicyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PolicyError\";\n }\n}\n"],"mappings":";;;;;AAIA,IAAa,cAAb,cAAiC,MAAM;CACrC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Actions, ConditionFn, Context, PermitConfig, Policy, PolicyFn, Resources, Role, RoleHierarchy } from "./types.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/index.d.ts
|
|
4
4
|
|
|
@@ -219,8 +219,8 @@ declare function hasRole<TContext extends Context & {
|
|
|
219
219
|
*
|
|
220
220
|
* // Check permissions
|
|
221
221
|
* const post = { id: "1", authorId: "user-1", visibility: "public" as const };
|
|
222
|
-
* policy.can(ctx, "read", "post", post); // true
|
|
223
|
-
* policy.can(ctx, "write", "post", post); // depends on ctx.user.id
|
|
222
|
+
* await policy.can(ctx, "read", "post", post); // true
|
|
223
|
+
* await policy.can(ctx, "write", "post", post); // depends on ctx.user.id
|
|
224
224
|
* ```
|
|
225
225
|
*/
|
|
226
226
|
declare function createPolicy<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(config: PermitConfig<TContext, TResources, TActions>): Policy<TContext, TResources, TActions>;
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;AAiCA;;;;;;;AAwBA;;;;;;;AAwBgB,iBAhDA,KAgDA,CACG,iBAhDA,OAgDA,EAIM,gBAAA,MAAA,GAAA,MAAA,EAAU,YAAA,OAAA,CAAS,CAAA,CAAA,EAjDvC,QAiDuC,CAjD9B,QAiD8B,EAjDpB,OAiDoB,EAjDX,SAiDW,CAAA;;;;;;;AAuB5C;;;;;;;;;;AAMG,iBA1Da,IA0Db,CAsBH,iBA/EmB,OA+EH,EACG,gBAAA,MAAA,GAAA,MAAA,EAIU,YAAA,OAAA,CAAU,CAAA,CAAA,EAjFlC,QAiFkC,CAjFzB,QAiFyB,EAjFf,OAiFe,EAjFN,SAiFM,CAAA;;;;;;;;AAoBvC;;;;;;;;;AAMG,iBAvFa,IAuFb,CAAA,iBAtFgB,OAsFhB,EAgBH,gBAAgB,MAAA,GAAA,MAAA,EAAqB,YAAA,OAAA,CAAyB,CAAA,SAAA,EAlGjD,WAkGiD,CAlGrC,QAkGqC,EAlG3B,OAkG2B,EAlGlB,SAkGkB,CAAA,CAAA,EAjG3D,QAiG2D,CAjGlD,QAiGkD,EAjGxC,OAiGwC,EAjG/B,SAiG+B,CAAA;;;;;;;AAwB9D;;;;;;;;;AA6CA;;AACqC,iBAjJrB,GAiJqB,CAAO,iBAhJzB,OAgJyB,EAGpC,gBAAA,MAAA,GAAA,MAAA,EAAmB,YAAA,OAAA,CAAU,CAAA,GAAA,UAAA,EA/IpB,WA+IoB,CA/IR,QA+IQ,EA/IE,OA+IF,EA/IW,SA+IX,CAAA,EAAA,CAAA,EA9IlC,WA8IkC,CA9ItB,QA8IsB,EA9IZ,OA8IY,EA9IH,SA8IG,CAAA;;;;AAErC;;;;;;;;;;;;;;AAuFgB,iBAjNA,EAiNA,CACG,iBAjNA,OAiNA,EACE,gBAAA,MAAA,GAAA,MAAA,EAAY,YAAA,OAAA,CACN,CAAA,GAAA,UAAA,EA/MV,WA+MU,CA/ME,QA+MF,EA/MY,OA+MZ,EA/MqB,SA+MrB,CAAA,EAAA,CAAA,EA9MxB,WA8MwB,CA9MZ,QA8MY,EA9MF,OA8ME,EA9MO,SA8MP,CAAA;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { PolicyError } from "./errors.mjs";
|
|
2
|
+
import { createStandardValidator } from "@zap-studio/validation";
|
|
3
3
|
|
|
4
4
|
//#region src/index.ts
|
|
5
5
|
/**
|
|
@@ -217,18 +217,18 @@ function hasRole(role, hierarchy) {
|
|
|
217
217
|
*
|
|
218
218
|
* // Check permissions
|
|
219
219
|
* const post = { id: "1", authorId: "user-1", visibility: "public" as const };
|
|
220
|
-
* policy.can(ctx, "read", "post", post); // true
|
|
221
|
-
* policy.can(ctx, "write", "post", post); // depends on ctx.user.id
|
|
220
|
+
* await policy.can(ctx, "read", "post", post); // true
|
|
221
|
+
* await policy.can(ctx, "write", "post", post); // depends on ctx.user.id
|
|
222
222
|
* ```
|
|
223
223
|
*/
|
|
224
224
|
function createPolicy(config) {
|
|
225
225
|
const { rules, resources, actions } = config;
|
|
226
226
|
const validators = /* @__PURE__ */ new Map();
|
|
227
|
-
const getValidatedResource = (resourceType, resource) => {
|
|
227
|
+
const getValidatedResource = async (resourceType, resource) => {
|
|
228
228
|
const validator = validators.get(resourceType);
|
|
229
229
|
if (!validator) return null;
|
|
230
230
|
try {
|
|
231
|
-
const result = validator(resource);
|
|
231
|
+
const result = await validator(resource);
|
|
232
232
|
if (result.issues) return null;
|
|
233
233
|
return result.value;
|
|
234
234
|
} catch (error) {
|
|
@@ -239,14 +239,14 @@ function createPolicy(config) {
|
|
|
239
239
|
for (const key of Object.keys(resources)) {
|
|
240
240
|
const schema = resources[key];
|
|
241
241
|
if (!schema) throw new PolicyError(`Missing schema for resource: ${String(key)}`);
|
|
242
|
-
const validator =
|
|
243
|
-
validators.set(key, (input) => validator(input));
|
|
242
|
+
const validator = createStandardValidator(schema);
|
|
243
|
+
validators.set(key, async (input) => validator(input));
|
|
244
244
|
}
|
|
245
|
-
return { can(context, action, resourceType, resource) {
|
|
245
|
+
return { async can(context, action, resourceType, resource) {
|
|
246
246
|
const allowedActions = actions[resourceType];
|
|
247
247
|
if (!allowedActions) return false;
|
|
248
248
|
if (!allowedActions.includes(action)) return false;
|
|
249
|
-
const validatedResource = getValidatedResource(resourceType, resource);
|
|
249
|
+
const validatedResource = await getValidatedResource(resourceType, resource);
|
|
250
250
|
if (!validatedResource) return false;
|
|
251
251
|
const resourceRules = rules[resourceType];
|
|
252
252
|
if (!resourceRules) return false;
|
|
@@ -274,9 +274,9 @@ function createPolicy(config) {
|
|
|
274
274
|
* ```
|
|
275
275
|
*/
|
|
276
276
|
function mergePolicies(...policies) {
|
|
277
|
-
return { can(context, action, resourceType, resource) {
|
|
277
|
+
return { async can(context, action, resourceType, resource) {
|
|
278
278
|
if (!policies.length) return false;
|
|
279
|
-
for (const policy of policies) if (!policy.can(context, action, resourceType, resource)) return false;
|
|
279
|
+
for (const policy of policies) if (!await policy.can(context, action, resourceType, resource)) return false;
|
|
280
280
|
return true;
|
|
281
281
|
} };
|
|
282
282
|
}
|
|
@@ -294,9 +294,10 @@ function mergePolicies(...policies) {
|
|
|
294
294
|
* ```
|
|
295
295
|
*/
|
|
296
296
|
function mergePoliciesAny(...policies) {
|
|
297
|
-
return { can(context, action, resourceType, resource) {
|
|
297
|
+
return { async can(context, action, resourceType, resource) {
|
|
298
298
|
if (!policies.length) return false;
|
|
299
|
-
|
|
299
|
+
for (const policy of policies) if (await policy.can(context, action, resourceType, resource)) return true;
|
|
300
|
+
return false;
|
|
300
301
|
} };
|
|
301
302
|
}
|
|
302
303
|
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { createSyncStandardValidator } from \"@zap-studio/validation\";\nimport { PolicyError } from \"./errors\";\nimport type {\n Actions,\n ConditionFn,\n Context,\n InferAction,\n InferResource,\n PermitConfig,\n Policy,\n PolicyFn,\n Resources,\n Role,\n RoleHierarchy,\n} from \"./types\";\n\n/**\n * Returns a policy function that always allows the action.\n *\n * @example\n * ```ts\n * const policy = createPolicy({\n * resources,\n * actions,\n * rules: {\n * post: {\n * read: allow(), // Always allow reading posts\n * },\n * },\n * });\n * ```\n */\nexport function allow<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(): PolicyFn<TContext, TAction, TResource> {\n return () => \"allow\";\n}\n\n/**\n * Returns a policy function that always denies the action.\n *\n * @example\n * ```ts\n * const policy = createPolicy({\n * resources,\n * actions,\n * rules: {\n * post: {\n * delete: deny(), // Never allow deleting posts\n * },\n * },\n * });\n * ```\n */\nexport function deny<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(): PolicyFn<TContext, TAction, TResource> {\n return () => \"deny\";\n}\n\n/**\n * Returns a policy function that allows or denies based on a condition.\n *\n * @example\n * ```ts\n * const policy = createPolicy({\n * resources,\n * actions,\n * rules: {\n * post: {\n * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),\n * },\n * },\n * });\n * ```\n */\nexport function when<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(\n condition: ConditionFn<TContext, TAction, TResource>\n): PolicyFn<TContext, TAction, TResource> {\n return (context, action, resource) =>\n condition(context, action, resource) ? \"allow\" : \"deny\";\n}\n\n/**\n * Returns a condition function that returns `true` if all conditions are met.\n *\n * @example\n * ```ts\n * const isOwnerAndPublished = and(\n * (ctx, action, resource) => ctx.user.id === resource.authorId,\n * (ctx, action, resource) => resource.status === \"published\"\n * );\n *\n * rules: {\n * post: {\n * delete: when(isOwnerAndPublished),\n * },\n * }\n * ```\n */\nexport function and<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(\n ...conditions: ConditionFn<TContext, TAction, TResource>[]\n): ConditionFn<TContext, TAction, TResource> {\n return (context, action, resource) =>\n conditions.every((condition) => condition(context, action, resource));\n}\n\n/**\n * Returns a condition function that returns `true` if any condition is met.\n *\n * @example\n * ```ts\n * const isOwnerOrAdmin = or(\n * (ctx, action, resource) => ctx.user.id === resource.authorId,\n * (ctx, action, resource) => ctx.user.role === \"admin\"\n * );\n *\n * rules: {\n * post: {\n * write: when(isOwnerOrAdmin),\n * },\n * }\n * ```\n */\nexport function or<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(\n ...conditions: ConditionFn<TContext, TAction, TResource>[]\n): ConditionFn<TContext, TAction, TResource> {\n return (context, action, resource) =>\n conditions.some((condition) => condition(context, action, resource));\n}\n\n/**\n * Returns a condition function that negates another condition.\n *\n * @example\n * ```ts\n * const isNotOwner = not((ctx, action, resource) => ctx.user.id === resource.authorId);\n *\n * rules: {\n * post: {\n * like: when(isNotOwner), // Can only like posts you don't own\n * },\n * }\n * ```\n */\nexport function not<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(\n condition: ConditionFn<TContext, TAction, TResource>\n): ConditionFn<TContext, TAction, TResource> {\n return (context, action, resource) => !condition(context, action, resource);\n}\n\n/**\n * Returns a condition function that checks if a context property equals a value.\n *\n * @example\n * ```ts\n * rules: {\n * post: {\n * write: when(has(\"role\", \"admin\")), // Only admins can write\n * },\n * }\n * ```\n */\nexport function has<TContext extends Context, K extends keyof TContext>(\n key: K,\n value: TContext[K]\n): ConditionFn<TContext> {\n return (context) => context[key] === value;\n}\n\n/**\n * Collects all roles including inherited ones from a role hierarchy.\n *\n * @example\n * ```ts\n * type Role = \"guest\" | \"user\" | \"admin\";\n *\n * const hierarchy: RoleHierarchy<Role> = {\n * guest: [],\n * user: [\"guest\"],\n * admin: [\"user\"],\n * };\n *\n * collectInheritedRoles([\"admin\"], hierarchy);\n * // Returns: Set { \"admin\", \"user\", \"guest\" }\n * ```\n */\nexport function collectInheritedRoles<TRole extends Role = Role>(\n roles: TRole[],\n hierarchy: RoleHierarchy<TRole>\n): Set<TRole> {\n const inherited = new Set<TRole>();\n\n function add(role: TRole) {\n if (!inherited.has(role)) {\n inherited.add(role);\n const parents = hierarchy[role] ?? [];\n parents.forEach(add); // recursively add parent roles\n }\n }\n\n roles.forEach(add);\n return inherited;\n}\n\n/**\n * Returns a condition function that checks if the user has a specific role.\n * Supports role hierarchy for inherited permissions.\n *\n * @example\n * ```ts\n * // Without hierarchy\n * rules: {\n * post: {\n * delete: when(hasRole(\"admin\")),\n * },\n * }\n *\n * // With hierarchy\n * const hierarchy = {\n * guest: [],\n * user: [\"guest\"],\n * admin: [\"user\"],\n * };\n *\n * rules: {\n * post: {\n * read: when(hasRole(\"guest\", hierarchy)), // Admins and users can also read\n * },\n * }\n * ```\n */\nexport function hasRole<\n TContext extends Context & { role: Role | Role[] },\n TAction extends string = string,\n TResource = unknown,\n>(role: Role): ConditionFn<TContext, TAction, TResource>;\n\nexport function hasRole<\n TContext extends Context & { role: TRole | TRole[] },\n TAction extends string = string,\n TResource = unknown,\n TRole extends Role = Role,\n>(\n role: TRole,\n hierarchy: RoleHierarchy<TRole>\n): ConditionFn<TContext, TAction, TResource>;\n\nexport function hasRole<\n TContext extends Context & { role: Role | Role[] },\n TAction extends string = string,\n TResource = unknown,\n>(\n role: Role,\n hierarchy?: RoleHierarchy<Role>\n): ConditionFn<TContext, TAction, TResource> {\n return (context) => {\n const userRoles = Array.isArray(context.role)\n ? context.role\n : [context.role];\n\n if (!hierarchy) {\n return userRoles.includes(role);\n }\n\n const inherited = collectInheritedRoles(userRoles, hierarchy);\n return inherited.has(role);\n };\n}\n\n/**\n * Creates a type-safe policy from resource schemas, actions, and rules.\n *\n * @example\n * ```ts\n * import { z } from \"zod\";\n * import { createPolicy, allow, deny, when } from \"@zap-studio/permit\";\n * import type { Resources, Actions } from \"@zap-studio/permit/types\";\n *\n * // Define resource schemas\n * const resources = {\n * post: z.object({\n * id: z.string(),\n * authorId: z.string(),\n * visibility: z.enum([\"public\", \"private\"]),\n * }),\n * comment: z.object({\n * id: z.string(),\n * postId: z.string(),\n * authorId: z.string(),\n * }),\n * } satisfies Resources;\n *\n * // Define actions per resource\n * const actions = {\n * post: [\"read\", \"write\", \"delete\"],\n * comment: [\"read\", \"write\"],\n * } as const satisfies Actions<typeof resources>;\n *\n * // Define context type\n * type AppContext = { user: { id: string; role: string } };\n *\n * // Create the policy\n * const policy = createPolicy<AppContext>({\n * resources,\n * actions,\n * rules: {\n * post: {\n * read: when((ctx, action, resource) => resource.visibility === \"public\"),\n * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),\n * delete: deny(),\n * },\n * comment: {\n * read: allow(),\n * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),\n * },\n * },\n * });\n *\n * // Check permissions\n * const post = { id: \"1\", authorId: \"user-1\", visibility: \"public\" as const };\n * policy.can(ctx, \"read\", \"post\", post); // true\n * policy.can(ctx, \"write\", \"post\", post); // depends on ctx.user.id\n * ```\n */\nexport function createPolicy<\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n config: PermitConfig<TContext, TResources, TActions>\n): Policy<TContext, TResources, TActions> {\n const { rules, resources, actions } = config;\n const validators = new Map<\n keyof TResources,\n (input: unknown) => StandardSchemaV1.Result<unknown>\n >();\n\n const getValidatedResource = <K extends keyof TResources>(\n resourceType: K,\n resource: InferResource<TResources, K>\n ): InferResource<TResources, K> | null => {\n const validator = validators.get(resourceType);\n if (!validator) {\n return null;\n }\n try {\n const result = validator(resource);\n if (result.issues) {\n return null;\n }\n return result.value as InferResource<TResources, K>;\n } catch (error) {\n console.warn(\n `Resource validation failed for ${String(resourceType)}: ${String(error)}`\n );\n return null;\n }\n };\n\n for (const key of Object.keys(resources) as Array<keyof TResources>) {\n const schema = resources[key];\n if (!schema) {\n throw new PolicyError(`Missing schema for resource: ${String(key)}`);\n }\n const validator = createSyncStandardValidator(schema);\n validators.set(key, (input: unknown) => validator(input));\n }\n\n return {\n can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n action: InferAction<TActions, K>,\n resourceType: K,\n resource: InferResource<TResources, K>\n ): boolean {\n const allowedActions = actions[resourceType];\n if (!allowedActions) {\n return false;\n }\n if (!allowedActions.includes(action)) {\n return false;\n }\n\n const validatedResource = getValidatedResource(resourceType, resource);\n if (!validatedResource) {\n return false;\n }\n\n const resourceRules = rules[resourceType];\n if (!resourceRules) {\n return false;\n }\n\n const policyFn = resourceRules[action];\n\n if (!policyFn) {\n return false;\n }\n\n try {\n return policyFn(context, action, validatedResource) === \"allow\";\n } catch (error) {\n console.warn(\n `Policy evaluation error for ${String(resourceType)}.${String(action)}: ${String(error)}`\n );\n return false;\n }\n },\n };\n}\n\n/**\n * Merges multiple policies into one using \"deny-overrides\" strategy.\n * If any policy denies, the merged policy denies. All must allow for the result to allow.\n *\n * @example\n * ```ts\n * const basePolicy = createPolicy({ ... });\n * const adminPolicy = createPolicy({ ... });\n *\n * const merged = mergePolicies(basePolicy, adminPolicy);\n * // Both policies must allow for the action to be permitted\n * ```\n */\nexport function mergePolicies<\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n ...policies: Policy<TContext, TResources, TActions>[]\n): Policy<TContext, TResources, TActions> {\n return {\n can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n action: InferAction<TActions, K>,\n resourceType: K,\n resource: InferResource<TResources, K>\n ): boolean {\n if (!policies.length) {\n return false;\n }\n for (const policy of policies) {\n if (!policy.can(context, action, resourceType, resource)) {\n return false;\n }\n }\n return true;\n },\n };\n}\n\n/**\n * Merges multiple policies into one using \"allow-overrides\" strategy.\n * If any policy allows, the merged policy allows. All must deny for the result to deny.\n *\n * @example\n * ```ts\n * const guestPolicy = createPolicy({ ... });\n * const memberPolicy = createPolicy({ ... });\n *\n * const merged = mergePoliciesAny(guestPolicy, memberPolicy);\n * // If either policy allows, the action is permitted\n * ```\n */\nexport function mergePoliciesAny<\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n ...policies: Policy<TContext, TResources, TActions>[]\n): Policy<TContext, TResources, TActions> {\n return {\n can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n action: InferAction<TActions, K>,\n resourceType: K,\n resource: InferResource<TResources, K>\n ): boolean {\n if (!policies.length) {\n return false;\n }\n return policies.some((policy) =>\n policy.can(context, action, resourceType, resource)\n );\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { createStandardValidator } from \"@zap-studio/validation\";\nimport { PolicyError } from \"./errors\";\nimport type {\n Actions,\n ConditionFn,\n Context,\n InferAction,\n InferResource,\n PermitConfig,\n Policy,\n PolicyFn,\n Resources,\n Role,\n RoleHierarchy,\n} from \"./types\";\n\n/**\n * Returns a policy function that always allows the action.\n *\n * @example\n * ```ts\n * const policy = createPolicy({\n * resources,\n * actions,\n * rules: {\n * post: {\n * read: allow(), // Always allow reading posts\n * },\n * },\n * });\n * ```\n */\nexport function allow<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(): PolicyFn<TContext, TAction, TResource> {\n return () => \"allow\";\n}\n\n/**\n * Returns a policy function that always denies the action.\n *\n * @example\n * ```ts\n * const policy = createPolicy({\n * resources,\n * actions,\n * rules: {\n * post: {\n * delete: deny(), // Never allow deleting posts\n * },\n * },\n * });\n * ```\n */\nexport function deny<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(): PolicyFn<TContext, TAction, TResource> {\n return () => \"deny\";\n}\n\n/**\n * Returns a policy function that allows or denies based on a condition.\n *\n * @example\n * ```ts\n * const policy = createPolicy({\n * resources,\n * actions,\n * rules: {\n * post: {\n * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),\n * },\n * },\n * });\n * ```\n */\nexport function when<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(\n condition: ConditionFn<TContext, TAction, TResource>\n): PolicyFn<TContext, TAction, TResource> {\n return (context, action, resource) =>\n condition(context, action, resource) ? \"allow\" : \"deny\";\n}\n\n/**\n * Returns a condition function that returns `true` if all conditions are met.\n *\n * @example\n * ```ts\n * const isOwnerAndPublished = and(\n * (ctx, action, resource) => ctx.user.id === resource.authorId,\n * (ctx, action, resource) => resource.status === \"published\"\n * );\n *\n * rules: {\n * post: {\n * delete: when(isOwnerAndPublished),\n * },\n * }\n * ```\n */\nexport function and<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(\n ...conditions: ConditionFn<TContext, TAction, TResource>[]\n): ConditionFn<TContext, TAction, TResource> {\n return (context, action, resource) =>\n conditions.every((condition) => condition(context, action, resource));\n}\n\n/**\n * Returns a condition function that returns `true` if any condition is met.\n *\n * @example\n * ```ts\n * const isOwnerOrAdmin = or(\n * (ctx, action, resource) => ctx.user.id === resource.authorId,\n * (ctx, action, resource) => ctx.user.role === \"admin\"\n * );\n *\n * rules: {\n * post: {\n * write: when(isOwnerOrAdmin),\n * },\n * }\n * ```\n */\nexport function or<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(\n ...conditions: ConditionFn<TContext, TAction, TResource>[]\n): ConditionFn<TContext, TAction, TResource> {\n return (context, action, resource) =>\n conditions.some((condition) => condition(context, action, resource));\n}\n\n/**\n * Returns a condition function that negates another condition.\n *\n * @example\n * ```ts\n * const isNotOwner = not((ctx, action, resource) => ctx.user.id === resource.authorId);\n *\n * rules: {\n * post: {\n * like: when(isNotOwner), // Can only like posts you don't own\n * },\n * }\n * ```\n */\nexport function not<\n TContext extends Context,\n TAction extends string = string,\n TResource = unknown,\n>(\n condition: ConditionFn<TContext, TAction, TResource>\n): ConditionFn<TContext, TAction, TResource> {\n return (context, action, resource) => !condition(context, action, resource);\n}\n\n/**\n * Returns a condition function that checks if a context property equals a value.\n *\n * @example\n * ```ts\n * rules: {\n * post: {\n * write: when(has(\"role\", \"admin\")), // Only admins can write\n * },\n * }\n * ```\n */\nexport function has<TContext extends Context, K extends keyof TContext>(\n key: K,\n value: TContext[K]\n): ConditionFn<TContext> {\n return (context) => context[key] === value;\n}\n\n/**\n * Collects all roles including inherited ones from a role hierarchy.\n *\n * @example\n * ```ts\n * type Role = \"guest\" | \"user\" | \"admin\";\n *\n * const hierarchy: RoleHierarchy<Role> = {\n * guest: [],\n * user: [\"guest\"],\n * admin: [\"user\"],\n * };\n *\n * collectInheritedRoles([\"admin\"], hierarchy);\n * // Returns: Set { \"admin\", \"user\", \"guest\" }\n * ```\n */\nexport function collectInheritedRoles<TRole extends Role = Role>(\n roles: TRole[],\n hierarchy: RoleHierarchy<TRole>\n): Set<TRole> {\n const inherited = new Set<TRole>();\n\n function add(role: TRole) {\n if (!inherited.has(role)) {\n inherited.add(role);\n const parents = hierarchy[role] ?? [];\n parents.forEach(add); // recursively add parent roles\n }\n }\n\n roles.forEach(add);\n return inherited;\n}\n\n/**\n * Returns a condition function that checks if the user has a specific role.\n * Supports role hierarchy for inherited permissions.\n *\n * @example\n * ```ts\n * // Without hierarchy\n * rules: {\n * post: {\n * delete: when(hasRole(\"admin\")),\n * },\n * }\n *\n * // With hierarchy\n * const hierarchy = {\n * guest: [],\n * user: [\"guest\"],\n * admin: [\"user\"],\n * };\n *\n * rules: {\n * post: {\n * read: when(hasRole(\"guest\", hierarchy)), // Admins and users can also read\n * },\n * }\n * ```\n */\nexport function hasRole<\n TContext extends Context & { role: Role | Role[] },\n TAction extends string = string,\n TResource = unknown,\n>(role: Role): ConditionFn<TContext, TAction, TResource>;\n\nexport function hasRole<\n TContext extends Context & { role: TRole | TRole[] },\n TAction extends string = string,\n TResource = unknown,\n TRole extends Role = Role,\n>(\n role: TRole,\n hierarchy: RoleHierarchy<TRole>\n): ConditionFn<TContext, TAction, TResource>;\n\nexport function hasRole<\n TContext extends Context & { role: Role | Role[] },\n TAction extends string = string,\n TResource = unknown,\n>(\n role: Role,\n hierarchy?: RoleHierarchy<Role>\n): ConditionFn<TContext, TAction, TResource> {\n return (context) => {\n const userRoles = Array.isArray(context.role)\n ? context.role\n : [context.role];\n\n if (!hierarchy) {\n return userRoles.includes(role);\n }\n\n const inherited = collectInheritedRoles(userRoles, hierarchy);\n return inherited.has(role);\n };\n}\n\n/**\n * Creates a type-safe policy from resource schemas, actions, and rules.\n *\n * @example\n * ```ts\n * import { z } from \"zod\";\n * import { createPolicy, allow, deny, when } from \"@zap-studio/permit\";\n * import type { Resources, Actions } from \"@zap-studio/permit/types\";\n *\n * // Define resource schemas\n * const resources = {\n * post: z.object({\n * id: z.string(),\n * authorId: z.string(),\n * visibility: z.enum([\"public\", \"private\"]),\n * }),\n * comment: z.object({\n * id: z.string(),\n * postId: z.string(),\n * authorId: z.string(),\n * }),\n * } satisfies Resources;\n *\n * // Define actions per resource\n * const actions = {\n * post: [\"read\", \"write\", \"delete\"],\n * comment: [\"read\", \"write\"],\n * } as const satisfies Actions<typeof resources>;\n *\n * // Define context type\n * type AppContext = { user: { id: string; role: string } };\n *\n * // Create the policy\n * const policy = createPolicy<AppContext>({\n * resources,\n * actions,\n * rules: {\n * post: {\n * read: when((ctx, action, resource) => resource.visibility === \"public\"),\n * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),\n * delete: deny(),\n * },\n * comment: {\n * read: allow(),\n * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),\n * },\n * },\n * });\n *\n * // Check permissions\n * const post = { id: \"1\", authorId: \"user-1\", visibility: \"public\" as const };\n * await policy.can(ctx, \"read\", \"post\", post); // true\n * await policy.can(ctx, \"write\", \"post\", post); // depends on ctx.user.id\n * ```\n */\nexport function createPolicy<\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n config: PermitConfig<TContext, TResources, TActions>\n): Policy<TContext, TResources, TActions> {\n const { rules, resources, actions } = config;\n const validators = new Map<\n keyof TResources,\n (input: unknown) => Promise<StandardSchemaV1.Result<unknown>>\n >();\n\n const getValidatedResource = async <K extends keyof TResources>(\n resourceType: K,\n resource: InferResource<TResources, K>\n ): Promise<InferResource<TResources, K> | null> => {\n const validator = validators.get(resourceType);\n if (!validator) {\n return null;\n }\n try {\n const result = await validator(resource);\n if (result.issues) {\n return null;\n }\n return result.value as InferResource<TResources, K>;\n } catch (error) {\n console.warn(\n `Resource validation failed for ${String(resourceType)}: ${String(error)}`\n );\n return null;\n }\n };\n\n for (const key of Object.keys(resources) as Array<keyof TResources>) {\n const schema = resources[key];\n if (!schema) {\n throw new PolicyError(`Missing schema for resource: ${String(key)}`);\n }\n const validator = createStandardValidator(schema);\n validators.set(key, async (input: unknown) => validator(input));\n }\n\n return {\n async can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n action: InferAction<TActions, K>,\n resourceType: K,\n resource: InferResource<TResources, K>\n ): Promise<boolean> {\n const allowedActions = actions[resourceType];\n if (!allowedActions) {\n return false;\n }\n if (!allowedActions.includes(action)) {\n return false;\n }\n\n const validatedResource = await getValidatedResource(\n resourceType,\n resource\n );\n if (!validatedResource) {\n return false;\n }\n\n const resourceRules = rules[resourceType];\n if (!resourceRules) {\n return false;\n }\n\n const policyFn = resourceRules[action];\n\n if (!policyFn) {\n return false;\n }\n\n try {\n return policyFn(context, action, validatedResource) === \"allow\";\n } catch (error) {\n console.warn(\n `Policy evaluation error for ${String(resourceType)}.${String(action)}: ${String(error)}`\n );\n return false;\n }\n },\n };\n}\n\n/**\n * Merges multiple policies into one using \"deny-overrides\" strategy.\n * If any policy denies, the merged policy denies. All must allow for the result to allow.\n *\n * @example\n * ```ts\n * const basePolicy = createPolicy({ ... });\n * const adminPolicy = createPolicy({ ... });\n *\n * const merged = mergePolicies(basePolicy, adminPolicy);\n * // Both policies must allow for the action to be permitted\n * ```\n */\nexport function mergePolicies<\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n ...policies: Policy<TContext, TResources, TActions>[]\n): Policy<TContext, TResources, TActions> {\n return {\n async can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n action: InferAction<TActions, K>,\n resourceType: K,\n resource: InferResource<TResources, K>\n ): Promise<boolean> {\n if (!policies.length) {\n return false;\n }\n for (const policy of policies) {\n if (!(await policy.can(context, action, resourceType, resource))) {\n return false;\n }\n }\n return true;\n },\n };\n}\n\n/**\n * Merges multiple policies into one using \"allow-overrides\" strategy.\n * If any policy allows, the merged policy allows. All must deny for the result to deny.\n *\n * @example\n * ```ts\n * const guestPolicy = createPolicy({ ... });\n * const memberPolicy = createPolicy({ ... });\n *\n * const merged = mergePoliciesAny(guestPolicy, memberPolicy);\n * // If either policy allows, the action is permitted\n * ```\n */\nexport function mergePoliciesAny<\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n ...policies: Policy<TContext, TResources, TActions>[]\n): Policy<TContext, TResources, TActions> {\n return {\n async can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n action: InferAction<TActions, K>,\n resourceType: K,\n resource: InferResource<TResources, K>\n ): Promise<boolean> {\n if (!policies.length) {\n return false;\n }\n for (const policy of policies) {\n if (await policy.can(context, action, resourceType, resource)) {\n return true;\n }\n }\n return false;\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/types.d.mts
CHANGED
|
@@ -1,2 +1,134 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents the possible outcomes of a policy decision.
|
|
7
|
+
* - "allow": The action is permitted.
|
|
8
|
+
* - "deny": The action is not permitted.
|
|
9
|
+
*/
|
|
10
|
+
type Decision = "allow" | "deny";
|
|
11
|
+
/**
|
|
12
|
+
* Represents the context in which a policy decision is made.
|
|
13
|
+
* Can include user information, environment, or any relevant data.
|
|
14
|
+
*/
|
|
15
|
+
type Context<TContext = unknown> = TContext;
|
|
16
|
+
/**
|
|
17
|
+
* Represents a role within the system.
|
|
18
|
+
*/
|
|
19
|
+
type Role<TRole extends string = string> = TRole;
|
|
20
|
+
/**
|
|
21
|
+
* Represents a role hierarchy within the system.
|
|
22
|
+
* Maps each role to an array of roles it inherits from.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* type Roles = "guest" | "user" | "admin";
|
|
27
|
+
*
|
|
28
|
+
* const hierarchy: RoleHierarchy<Roles> = {
|
|
29
|
+
* guest: [],
|
|
30
|
+
* user: ["guest"],
|
|
31
|
+
* admin: ["user"],
|
|
32
|
+
* };
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
type RoleHierarchy<TRole extends Role = Role> = Record<TRole, TRole[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Type helper for defining resource schemas using Standard Schema.
|
|
38
|
+
* Use with `satisfies` to ensure type safety when defining resources.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { z } from "zod";
|
|
43
|
+
* import type { Resources } from "@zap-studio/permit/types";
|
|
44
|
+
*
|
|
45
|
+
* const resources = {
|
|
46
|
+
* post: z.object({ id: z.string(), authorId: z.string() }),
|
|
47
|
+
* comment: z.object({ id: z.string(), postId: z.string() }),
|
|
48
|
+
* } satisfies Resources;
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
type Resources<TResourceKey extends string = string> = Record<TResourceKey, StandardSchemaV1>;
|
|
52
|
+
/**
|
|
53
|
+
* Type helper for defining actions per resource.
|
|
54
|
+
* Use with `satisfies` to ensure keys match the resource definitions.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* import type { Actions } from "@zap-studio/permit/types";
|
|
59
|
+
*
|
|
60
|
+
* const actions = {
|
|
61
|
+
* post: ["read", "write", "delete"],
|
|
62
|
+
* comment: ["read", "write"],
|
|
63
|
+
* } as const satisfies Actions<typeof resources>;
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
type Actions<TResources extends Resources> = { [K in keyof TResources]: readonly string[] };
|
|
67
|
+
/**
|
|
68
|
+
* Infers the output type from a Standard Schema.
|
|
69
|
+
*/
|
|
70
|
+
type InferResource<TResources extends Resources, TResourceKey extends keyof TResources> = StandardSchemaV1.InferOutput<TResources[TResourceKey]>;
|
|
71
|
+
/**
|
|
72
|
+
* Infers the action union type for a specific resource.
|
|
73
|
+
*/
|
|
74
|
+
type InferAction<TActions extends Record<string, readonly string[]>, K$1 extends keyof TActions> = TActions[K$1][number];
|
|
75
|
+
/**
|
|
76
|
+
* A function that determines whether a given action on a resource is allowed in a specific context.
|
|
77
|
+
*/
|
|
78
|
+
type PolicyFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => Decision;
|
|
79
|
+
/**
|
|
80
|
+
* A function that evaluates a condition for a given action and resource in a specific context.
|
|
81
|
+
*/
|
|
82
|
+
type ConditionFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Maps actions to their corresponding policy functions for a specific resource.
|
|
85
|
+
*/
|
|
86
|
+
type ActionPolicyMap<TContext extends Context, TAction extends string = string, TResource = unknown> = { [A in TAction]?: PolicyFn<TContext, A, TResource> };
|
|
87
|
+
/**
|
|
88
|
+
* Defines the rules for each resource and action combination.
|
|
89
|
+
* Each resource key maps to an object where each action key maps to a policy function.
|
|
90
|
+
*/
|
|
91
|
+
type Rules<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> = { [K in keyof TResources & keyof TActions]: ActionPolicyMap<TContext, InferAction<TActions, K>, InferResource<TResources, K>> };
|
|
92
|
+
/**
|
|
93
|
+
* Configuration object for creating a permit policy.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const config: PermitConfig<MyContext> = {
|
|
98
|
+
* resources,
|
|
99
|
+
* actions,
|
|
100
|
+
* rules: {
|
|
101
|
+
* post: { read: allow(), write: deny() },
|
|
102
|
+
* },
|
|
103
|
+
* };
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
interface PermitConfig<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> {
|
|
107
|
+
actions: TActions;
|
|
108
|
+
resources: TResources;
|
|
109
|
+
rules: Rules<TContext, TResources, TActions>;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Represents a policy object that can evaluate permissions.
|
|
113
|
+
* The `can` method checks if a given action is permitted on a resource in a specific context.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const policy: Policy<MyContext> = createPolicy({
|
|
118
|
+
* resources,
|
|
119
|
+
* actions,
|
|
120
|
+
* rules: { ... },
|
|
121
|
+
* });
|
|
122
|
+
*
|
|
123
|
+
* await policy.can(ctx, "read", "post", postData); // true or false
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
interface Policy<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> {
|
|
127
|
+
/**
|
|
128
|
+
* Determines if the specified action is permitted on the resource in the given context.
|
|
129
|
+
*/
|
|
130
|
+
can<K$1 extends keyof TResources & keyof TActions>(context: TContext, action: InferAction<TActions, K$1>, resourceType: K$1, resource: InferResource<TResources, K$1>): Promise<boolean>;
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { ActionPolicyMap, Actions, ConditionFn, Context, Decision, InferAction, InferResource, PermitConfig, Policy, PolicyFn, Resources, Role, RoleHierarchy, Rules };
|
|
134
|
+
//# sourceMappingURL=types.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;;AAOA;AAMA;AAKA;AAiBY,KA5BA,QAAA,GA4BA,OAAA,GAAA,MAAA;;;;;AAA2C,KAtB3C,OAsB2C,CAAA,WAAA,OAAA,CAAA,GAtBb,QAsBa;;AAiBvD;;AAEE,KApCU,IAoCV,CAAA,cAAA,MAAA,GAAA,MAAA,CAAA,GApCgD,KAoChD;;;AAiBF;AAOA;;;;;;;AAQA;;;;;AAGa,KAtDD,aAsDC,CAAA,cAtD2B,IAsD3B,GAtDkC,IAsDlC,CAAA,GAtD0C,MAsD1C,CAtDiD,KAsDjD,EAtDwD,KAsDxD,EAAA,CAAA;AAKb;;;;;;;AASA;;;;;;AASA;;AAKQ,KAjEI,SAiEJ,CAAA,qBAAA,MAAA,GAAA,MAAA,CAAA,GAjEsD,MAiEtD,CAhEN,YAgEM,EA/DN,gBA+DM,CAAA;;;;;;AAOR;;;;;;;;;AAKiC,KA1DrB,OA0DqB,CAAA,mBA1DM,SA0DN,CAAA,GAAA,QAAA,MAzDnB,UAyDmB,GAAA,SAAA,MAAA,EAAA,EAAA;;;;AAE7B,KArDQ,aAqDR,CACc,mBArDG,SAqDH,EAAY,qBAAA,MApDD,UAoDC,CAA1B,GAnDA,gBAAA,CAAiB,WAmDjB,CAnD6B,UAmD7B,CAnDwC,YAmDxC,CAAA,CAAA;;;AAkBJ;AACmB,KAjEP,WAiEO,CACE,iBAjEF,MAiEE,CAAA,MAAA,EAAA,SAAA,MAAA,EAAA,CAAA,EAAY,YAAA,MAhEf,QAgEe,CACN,GAhEvB,QAgEuB,CAhEd,GAgEc,CAAA,CAAA,MAAA,CAAA;;;;AAEhB,KA7DC,QA6DD,CACE,iBA7DM,OA6DN,EACE,gBAAA,MAAA,GAAA,MAAA,EAAU,YAAA,OAAA,CAAY,GAAA,CAAA,OAAA,EA3DvB,QA2DuB,EAAA,MAAA,EA3DL,OA2DK,EAAA,QAAA,EA3Dc,SA2Dd,EAAA,GA3D4B,QA2D5B;;;AAkBrC;AACmB,KAzEP,WAyEO,CACE,iBAzEF,OAyEE,EAAY,gBAAA,MAAA,GAAA,MAAA,EACN,YAAA,OAAA,CAAR,GAAA,CAAA,OAAA,EAvEL,QAuEK,EAAA,MAAA,EAvEa,OAuEb,EAAA,QAAA,EAvEgC,SAuEhC,EAAA,GAAA,OAAA;;;;AAKsB,KAvE7B,eAuE6B,CAC5B,iBAvEM,OAuEN,EACW,gBAAA,MAAA,GAAA,MAAA,EAAU,YAAA,OAAA,CAAtB,GAAA,QApEJ,OAoEI,IApEO,QAoEP,CApEgB,QAoEhB,EApE0B,CAoE1B,EApE6B,SAoE7B,CAAA,EAAA;;;;;AAGP,KAhEO,KAgEP,CAAA,iBA/Dc,OA+Dd,qBA9DgB,YAAY,4BACd,QAAQ,cAAc,QAAQ,6BAEnC,mBAAmB,WAAW,gBACxC,UACA,YAAY,UAAU,IACtB,cAAc,YAAY;;;;;;;;;;;;;;;UAkBb,8BACE,4BACE,YAAY,4BACd,QAAQ,cAAc,QAAQ;WAEtC;aACE;SACJ,MAAM,UAAU,YAAY;;;;;;;;;;;;;;;;;UAkBpB,wBACE,4BACE,YAAY,4BACd,QAAQ,cAAc,QAAQ;;;;wBAK3B,mBAAmB,mBAC5B,kBACD,YAAY,UAAU,oBAChB,eACJ,cAAc,YAAY,OACnC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zap-studio/permit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -35,17 +35,17 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@standard-schema/spec": "^1.1.0",
|
|
38
|
-
"@zap-studio/validation": "0.
|
|
38
|
+
"@zap-studio/validation": "0.3.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.0.2",
|
|
42
42
|
"@vitest/coverage-v8": "^4.0.15",
|
|
43
43
|
"tsdown": "^0.18.0",
|
|
44
44
|
"typescript": "^5.9.3",
|
|
45
|
-
"vitest": "^4.0.
|
|
46
|
-
"@zap-studio/
|
|
45
|
+
"vitest": "^4.0.18",
|
|
46
|
+
"@zap-studio/typescript-config": "0.0.0",
|
|
47
47
|
"@zap-studio/tsdown-config": "0.0.0",
|
|
48
|
-
"@zap-studio/
|
|
48
|
+
"@zap-studio/vitest-config": "0.0.0"
|
|
49
49
|
},
|
|
50
50
|
"exports": {
|
|
51
51
|
".": "./dist/index.mjs",
|
package/dist/errors-CgD70cJR.mjs
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
//#region src/errors.ts
|
|
2
|
-
/**
|
|
3
|
-
* Represents an error that occurs during policy evaluation or enforcement.
|
|
4
|
-
* Use this error to indicate issues related to policy logic, configuration, or execution.
|
|
5
|
-
*/
|
|
6
|
-
var PolicyError = class extends Error {
|
|
7
|
-
constructor(message) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.name = "PolicyError";
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
//#endregion
|
|
14
|
-
export { PolicyError as t };
|
|
15
|
-
//# sourceMappingURL=errors-CgD70cJR.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors-CgD70cJR.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Represents an error that occurs during policy evaluation or enforcement.\n * Use this error to indicate issues related to policy logic, configuration, or execution.\n */\nexport class PolicyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PolicyError\";\n }\n}\n"],"mappings":";;;;;AAIA,IAAa,cAAb,cAAiC,MAAM;CACrC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO"}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
|
|
3
|
-
//#region src/types.d.ts
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Represents the possible outcomes of a policy decision.
|
|
7
|
-
* - "allow": The action is permitted.
|
|
8
|
-
* - "deny": The action is not permitted.
|
|
9
|
-
*/
|
|
10
|
-
type Decision = "allow" | "deny";
|
|
11
|
-
/**
|
|
12
|
-
* Represents the context in which a policy decision is made.
|
|
13
|
-
* Can include user information, environment, or any relevant data.
|
|
14
|
-
*/
|
|
15
|
-
type Context<TContext = unknown> = TContext;
|
|
16
|
-
/**
|
|
17
|
-
* Represents a role within the system.
|
|
18
|
-
*/
|
|
19
|
-
type Role<TRole extends string = string> = TRole;
|
|
20
|
-
/**
|
|
21
|
-
* Represents a role hierarchy within the system.
|
|
22
|
-
* Maps each role to an array of roles it inherits from.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```ts
|
|
26
|
-
* type Roles = "guest" | "user" | "admin";
|
|
27
|
-
*
|
|
28
|
-
* const hierarchy: RoleHierarchy<Roles> = {
|
|
29
|
-
* guest: [],
|
|
30
|
-
* user: ["guest"],
|
|
31
|
-
* admin: ["user"],
|
|
32
|
-
* };
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
type RoleHierarchy<TRole extends Role = Role> = Record<TRole, TRole[]>;
|
|
36
|
-
/**
|
|
37
|
-
* Type helper for defining resource schemas using Standard Schema.
|
|
38
|
-
* Use with `satisfies` to ensure type safety when defining resources.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* import { z } from "zod";
|
|
43
|
-
* import type { Resources } from "@zap-studio/permit/types";
|
|
44
|
-
*
|
|
45
|
-
* const resources = {
|
|
46
|
-
* post: z.object({ id: z.string(), authorId: z.string() }),
|
|
47
|
-
* comment: z.object({ id: z.string(), postId: z.string() }),
|
|
48
|
-
* } satisfies Resources;
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
type Resources<TResourceKey extends string = string> = Record<TResourceKey, StandardSchemaV1>;
|
|
52
|
-
/**
|
|
53
|
-
* Type helper for defining actions per resource.
|
|
54
|
-
* Use with `satisfies` to ensure keys match the resource definitions.
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```ts
|
|
58
|
-
* import type { Actions } from "@zap-studio/permit/types";
|
|
59
|
-
*
|
|
60
|
-
* const actions = {
|
|
61
|
-
* post: ["read", "write", "delete"],
|
|
62
|
-
* comment: ["read", "write"],
|
|
63
|
-
* } as const satisfies Actions<typeof resources>;
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
type Actions<TResources extends Resources> = { [K in keyof TResources]: readonly string[] };
|
|
67
|
-
/**
|
|
68
|
-
* Infers the output type from a Standard Schema.
|
|
69
|
-
*/
|
|
70
|
-
type InferResource<TResources extends Resources, TResourceKey extends keyof TResources> = StandardSchemaV1.InferOutput<TResources[TResourceKey]>;
|
|
71
|
-
/**
|
|
72
|
-
* Infers the action union type for a specific resource.
|
|
73
|
-
*/
|
|
74
|
-
type InferAction<TActions extends Record<string, readonly string[]>, K$1 extends keyof TActions> = TActions[K$1][number];
|
|
75
|
-
/**
|
|
76
|
-
* A function that determines whether a given action on a resource is allowed in a specific context.
|
|
77
|
-
*/
|
|
78
|
-
type PolicyFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => Decision;
|
|
79
|
-
/**
|
|
80
|
-
* A function that evaluates a condition for a given action and resource in a specific context.
|
|
81
|
-
*/
|
|
82
|
-
type ConditionFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => boolean;
|
|
83
|
-
/**
|
|
84
|
-
* Maps actions to their corresponding policy functions for a specific resource.
|
|
85
|
-
*/
|
|
86
|
-
type ActionPolicyMap<TContext extends Context, TAction extends string = string, TResource = unknown> = { [A in TAction]?: PolicyFn<TContext, A, TResource> };
|
|
87
|
-
/**
|
|
88
|
-
* Defines the rules for each resource and action combination.
|
|
89
|
-
* Each resource key maps to an object where each action key maps to a policy function.
|
|
90
|
-
*/
|
|
91
|
-
type Rules<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> = { [K in keyof TResources & keyof TActions]: ActionPolicyMap<TContext, InferAction<TActions, K>, InferResource<TResources, K>> };
|
|
92
|
-
/**
|
|
93
|
-
* Configuration object for creating a permit policy.
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* ```ts
|
|
97
|
-
* const config: PermitConfig<MyContext> = {
|
|
98
|
-
* resources,
|
|
99
|
-
* actions,
|
|
100
|
-
* rules: {
|
|
101
|
-
* post: { read: allow(), write: deny() },
|
|
102
|
-
* },
|
|
103
|
-
* };
|
|
104
|
-
* ```
|
|
105
|
-
*/
|
|
106
|
-
type PermitConfig<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> = {
|
|
107
|
-
resources: TResources;
|
|
108
|
-
actions: TActions;
|
|
109
|
-
rules: Rules<TContext, TResources, TActions>;
|
|
110
|
-
};
|
|
111
|
-
/**
|
|
112
|
-
* Represents a policy object that can evaluate permissions.
|
|
113
|
-
* The `can` method checks if a given action is permitted on a resource in a specific context.
|
|
114
|
-
*
|
|
115
|
-
* @example
|
|
116
|
-
* ```ts
|
|
117
|
-
* const policy: Policy<MyContext> = createPolicy({
|
|
118
|
-
* resources,
|
|
119
|
-
* actions,
|
|
120
|
-
* rules: { ... },
|
|
121
|
-
* });
|
|
122
|
-
*
|
|
123
|
-
* policy.can(ctx, "read", "post", postData); // true or false
|
|
124
|
-
* ```
|
|
125
|
-
*/
|
|
126
|
-
type Policy<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> = {
|
|
127
|
-
/**
|
|
128
|
-
* Determines if the specified action is permitted on the resource in the given context.
|
|
129
|
-
*/
|
|
130
|
-
can<K$1 extends keyof TResources & keyof TActions>(context: TContext, action: InferAction<TActions, K$1>, resourceType: K$1, resource: InferResource<TResources, K$1>): boolean;
|
|
131
|
-
};
|
|
132
|
-
//#endregion
|
|
133
|
-
export { Decision as a, PermitConfig as c, Resources as d, Role as f, Context as i, Policy as l, Rules as m, Actions as n, InferAction as o, RoleHierarchy as p, ConditionFn as r, InferResource as s, ActionPolicyMap as t, PolicyFn as u };
|
|
134
|
-
//# sourceMappingURL=types-BgG1Aq6K.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-BgG1Aq6K.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;;AAOA;AAMA;AAKA;AAiBY,KA5BA,QAAA,GA4BA,OAAA,GAAA,MAAA;;;;;AAA2C,KAtB3C,OAsB2C,CAAA,WAAA,OAAA,CAAA,GAtBb,QAsBa;;AAiBvD;;AAEE,KApCU,IAoCV,CAAA,cAAA,MAAA,GAAA,MAAA,CAAA,GApCgD,KAoChD;;;AAiBF;AAOA;;;;;;;AAQA;;;;;AAGa,KAtDD,aAsDC,CAAA,cAtD2B,IAsD3B,GAtDkC,IAsDlC,CAAA,GAtD0C,MAsD1C,CAtDiD,KAsDjD,EAtDwD,KAsDxD,EAAA,CAAA;AAKb;;;;;;;AASA;;;;;;AASA;;AAKQ,KAjEI,SAiEJ,CAAA,qBAAA,MAAA,GAAA,MAAA,CAAA,GAjEsD,MAiEtD,CAhEN,YAgEM,EA/DN,gBA+DM,CAAA;;;;;;AAOR;;;;;;;;;AAKiC,KA1DrB,OA0DqB,CAAA,mBA1DM,SA0DN,CAAA,GAAA,QAAA,MAzDnB,UAyDmB,GAAA,SAAA,MAAA,EAAA,EAAA;;;;AAE7B,KArDQ,aAqDR,CACc,mBArDG,SAqDH,EAAY,qBAAA,MApDD,UAoDC,CAA1B,GAnDA,gBAAA,CAAiB,WAmDjB,CAnD6B,UAmD7B,CAnDwC,YAmDxC,CAAA,CAAA;;;AAkBJ;AACmB,KAjEP,WAiEO,CACE,iBAjEF,MAiEE,CAAA,MAAA,EAAA,SAAA,MAAA,EAAA,CAAA,EAAY,YAAA,MAhEf,QAgEe,CACN,GAhEvB,QAgEuB,CAhEd,GAgEc,CAAA,CAAA,MAAA,CAAA;;;;AAEd,KA7DD,QA6DC,CACF,iBA7DQ,OA6DR,EACI,gBAAA,MAAA,GAAA,MAAA,EAAU,YAAA,OAAA,CAAY,GAAA,CAAA,OAAA,EA3DvB,QA2DuB,EAAA,MAAA,EA3DL,OA2DK,EAAA,QAAA,EA3Dc,SA2Dd,EAAA,GA3D4B,QA2D5B;;;AAkBrC;AACmB,KAzEP,WAyEO,CACE,iBAzEF,OAyEE,EAAY,gBAAA,MAAA,GAAA,MAAA,EACN,YAAA,OAAA,CAAR,GAAA,CAAA,OAAA,EAvEL,QAuEK,EAAA,MAAA,EAvEa,OAuEb,EAAA,QAAA,EAvEgC,SAuEhC,EAAA,GAAA,OAAA;;;;AAKsB,KAvE7B,eAuE6B,CAC5B,iBAvEM,OAuEN,EACW,gBAAA,MAAA,GAAA,MAAA,EAAU,YAAA,OAAA,CAAtB,GAAA,QApEJ,OAoEI,IApEO,QAoEP,CApEgB,QAoEhB,EApE0B,CAoE1B,EApE6B,SAoE7B,CAAA,EAAA;;;;;AAEE,KA/DF,KA+DE,kBA9DK,4BACE,YAAY,4BACd,QAAQ,cAAc,QAAQ,6BAEnC,mBAAmB,WAAW,gBACxC,UACA,YAAY,UAAU,IACtB,cAAc,YAAY;;;;;;;;;;;;;;;KAkBlB,8BACO,4BACE,YAAY,4BACd,QAAQ,cAAc,QAAQ;aAEpC;WACF;SACF,MAAM,UAAU,YAAY;;;;;;;;;;;;;;;;;KAkBzB,wBACO,4BACE,YAAY,4BACd,QAAQ,cAAc,QAAQ;;;;wBAK3B,mBAAmB,mBAC5B,kBACD,YAAY,UAAU,oBAChB,eACJ,cAAc,YAAY"}
|