@zap-studio/permit 0.3.2 → 0.3.4

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.
@@ -0,0 +1,89 @@
1
+ import { Actions, Context, PermitConfig, Policy, Resources } from "./types.js";
2
+ //#region src/policy.d.ts
3
+ /**
4
+ * Creates a type-safe policy from resource schemas, actions, and rules.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { z } from "zod";
9
+ * import { createPolicy, allow, deny, when } from "@zap-studio/permit";
10
+ * import type { Resources, Actions } from "@zap-studio/permit/types";
11
+ *
12
+ * // Define resource schemas
13
+ * const resources = {
14
+ * post: z.object({
15
+ * id: z.string(),
16
+ * authorId: z.string(),
17
+ * visibility: z.enum(["public", "private"]),
18
+ * }),
19
+ * comment: z.object({
20
+ * id: z.string(),
21
+ * postId: z.string(),
22
+ * authorId: z.string(),
23
+ * }),
24
+ * } satisfies Resources;
25
+ *
26
+ * // Define actions per resource
27
+ * const actions = {
28
+ * post: ["read", "write", "delete"],
29
+ * comment: ["read", "write"],
30
+ * } as const satisfies Actions<typeof resources>;
31
+ *
32
+ * // Define context type
33
+ * type AppContext = { user: { id: string; role: string } };
34
+ *
35
+ * // Create the policy
36
+ * const policy = createPolicy<AppContext>({
37
+ * resources,
38
+ * actions,
39
+ * rules: {
40
+ * post: {
41
+ * read: when((ctx, action, resource) => resource.visibility === "public"),
42
+ * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
43
+ * delete: deny(),
44
+ * },
45
+ * comment: {
46
+ * read: allow(),
47
+ * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
48
+ * },
49
+ * },
50
+ * });
51
+ *
52
+ * // Check permissions
53
+ * const post = { id: "1", authorId: "user-1", visibility: "public" as const };
54
+ * await policy.can(ctx, "post:read", post); // true
55
+ * await policy.can(ctx, "post:write", post); // depends on ctx.user.id
56
+ * ```
57
+ */
58
+ declare const createPolicy: <TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(config: PermitConfig<TContext, TResources, TActions>) => Policy<TContext, TResources, TActions>;
59
+ /**
60
+ * Merges multiple policies into one using "deny-overrides" strategy.
61
+ * If any policy denies, the merged policy denies. All must allow for the result to allow.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * const basePolicy = createPolicy({ ... });
66
+ * const adminPolicy = createPolicy({ ... });
67
+ *
68
+ * const merged = mergePolicies(basePolicy, adminPolicy);
69
+ * // Both policies must allow for the action to be permitted
70
+ * ```
71
+ */
72
+ declare const mergePolicies: <TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(...policies: Policy<TContext, TResources, TActions>[]) => Policy<TContext, TResources, TActions>;
73
+ /**
74
+ * Merges multiple policies into one using "allow-overrides" strategy.
75
+ * If any policy allows, the merged policy allows. All must deny for the result to deny.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * const guestPolicy = createPolicy({ ... });
80
+ * const memberPolicy = createPolicy({ ... });
81
+ *
82
+ * const merged = mergePoliciesAny(guestPolicy, memberPolicy);
83
+ * // If either policy allows, the action is permitted
84
+ * ```
85
+ */
86
+ declare const mergePoliciesAny: <TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(...policies: Policy<TContext, TResources, TActions>[]) => Policy<TContext, TResources, TActions>;
87
+ //#endregion
88
+ export { createPolicy, mergePolicies, mergePoliciesAny };
89
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","names":[],"sources":["../src/policy.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAyGa,eACX,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,aAE/C,QAAQ,aAAa,UAAU,YAAY,cAC1C,OAAO,UAAU,YAAY;;;;;;;;;;;;;;cA2InB,gBACX,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,gBAE5C,UAAU,OAAO,UAAU,YAAY,gBACzC,OAAO,UAAU,YAAY;;;;;;;;;;;;;;cAgBnB,mBACX,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,gBAE5C,UAAU,OAAO,UAAU,YAAY,gBACzC,OAAO,UAAU,YAAY"}
package/dist/policy.js ADDED
@@ -0,0 +1,153 @@
1
+ import { PolicyError } from "./errors.js";
2
+ import { createStandardValidator } from "@zap-studio/validation";
3
+ //#region src/policy.ts
4
+ /**
5
+ * Splits a typed `resource:action` permission string into its parts.
6
+ * Returns `null` when the string is malformed (missing/empty part or extra segments).
7
+ */
8
+ const parsePermission = (permission) => {
9
+ const [resourceTypeValue, actionValue, ...rest] = permission.split(":");
10
+ if (resourceTypeValue === void 0 || resourceTypeValue.length === 0 || actionValue === void 0 || actionValue.length === 0 || rest.length > 0) return null;
11
+ return {
12
+ action: actionValue,
13
+ resourceType: resourceTypeValue
14
+ };
15
+ };
16
+ /**
17
+ * Creates a type-safe policy from resource schemas, actions, and rules.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { z } from "zod";
22
+ * import { createPolicy, allow, deny, when } from "@zap-studio/permit";
23
+ * import type { Resources, Actions } from "@zap-studio/permit/types";
24
+ *
25
+ * // Define resource schemas
26
+ * const resources = {
27
+ * post: z.object({
28
+ * id: z.string(),
29
+ * authorId: z.string(),
30
+ * visibility: z.enum(["public", "private"]),
31
+ * }),
32
+ * comment: z.object({
33
+ * id: z.string(),
34
+ * postId: z.string(),
35
+ * authorId: z.string(),
36
+ * }),
37
+ * } satisfies Resources;
38
+ *
39
+ * // Define actions per resource
40
+ * const actions = {
41
+ * post: ["read", "write", "delete"],
42
+ * comment: ["read", "write"],
43
+ * } as const satisfies Actions<typeof resources>;
44
+ *
45
+ * // Define context type
46
+ * type AppContext = { user: { id: string; role: string } };
47
+ *
48
+ * // Create the policy
49
+ * const policy = createPolicy<AppContext>({
50
+ * resources,
51
+ * actions,
52
+ * rules: {
53
+ * post: {
54
+ * read: when((ctx, action, resource) => resource.visibility === "public"),
55
+ * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
56
+ * delete: deny(),
57
+ * },
58
+ * comment: {
59
+ * read: allow(),
60
+ * write: when((ctx, action, resource) => ctx.user.id === resource.authorId),
61
+ * },
62
+ * },
63
+ * });
64
+ *
65
+ * // Check permissions
66
+ * const post = { id: "1", authorId: "user-1", visibility: "public" as const };
67
+ * await policy.can(ctx, "post:read", post); // true
68
+ * await policy.can(ctx, "post:write", post); // depends on ctx.user.id
69
+ * ```
70
+ */
71
+ const createPolicy = (config) => {
72
+ const { rules, resources, actions } = config;
73
+ const validators = /* @__PURE__ */ new Map();
74
+ const getValidatedResource = async (resourceType, resource) => {
75
+ const validator = validators.get(resourceType);
76
+ if (validator === void 0) return null;
77
+ try {
78
+ const result = await validator(resource);
79
+ if (result.issues) return null;
80
+ return result.value;
81
+ } catch (error) {
82
+ console.warn(`Resource validation failed for ${String(resourceType)}: ${String(error)}`);
83
+ return null;
84
+ }
85
+ };
86
+ const hasAllowedAction = (resourceType, action) => actions[resourceType]?.includes(action) ?? false;
87
+ const evaluatePolicy = (context, resourceType, action, resource) => {
88
+ const policyFn = rules[resourceType]?.[action];
89
+ if (policyFn === void 0) return false;
90
+ try {
91
+ return policyFn(context, action, resource) === "allow";
92
+ } catch (error) {
93
+ console.warn(`Policy evaluation error for ${String(resourceType)}.${action}: ${String(error)}`);
94
+ return false;
95
+ }
96
+ };
97
+ for (const key of Object.keys(resources)) {
98
+ const schema = resources[key];
99
+ if (schema === void 0) throw new PolicyError(`Missing schema for resource: ${String(key)}`);
100
+ const validator = createStandardValidator(schema);
101
+ validators.set(key, async (input) => await validator(input));
102
+ }
103
+ return { async can(context, permission, resource) {
104
+ const parsedPermission = parsePermission(permission);
105
+ if (parsedPermission === null) return false;
106
+ const { action, resourceType } = parsedPermission;
107
+ if (!hasAllowedAction(resourceType, action)) return false;
108
+ const validatedResource = await getValidatedResource(resourceType, resource);
109
+ if (validatedResource === null) return false;
110
+ return evaluatePolicy(context, resourceType, action, validatedResource);
111
+ } };
112
+ };
113
+ const mergePoliciesWithStrategy = (policies, strategy) => ({ async can(context, permission, resource) {
114
+ if (policies.length === 0) return false;
115
+ for (const policy of policies) {
116
+ const allowed = await policy.can(context, permission, resource);
117
+ if (strategy === "allow-overrides" && allowed) return true;
118
+ if (strategy === "deny-overrides" && !allowed) return false;
119
+ }
120
+ return strategy === "deny-overrides";
121
+ } });
122
+ /**
123
+ * Merges multiple policies into one using "deny-overrides" strategy.
124
+ * If any policy denies, the merged policy denies. All must allow for the result to allow.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * const basePolicy = createPolicy({ ... });
129
+ * const adminPolicy = createPolicy({ ... });
130
+ *
131
+ * const merged = mergePolicies(basePolicy, adminPolicy);
132
+ * // Both policies must allow for the action to be permitted
133
+ * ```
134
+ */
135
+ const mergePolicies = (...policies) => mergePoliciesWithStrategy(policies, "deny-overrides");
136
+ /**
137
+ * Merges multiple policies into one using "allow-overrides" strategy.
138
+ * If any policy allows, the merged policy allows. All must deny for the result to deny.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const guestPolicy = createPolicy({ ... });
143
+ * const memberPolicy = createPolicy({ ... });
144
+ *
145
+ * const merged = mergePoliciesAny(guestPolicy, memberPolicy);
146
+ * // If either policy allows, the action is permitted
147
+ * ```
148
+ */
149
+ const mergePoliciesAny = (...policies) => mergePoliciesWithStrategy(policies, "allow-overrides");
150
+ //#endregion
151
+ export { createPolicy, mergePolicies, mergePoliciesAny };
152
+
153
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","names":[],"sources":["../src/policy.ts"],"sourcesContent":["/**\n * Policy creation and composition: `createPolicy`, `mergePolicies`, and\n * `mergePoliciesAny`.\n *\n * @module @zap-studio/permit/policy\n */\n\nimport type { StandardSchemaV1 } from \"@zap-studio/validation\";\nimport { createStandardValidator } from \"@zap-studio/validation\";\n\nimport { PolicyError } from \"./errors.js\";\nimport type {\n Actions,\n Context,\n InferAction,\n InferResource,\n PermitConfig,\n Policy,\n Resources,\n} from \"./types.js\";\n\n/**\n * Splits a typed `resource:action` permission string into its parts.\n * Returns `null` when the string is malformed (missing/empty part or extra segments).\n */\nconst parsePermission = <\n TResources extends Resources,\n TActions extends Actions<TResources>,\n K extends keyof TResources & keyof TActions,\n>(\n permission: `${K & string}:${InferAction<TActions, K> & string}`\n): { action: InferAction<TActions, K>; resourceType: K } | null => {\n const [resourceTypeValue, actionValue, ...rest] = permission.split(\":\");\n if (\n resourceTypeValue === undefined ||\n resourceTypeValue.length === 0 ||\n actionValue === undefined ||\n actionValue.length === 0 ||\n rest.length > 0\n ) {\n return null;\n }\n\n return {\n action: actionValue,\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Parsed permission strings are constrained by the typed permission template.\n resourceType: resourceTypeValue as K,\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, \"post:read\", post); // true\n * await policy.can(ctx, \"post:write\", post); // depends on ctx.user.id\n * ```\n */\nexport const 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 === undefined) {\n return null;\n }\n try {\n const result = await validator(resource);\n if (result.issues) {\n return null;\n }\n return result.value;\n } catch (error) {\n console.warn(\n `Resource validation failed for ${String(resourceType)}: ${String(error)}`\n );\n return null;\n }\n };\n\n const hasAllowedAction = <K extends keyof TResources & keyof TActions>(\n resourceType: K,\n action: InferAction<TActions, K>\n ): boolean => actions[resourceType]?.includes(action) ?? false;\n\n const evaluatePolicy = <K extends keyof TResources & keyof TActions>(\n context: TContext,\n resourceType: K,\n action: InferAction<TActions, K>,\n resource: InferResource<TResources, K>\n ): boolean => {\n const policyFn = rules[resourceType]?.[action];\n if (policyFn === undefined) {\n return false;\n }\n\n try {\n return policyFn(context, action, resource) === \"allow\";\n } catch (error) {\n console.warn(\n `Policy evaluation error for ${String(resourceType)}.${action}: ${String(error)}`\n );\n return false;\n }\n };\n\n for (const key of Object.keys(resources) as (keyof TResources)[]) {\n const schema = resources[key];\n if (schema === undefined) {\n throw new PolicyError(`Missing schema for resource: ${String(key)}`);\n }\n const validator = createStandardValidator(schema);\n validators.set(key, async (input: unknown) => await validator(input));\n }\n\n return {\n async can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n permission: `${K & string}:${InferAction<TActions, K> & string}`,\n resource: InferResource<TResources, K>\n ): Promise<boolean> {\n const parsedPermission = parsePermission<TResources, TActions, K>(\n permission\n );\n if (parsedPermission === null) {\n return false;\n }\n\n const { action, resourceType } = parsedPermission;\n if (!hasAllowedAction(resourceType, action)) {\n return false;\n }\n\n const validatedResource = await getValidatedResource(\n resourceType,\n resource\n );\n if (validatedResource === null) {\n return false;\n }\n\n return evaluatePolicy(context, resourceType, action, validatedResource);\n },\n };\n};\n\nconst mergePoliciesWithStrategy = <\n TContext extends Context,\n TResources extends Resources = Resources,\n TActions extends Actions<TResources> = Actions<TResources>,\n>(\n policies: Policy<TContext, TResources, TActions>[],\n strategy: \"allow-overrides\" | \"deny-overrides\"\n): Policy<TContext, TResources, TActions> => ({\n async can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n permission: `${K & string}:${InferAction<TActions, K> & string}`,\n resource: InferResource<TResources, K>\n ): Promise<boolean> {\n if (policies.length === 0) {\n return false;\n }\n for (const policy of policies) {\n // oxlint-disable-next-line no-await-in-loop -- Policies must evaluate sequentially to preserve short-circuit semantics.\n const allowed = await policy.can(context, permission, resource);\n\n if (strategy === \"allow-overrides\" && allowed) {\n return true;\n }\n if (strategy === \"deny-overrides\" && !allowed) {\n return false;\n }\n }\n return strategy === \"deny-overrides\";\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 const 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 mergePoliciesWithStrategy(policies, \"deny-overrides\");\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 const 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 mergePoliciesWithStrategy(policies, \"allow-overrides\");\n"],"mappings":";;;;;;;AAyBA,MAAM,mBAKJ,eACiE;CACjE,MAAM,CAAC,mBAAmB,aAAa,GAAG,QAAQ,WAAW,MAAM,GAAG;CACtE,IACE,sBAAsB,KAAA,KACtB,kBAAkB,WAAW,KAC7B,gBAAgB,KAAA,KAChB,YAAY,WAAW,KACvB,KAAK,SAAS,GAEd,OAAO;CAGT,OAAO;EACL,QAAQ;EAER,cAAc;CAChB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,MAAa,gBAKX,WAC2C;CAC3C,MAAM,EAAE,OAAO,WAAW,YAAY;CACtC,MAAM,6BAAa,IAAI,IAGrB;CAEF,MAAM,uBAAuB,OAC3B,cACA,aACiD;EACjD,MAAM,YAAY,WAAW,IAAI,YAAY;EAC7C,IAAI,cAAc,KAAA,GAChB,OAAO;EAET,IAAI;GACF,MAAM,SAAS,MAAM,UAAU,QAAQ;GACvC,IAAI,OAAO,QACT,OAAO;GAET,OAAO,OAAO;EAChB,SAAS,OAAO;GACd,QAAQ,KACN,kCAAkC,OAAO,YAAY,EAAE,IAAI,OAAO,KAAK,GACzE;GACA,OAAO;EACT;CACF;CAEA,MAAM,oBACJ,cACA,WACY,QAAQ,aAAa,EAAE,SAAS,MAAM,KAAK;CAEzD,MAAM,kBACJ,SACA,cACA,QACA,aACY;EACZ,MAAM,WAAW,MAAM,aAAa,GAAG;EACvC,IAAI,aAAa,KAAA,GACf,OAAO;EAGT,IAAI;GACF,OAAO,SAAS,SAAS,QAAQ,QAAQ,MAAM;EACjD,SAAS,OAAO;GACd,QAAQ,KACN,+BAA+B,OAAO,YAAY,EAAE,GAAG,OAAO,IAAI,OAAO,KAAK,GAChF;GACA,OAAO;EACT;CACF;CAEA,KAAK,MAAM,OAAO,OAAO,KAAK,SAAS,GAA2B;EAChE,MAAM,SAAS,UAAU;EACzB,IAAI,WAAW,KAAA,GACb,MAAM,IAAI,YAAY,gCAAgC,OAAO,GAAG,GAAG;EAErE,MAAM,YAAY,wBAAwB,MAAM;EAChD,WAAW,IAAI,KAAK,OAAO,UAAmB,MAAM,UAAU,KAAK,CAAC;CACtE;CAEA,OAAO,EACL,MAAM,IACJ,SACA,YACA,UACkB;EAClB,MAAM,mBAAmB,gBACvB,UACF;EACA,IAAI,qBAAqB,MACvB,OAAO;EAGT,MAAM,EAAE,QAAQ,iBAAiB;EACjC,IAAI,CAAC,iBAAiB,cAAc,MAAM,GACxC,OAAO;EAGT,MAAM,oBAAoB,MAAM,qBAC9B,cACA,QACF;EACA,IAAI,sBAAsB,MACxB,OAAO;EAGT,OAAO,eAAe,SAAS,cAAc,QAAQ,iBAAiB;CACxE,EACF;AACF;AAEA,MAAM,6BAKJ,UACA,cAC4C,EAC5C,MAAM,IACJ,SACA,YACA,UACkB;CAClB,IAAI,SAAS,WAAW,GACtB,OAAO;CAET,KAAK,MAAM,UAAU,UAAU;EAE7B,MAAM,UAAU,MAAM,OAAO,IAAI,SAAS,YAAY,QAAQ;EAE9D,IAAI,aAAa,qBAAqB,SACpC,OAAO;EAET,IAAI,aAAa,oBAAoB,CAAC,SACpC,OAAO;CAEX;CACA,OAAO,aAAa;AACtB,EACF;;;;;;;;;;;;;;AAeA,MAAa,iBAKX,GAAG,aAEH,0BAA0B,UAAU,gBAAgB;;;;;;;;;;;;;;AAetD,MAAa,oBAKX,GAAG,aAEH,0BAA0B,UAAU,iBAAiB"}
@@ -0,0 +1,142 @@
1
+ import { StandardSchemaV1 } from "@zap-studio/validation";
2
+ //#region src/types.d.ts
3
+ /**
4
+ * Represents the possible outcomes of a policy decision.
5
+ * - "allow": The action is permitted.
6
+ * - "deny": The action is not permitted.
7
+ */
8
+ type Decision = "allow" | "deny";
9
+ /**
10
+ * Represents the context in which a policy decision is made.
11
+ * Can include user information, environment, or any relevant data.
12
+ */
13
+ type Context<TContext = unknown> = TContext;
14
+ /**
15
+ * Represents a role within the system.
16
+ */
17
+ type Role<TRole extends string = string> = TRole;
18
+ /**
19
+ * Represents a role hierarchy within the system.
20
+ * Maps each role to an array of roles it inherits from.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * type Roles = "guest" | "user" | "admin";
25
+ *
26
+ * const hierarchy: RoleHierarchy<Roles> = {
27
+ * guest: [],
28
+ * user: ["guest"],
29
+ * admin: ["user"],
30
+ * };
31
+ * ```
32
+ */
33
+ type RoleHierarchy<TRole extends Role = Role> = Record<TRole, TRole[]>;
34
+ /**
35
+ * Type helper for defining resource schemas using Standard Schema.
36
+ * Use with `satisfies` to ensure type safety when defining resources.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { z } from "zod";
41
+ * import type { Resources } from "@zap-studio/permit/types";
42
+ *
43
+ * const resources = {
44
+ * post: z.object({ id: z.string(), authorId: z.string() }),
45
+ * comment: z.object({ id: z.string(), postId: z.string() }),
46
+ * } satisfies Resources;
47
+ * ```
48
+ */
49
+ type Resources<TResourceKey extends string = string> = Record<TResourceKey, StandardSchemaV1>;
50
+ /**
51
+ * Type helper for defining actions per resource.
52
+ * Use with `satisfies` to ensure keys match the resource definitions.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import type { Actions } from "@zap-studio/permit/types";
57
+ *
58
+ * const actions = {
59
+ * post: ["read", "write", "delete"],
60
+ * comment: ["read", "write"],
61
+ * } as const satisfies Actions<typeof resources>;
62
+ * ```
63
+ */
64
+ type Actions<TResources extends Resources> = { [K in keyof TResources]: readonly string[]; };
65
+ /**
66
+ * Infers the output type from a Standard Schema.
67
+ */
68
+ type InferResource<TResources extends Resources, TResourceKey extends keyof TResources> = StandardSchemaV1.InferOutput<TResources[TResourceKey]>;
69
+ /**
70
+ * Infers the action union type for a specific resource.
71
+ */
72
+ type InferAction<TActions extends Record<string, readonly string[]>, K extends keyof TActions> = TActions[K][number];
73
+ /**
74
+ * Infers the permission-string union for all resource/action combinations.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * type Permission = InferPermission<typeof resources, typeof actions>;
79
+ * // "post:read" | "post:write" | "comment:read"
80
+ * ```
81
+ */
82
+ type InferPermission<TResources extends Resources, TActions extends Actions<TResources>> = { [K in keyof TResources & keyof TActions]: `${K & string}:${InferAction<TActions, K> & string}`; }[keyof TResources & keyof TActions];
83
+ /**
84
+ * A function that determines whether a given action on a resource is allowed in a specific context.
85
+ */
86
+ type PolicyFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => Decision;
87
+ /**
88
+ * A function that evaluates a condition for a given action and resource in a specific context.
89
+ */
90
+ type ConditionFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => boolean;
91
+ /**
92
+ * Maps actions to their corresponding policy functions for a specific resource.
93
+ */
94
+ type ActionPolicyMap<TContext extends Context, TAction extends string = string, TResource = unknown> = { [A in TAction]?: PolicyFn<TContext, A, TResource>; };
95
+ /**
96
+ * Defines the rules for each resource and action combination.
97
+ * Each resource key maps to an object where each action key maps to a policy function.
98
+ */
99
+ 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>>; };
100
+ /**
101
+ * Configuration object for creating a permit policy.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * const config: PermitConfig<MyContext> = {
106
+ * resources,
107
+ * actions,
108
+ * rules: {
109
+ * post: { read: allow(), write: deny() },
110
+ * },
111
+ * };
112
+ * ```
113
+ */
114
+ interface PermitConfig<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> {
115
+ actions: TActions;
116
+ resources: TResources;
117
+ rules: Rules<TContext, TResources, TActions>;
118
+ }
119
+ /**
120
+ * Represents a policy object that can evaluate permissions.
121
+ * The `can` method checks if a given action is permitted on a resource in a specific context.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const policy: Policy<MyContext> = createPolicy({
126
+ * resources,
127
+ * actions,
128
+ * rules: { ... },
129
+ * });
130
+ *
131
+ * await policy.can(ctx, "post:read", postData); // true or false
132
+ * ```
133
+ */
134
+ interface Policy<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> {
135
+ /**
136
+ * Determines if the specified action is permitted on the resource in the given context.
137
+ */
138
+ can: <K extends keyof TResources & keyof TActions>(context: TContext, permission: `${K & string}:${InferAction<TActions, K> & string}`, resource: InferResource<TResources, K>) => Promise<boolean>;
139
+ }
140
+ //#endregion
141
+ export { ActionPolicyMap, Actions, ConditionFn, Context, Decision, InferAction, InferPermission, InferResource, PermitConfig, Policy, PolicyFn, Resources, Role, RoleHierarchy, Rules };
142
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;KAaY;;;;;KAMA,QAAQ,sBAAsB;;;;KAK9B,KAAK,iCAAiC;;;;;;;;;;;;;;;;KAiBtC,cAAc,cAAc,OAAO,QAAQ,OAAO,OAAO;;;;;;;;;;;;;;;;KAiBzD,UAAU,wCAAwC,OAC5D,cACA;;;;;;;;;;;;;;;KAiBU,QAAQ,mBAAmB,gBACpC,WAAW;;;;KAMF,cACV,mBAAmB,WACnB,2BAA2B,cACzB,iBAAiB,YAAY,WAAW;;;;KAKhC,YACV,iBAAiB,mCACjB,gBAAgB,YACd,SAAS;;;;;;;;;;KAWD,gBACV,mBAAmB,WACnB,iBAAiB,QAAQ,kBAExB,WAAW,mBACJ,cAAc,cAAc,YAAY,UAAU,uBACpD,mBAAmB;;;;KAKf,SACV,iBAAiB,SACjB,iCACA,wBACG,SAAS,UAAU,QAAQ,SAAS,UAAU,cAAc;;;;KAKrD,YACV,iBAAiB,SACjB,iCACA,wBACG,SAAS,UAAU,QAAQ,SAAS,UAAU;;;;KAKvC,gBACV,iBAAiB,SACjB,iCACA,0BAEC,KAAK,WAAW,SAAS,UAAU,GAAG;;;;;KAO7B,MACV,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,kBAE9C,WAAW,mBAAmB,WAAW,gBACxC,UACA,YAAY,UAAU,IACtB,cAAc,YAAY;;;;;;;;;;;;;;;UAkBb,aACf,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ;EAE/C,SAAS;EACT,WAAW;EACX,OAAO,MAAM,UAAU,YAAY;;;;;;;;;;;;;;;;;UAkBpB,OACf,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ;;;;EAK/C,MAAM,gBAAgB,mBAAmB,UACvC,SAAS,UACT,eAAe,cAAc,YAAY,UAAU,eACnD,UAAU,cAAc,YAAY,OACjC"}
package/dist/types.js ADDED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zap-studio/permit",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "private": false,
5
5
  "description": "A type-safe, declarative authorization library for TypeScript with Standard Schema support",
6
6
  "keywords": [
@@ -31,31 +31,29 @@
31
31
  ],
32
32
  "type": "module",
33
33
  "sideEffects": false,
34
- "types": "./dist/index.d.mts",
34
+ "types": "./dist/index.d.ts",
35
35
  "exports": {
36
- ".": "./dist/index.mjs",
37
- "./errors": "./dist/errors.mjs",
38
- "./helpers": "./dist/helpers.mjs",
39
- "./types": "./dist/types.mjs",
36
+ ".": "./dist/index.js",
37
+ "./conditions": "./dist/conditions.js",
38
+ "./errors": "./dist/errors.js",
39
+ "./helpers": "./dist/helpers.js",
40
+ "./policy": "./dist/policy.js",
41
+ "./types": "./dist/types.js",
40
42
  "./package.json": "./package.json"
41
43
  },
42
44
  "publishConfig": {
43
45
  "access": "public"
44
46
  },
45
47
  "dependencies": {
46
- "@zap-studio/validation": "0.3.4"
48
+ "@zap-studio/validation": "workspace:*"
47
49
  },
48
50
  "devDependencies": {
49
- "typescript": "^6.0.3",
50
- "vite-plus": "^0.1.19",
51
- "@zap-studio/typescript": "0.0.0"
51
+ "@zap-studio/typescript": "workspace:*",
52
+ "tsdown": "catalog:",
53
+ "typescript": "catalog:",
54
+ "vitest": "catalog:"
52
55
  },
53
56
  "engines": {
54
57
  "node": ">=18.0.0"
55
- },
56
- "scripts": {
57
- "build": "vp pack",
58
- "test": "vp test run",
59
- "test:watch": "vp test watch"
60
58
  }
61
- }
59
+ }
package/dist/errors.d.mts DELETED
@@ -1,11 +0,0 @@
1
- //#region src/errors.d.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
- declare class PolicyError extends Error {
7
- constructor(message: string);
8
- }
9
- //#endregion
10
- export { PolicyError };
11
- //# sourceMappingURL=errors.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.d.mts","names":[],"sources":["../src/errors.ts"],"mappings":";;AAIA;;;cAAa,WAAA,SAAoB,KAAA;EAC/B,WAAA,CAAY,OAAA;AAAA"}
@@ -1 +0,0 @@
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"}
@@ -1,27 +0,0 @@
1
- //#region src/helpers.d.ts
2
- /**
3
- * Ensures that a value of type `never` is actually never encountered at runtime.
4
- * This is useful for exhaustive checks on discriminated unions.
5
- *
6
- * @example
7
- * ```ts
8
- * type Action = 'read' | 'write'
9
- *
10
- * function performAction(action: Action) {
11
- * switch (action) {
12
- * case 'read':
13
- * console.log('Reading...')
14
- * break
15
- * case 'write':
16
- * console.log('Writing...')
17
- * break
18
- * default:
19
- * assertNever(action) // TypeScript will error if a new Action is added but not handled
20
- * }
21
- * }
22
- * ```
23
- */
24
- declare function assertNever(value: never): never;
25
- //#endregion
26
- export { assertNever };
27
- //# sourceMappingURL=helpers.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"helpers.d.mts","names":[],"sources":["../src/helpers.ts"],"mappings":";;AAsBA;;;;;;;;;;;;;;;;;;;;;iBAAgB,WAAA,CAAY,KAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"helpers.mjs","names":[],"sources":["../src/helpers.ts"],"sourcesContent":["/**\n * Ensures that a value of type `never` is actually never encountered at runtime.\n * This is useful for exhaustive checks on discriminated unions.\n *\n * @example\n * ```ts\n * type Action = 'read' | 'write'\n *\n * function performAction(action: Action) {\n * switch (action) {\n * case 'read':\n * console.log('Reading...')\n * break\n * case 'write':\n * console.log('Writing...')\n * break\n * default:\n * assertNever(action) // TypeScript will error if a new Action is added but not handled\n * }\n * }\n * ```\n */\nexport function assertNever(value: never): never {\n throw new Error(`Unexpected value: ${String(value)}`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,YAAY,OAAqB;AAC/C,OAAM,IAAI,MAAM,qBAAqB,OAAO,MAAM,GAAG"}