@zap-studio/permit 2.0.1 → 2.0.2

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 CHANGED
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+
8
+ ## 2.0.2
9
+ <sub>2026-09-13</sub>
10
+
11
+ - [#613](https://github.com/zap-studio/monorepo/pull/613) *(patch)* Thanks [@alexandretrotel](https://github.com/alexandretrotel)!
12
+ Widen internal peer dependency ranges from an exact pin to a caret range, so consumers no longer resolve a duplicate copy of the peer on version skew.
13
+
7
14
  ## [2.0.1]
8
15
 
9
16
  ### Fixed
@@ -16,7 +16,7 @@ import { ConditionFn, Context, HasRoleFn, PolicyFn, Role, RoleHierarchy } from "
16
16
  * });
17
17
  * ```
18
18
  */
19
- declare const allow: <TContext extends Context, TAction extends string = string, TResource = unknown>() => PolicyFn<TContext, TAction, TResource>;
19
+ export declare const allow: <TContext extends Context, TAction extends string = string, TResource = unknown>() => PolicyFn<TContext, TAction, TResource>;
20
20
  /**
21
21
  * Returns a policy function that always denies the action.
22
22
  *
@@ -33,7 +33,7 @@ declare const allow: <TContext extends Context, TAction extends string = string,
33
33
  * });
34
34
  * ```
35
35
  */
36
- declare const deny: <TContext extends Context, TAction extends string = string, TResource = unknown>() => PolicyFn<TContext, TAction, TResource>;
36
+ export declare const deny: <TContext extends Context, TAction extends string = string, TResource = unknown>() => PolicyFn<TContext, TAction, TResource>;
37
37
  /**
38
38
  * Returns a policy function that allows or denies based on a condition.
39
39
  *
@@ -50,7 +50,7 @@ declare const deny: <TContext extends Context, TAction extends string = string,
50
50
  * });
51
51
  * ```
52
52
  */
53
- declare const when: <TContext extends Context, TAction extends string = string, TResource = unknown>(condition: ConditionFn<TContext, TAction, TResource>) => PolicyFn<TContext, TAction, TResource>;
53
+ export declare const when: <TContext extends Context, TAction extends string = string, TResource = unknown>(condition: ConditionFn<TContext, TAction, TResource>) => PolicyFn<TContext, TAction, TResource>;
54
54
  /**
55
55
  * Returns a condition function that returns `true` if all conditions are met.
56
56
  *
@@ -68,7 +68,7 @@ declare const when: <TContext extends Context, TAction extends string = string,
68
68
  * }
69
69
  * ```
70
70
  */
71
- declare const and: <TContext extends Context, TAction extends string = string, TResource = unknown>(...conditions: ConditionFn<TContext, TAction, TResource>[]) => ConditionFn<TContext, TAction, TResource>;
71
+ export declare const and: <TContext extends Context, TAction extends string = string, TResource = unknown>(...conditions: ConditionFn<TContext, TAction, TResource>[]) => ConditionFn<TContext, TAction, TResource>;
72
72
  /**
73
73
  * Returns a condition function that returns `true` if any condition is met.
74
74
  *
@@ -86,7 +86,7 @@ declare const and: <TContext extends Context, TAction extends string = string, T
86
86
  * }
87
87
  * ```
88
88
  */
89
- declare const or: <TContext extends Context, TAction extends string = string, TResource = unknown>(...conditions: ConditionFn<TContext, TAction, TResource>[]) => ConditionFn<TContext, TAction, TResource>;
89
+ export declare const or: <TContext extends Context, TAction extends string = string, TResource = unknown>(...conditions: ConditionFn<TContext, TAction, TResource>[]) => ConditionFn<TContext, TAction, TResource>;
90
90
  /**
91
91
  * Returns a condition function that negates another condition.
92
92
  *
@@ -101,7 +101,7 @@ declare const or: <TContext extends Context, TAction extends string = string, TR
101
101
  * }
102
102
  * ```
103
103
  */
104
- declare const not: <TContext extends Context, TAction extends string = string, TResource = unknown>(condition: ConditionFn<TContext, TAction, TResource>) => ConditionFn<TContext, TAction, TResource>;
104
+ export declare const not: <TContext extends Context, TAction extends string = string, TResource = unknown>(condition: ConditionFn<TContext, TAction, TResource>) => ConditionFn<TContext, TAction, TResource>;
105
105
  /**
106
106
  * Returns a condition function that checks if a context property equals a value.
107
107
  *
@@ -114,7 +114,7 @@ declare const not: <TContext extends Context, TAction extends string = string, T
114
114
  * }
115
115
  * ```
116
116
  */
117
- declare const has: <TContext extends Context, K extends keyof TContext>(key: K, value: TContext[K]) => ConditionFn<TContext>;
117
+ export declare const has: <TContext extends Context, K extends keyof TContext>(key: K, value: TContext[K]) => ConditionFn<TContext>;
118
118
  /**
119
119
  * Collects all roles including inherited ones from a role hierarchy.
120
120
  *
@@ -132,7 +132,7 @@ declare const has: <TContext extends Context, K extends keyof TContext>(key: K,
132
132
  * // Returns: Set { "admin", "user", "guest" }
133
133
  * ```
134
134
  */
135
- declare const collectInheritedRoles: <TRole extends Role = Role>(roles: TRole[], hierarchy: RoleHierarchy<TRole>) => Set<TRole>;
135
+ export declare const collectInheritedRoles: <TRole extends Role = Role>(roles: TRole[], hierarchy: RoleHierarchy<TRole>) => Set<TRole>;
136
136
  /**
137
137
  * Returns a condition function that checks if the user has a specific role.
138
138
  * Supports role hierarchy for inherited permissions.
@@ -160,7 +160,6 @@ declare const collectInheritedRoles: <TRole extends Role = Role>(roles: TRole[],
160
160
  * }
161
161
  * ```
162
162
  */
163
- declare const hasRole: HasRoleFn;
163
+ export declare const hasRole: HasRoleFn;
164
164
  //#endregion
165
- export { allow, and, collectInheritedRoles, deny, has, hasRole, not, or, when };
166
165
  //# sourceMappingURL=conditions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conditions.d.ts","names":[],"sources":["../src/conditions.ts"],"mappings":";;;;;;;;;;;;;;;;;;cAyBa,QACV,iBAAiB,SAAS,iCAAiC,0BAAwB,SAClF,UACA,SACA;;;;;;;;;;;;;;;;;cAqBS,OACV,iBAAiB,SAAS,iCAAiC,0BAAwB,SAClF,UACA,SACA;;;;;;;;;;;;;;;;;cAqBS,OACV,iBAAiB,SAAS,iCAAiC,qBAC1D,WAAW,YAAY,UAAU,SAAS,eACzC,SAAS,UAAU,SAAS;;;;;;;;;;;;;;;;;;cAqBpB,MACV,iBAAiB,SAAS,iCAAiC,wBACvD,YAAY,YAAY,UAAU,SAAS,iBAC7C,YAAY,UAAU,SAAS;;;;;;;;;;;;;;;;;;cAqBvB,KACV,iBAAiB,SAAS,iCAAiC,wBACvD,YAAY,YAAY,UAAU,SAAS,iBAC7C,YAAY,UAAU,SAAS;;;;;;;;;;;;;;;cAkBvB,MACV,iBAAiB,SAAS,iCAAiC,qBAC1D,WAAW,YAAY,UAAU,SAAS,eACzC,YAAY,UAAU,SAAS;;;;;;;;;;;;;cAgBvB,MACV,iBAAiB,SAAS,gBAAgB,UACzC,KAAK,GACL,OAAO,SAAS,OACf,YAAY;;;;;;;;;;;;;;;;;;cAqBJ,wBAAyB,cAAc,OAAO,MACzD,OAAO,SACP,WAAW,cAAc,WACxB,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgDM,SAAS"}
1
+ {"version":3,"file":"conditions.d.ts","names":[],"sources":["../src/conditions.ts"],"mappings":";;;;;;;;;;;;;;;;;;qBAyBa,QACV,iBAAiB,SAAS,iCAAiC,0BAAwB,SAClF,UACA,SACA;;;;;;;;;;;;;;;;;qBAqBS,OACV,iBAAiB,SAAS,iCAAiC,0BAAwB,SAClF,UACA,SACA;;;;;;;;;;;;;;;;;qBAqBS,OACV,iBAAiB,SAAS,iCAAiC,qBAC1D,WAAW,YAAY,UAAU,SAAS,eACzC,SAAS,UAAU,SAAS;;;;;;;;;;;;;;;;;;qBAqBpB,MACV,iBAAiB,SAAS,iCAAiC,wBACvD,YAAY,YAAY,UAAU,SAAS,iBAC7C,YAAY,UAAU,SAAS;;;;;;;;;;;;;;;;;;qBAqBvB,KACV,iBAAiB,SAAS,iCAAiC,wBACvD,YAAY,YAAY,UAAU,SAAS,iBAC7C,YAAY,UAAU,SAAS;;;;;;;;;;;;;;;qBAkBvB,MACV,iBAAiB,SAAS,iCAAiC,qBAC1D,WAAW,YAAY,UAAU,SAAS,eACzC,YAAY,UAAU,SAAS;;;;;;;;;;;;;qBAgBvB,MACV,iBAAiB,SAAS,gBAAgB,UACzC,KAAK,GACL,OAAO,SAAS,OACf,YAAY;;;;;;;;;;;;;;;;;;qBAqBJ,wBAAyB,cAAc,OAAO,MACzD,OAAO,SACP,WAAW,cAAc,WACxB,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgDM,SAAS"}
package/dist/errors.d.ts CHANGED
@@ -22,7 +22,7 @@
22
22
  * }
23
23
  * ```
24
24
  */
25
- declare class PolicyError extends Error {
25
+ export declare class PolicyError extends Error {
26
26
  /**
27
27
  * Creates a policy error with a human-readable message.
28
28
  *
@@ -31,5 +31,4 @@ declare class PolicyError extends Error {
31
31
  constructor(message: string);
32
32
  }
33
33
  //#endregion
34
- export { PolicyError };
35
34
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;cAwBa,oBAAoB;;;;;;EAM/B,YAAY"}
1
+ {"version":3,"file":"errors.d.ts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;qBAwBa,oBAAoB;;;;;;EAM/B,YAAY"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { allow, and, collectInheritedRoles, deny, has, hasRole, not, or, when } from "./conditions.js";
2
2
  import { PolicyError } from "./errors.js";
3
- import { n as mergePoliciesAnd, r as mergePoliciesOr, t as createPolicy } from "./policy-BBfZBocp.js";
3
+ import { n as mergePoliciesAnd, r as mergePoliciesOr, t as createPolicy } from "./policy-Dby1nu6_.js";
4
4
  export { PolicyError, allow, and, collectInheritedRoles, createPolicy, deny, has, hasRole, mergePoliciesAnd, mergePoliciesOr, not, or, when };
@@ -3,7 +3,7 @@ import { createStandardValidator } from "@zap-studio/validation";
3
3
  import { SpanKind, context, metrics, trace } from "@opentelemetry/api";
4
4
  //#region package.json
5
5
  var name = "@zap-studio/permit";
6
- var version = "2.0.1";
6
+ var version = "2.0.2";
7
7
  //#endregion
8
8
  //#region src/_otel.ts
9
9
  /**
@@ -228,4 +228,4 @@ const mergePoliciesOr = (...policies) => mergePoliciesWithStrategy(policies, "or
228
228
  //#endregion
229
229
  export { mergePoliciesAnd as n, mergePoliciesOr as r, createPolicy as t };
230
230
 
231
- //# sourceMappingURL=policy-BBfZBocp.js.map
231
+ //# sourceMappingURL=policy-Dby1nu6_.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy-BBfZBocp.js","names":["pkg.name","pkg.version"],"sources":["../package.json","../src/_otel.ts","../src/policy.ts"],"sourcesContent":["","/**\n * Internal OpenTelemetry wiring for the permit package: tracer resolution,\n * the per-check span, and the checks counter. Kept out of `policy.ts` so\n * authorization logic doesn't get tangled with tracing/metrics concerns.\n *\n * @module @zap-studio/permit/otel\n */\n\nimport { SpanKind, context, metrics, trace } from \"@opentelemetry/api\";\n\nimport pkg from \"../package.json\" with { type: \"json\" };\n\n/**\n * OpenTelemetry tracer for this package. Resolved once against the global\n * `TracerProvider`; a no-op provider (the default until an app registers an\n * SDK) makes every span call below a no-op too.\n */\nconst tracer = trace.getTracer(pkg.name, pkg.version);\n\n/**\n * Records one authorization check, tagged with `permit.decision: \"allow\" |\n * \"deny\"`.\n *\n * Resolves the meter and counter fresh on every call instead of caching\n * them at module scope: unlike `trace.getTracer()`, `metrics.getMeter()`\n * has no proxy indirection — a reference grabbed before an app registers\n * its `MeterProvider` (the common case, since ESM imports resolve before\n * the importing module's own SDK-bootstrap code runs) would stay a no-op\n * forever. Repeated `createCounter` calls with the same name are cheap and\n * idempotent, so this costs nothing meaningful.\n */\nconst recordPermitCheck = (decision: \"allow\" | \"deny\"): void => {\n metrics\n .getMeter(pkg.name, pkg.version)\n .createCounter(\"permit.checks\", {\n description: \"Number of authorization checks made, tagged by decision.\",\n })\n .add(1, { \"permit.decision\": decision });\n};\n\n/**\n * Wraps one authorization check in an `INTERNAL` span named\n * `permit.check {permission}`, setting `permit.decision` and recording the\n * `permit.checks` counter from `run`'s boolean result.\n */\nexport const withCheckSpan = async (\n permission: string,\n run: () => Promise<boolean>,\n): Promise<boolean> => {\n const span = tracer.startSpan(`permit.check ${permission}`, {\n kind: SpanKind.INTERNAL,\n });\n\n try {\n return await context.with(trace.setSpan(context.active(), span), async () => {\n const allowed = await run();\n const decision = allowed ? \"allow\" : \"deny\";\n span.setAttribute(\"permit.decision\", decision);\n recordPermitCheck(decision);\n return allowed;\n });\n } finally {\n span.end();\n }\n};\n","/**\n * Policy creation and composition: `createPolicy`, `mergePoliciesAnd`, and\n * `mergePoliciesOr`.\n *\n * @module @zap-studio/permit/policy\n */\n\nimport type { StandardSchemaV1 } from \"@zap-studio/validation\";\n\nimport { createStandardValidator } from \"@zap-studio/validation\";\n\nimport type {\n Actions,\n Context,\n InferAction,\n InferResource,\n PermitConfig,\n Policy,\n Resources,\n} from \"./types.ts\";\n\nimport { withCheckSpan } from \"./_otel.ts\";\nimport { PolicyError } from \"./errors.ts\";\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\n * segments) or `resourceType` is not one of `actions`' keys.\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<TResources, TActions, K> & string}`,\n actions: TActions,\n): { action: InferAction<TResources, TActions, K>; resourceType: K } | null => {\n const isValidResourceKey = (value: string): value is K & string =>\n Object.keys(actions).includes(value);\n\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 !isValidResourceKey(resourceTypeValue)\n ) {\n return null;\n }\n\n return {\n action: actionValue,\n resourceType: resourceTypeValue,\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, logger } = 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 logger?.warn(`Resource validation failed for ${String(resourceType)}: ${String(error)}`, {\n error,\n resourceType: String(resourceType),\n });\n return null;\n }\n };\n\n const hasAllowedAction = <K extends keyof TResources & keyof TActions>(\n resourceType: K,\n action: InferAction<TResources, 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<TResources, 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 const allowed = policyFn(context, action, resource) === \"allow\";\n\n if (allowed) {\n logger?.debug(\"permission allowed\", {\n action,\n resourceType: String(resourceType),\n });\n } else {\n logger?.info(\"permission denied\", {\n action,\n resourceType: String(resourceType),\n });\n }\n\n return allowed;\n } catch (error) {\n logger?.warn(\n `Policy evaluation error for ${String(resourceType)}.${action}: ${String(error)}`,\n {\n action,\n error,\n resourceType: String(resourceType),\n },\n );\n return false;\n }\n };\n\n for (const key in resources) {\n if (!Object.hasOwn(resources, key)) {\n continue;\n }\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<TResources, TActions, K> & string}`,\n resource: InferResource<TResources, K>,\n ): Promise<boolean> {\n return await withCheckSpan(permission, async () => {\n const parsedPermission = parsePermission<TResources, TActions, K>(permission, actions);\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(resourceType, resource);\n if (validatedResource === null) {\n return false;\n }\n\n return evaluatePolicy(context, resourceType, action, validatedResource);\n });\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: \"and\" | \"or\",\n): Policy<TContext, TResources, TActions> => ({\n async can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n permission: `${K & string}:${InferAction<TResources, TActions, K> & string}`,\n resource: InferResource<TResources, K>,\n ): Promise<boolean> {\n return await withCheckSpan(permission, async () => {\n if (policies.length === 0) {\n return false;\n }\n\n const settled = await Promise.allSettled(\n policies.map(async (policy) => await policy.can(context, permission, resource)),\n );\n\n const results = settled.map((result) => {\n if (result.status === \"fulfilled\") {\n return result.value;\n }\n return false;\n });\n\n return strategy === \"and\" ? results.every(Boolean) : results.some(Boolean);\n });\n },\n});\n\n/**\n * Merges multiple policies into one, requiring every policy to allow.\n * If any policy denies, the merged policy denies. Policies are evaluated\n * in parallel; every policy is invoked regardless of outcome.\n *\n * @example\n * ```ts\n * const basePolicy = createPolicy({ ... });\n * const adminPolicy = createPolicy({ ... });\n *\n * const merged = mergePoliciesAnd(basePolicy, adminPolicy);\n * // Both policies must allow for the action to be permitted\n * ```\n */\nexport const mergePoliciesAnd = <\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> => mergePoliciesWithStrategy(policies, \"and\");\n\n/**\n * Merges multiple policies into one, requiring at least one policy to allow.\n * If every policy denies, the merged policy denies. Policies are evaluated\n * in parallel; every policy is invoked regardless of outcome.\n *\n * @example\n * ```ts\n * const guestPolicy = createPolicy({ ... });\n * const memberPolicy = createPolicy({ ... });\n *\n * const merged = mergePoliciesOr(guestPolicy, memberPolicy);\n * // If either policy allows, the action is permitted\n * ```\n */\nexport const mergePoliciesOr = <\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> => mergePoliciesWithStrategy(policies, \"or\");\n"],"mappings":"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"}
1
+ {"version":3,"file":"policy-Dby1nu6_.js","names":["pkg.name","pkg.version"],"sources":["../package.json","../src/_otel.ts","../src/policy.ts"],"sourcesContent":["","/**\n * Internal OpenTelemetry wiring for the permit package: tracer resolution,\n * the per-check span, and the checks counter. Kept out of `policy.ts` so\n * authorization logic doesn't get tangled with tracing/metrics concerns.\n *\n * @module @zap-studio/permit/otel\n */\n\nimport { SpanKind, context, metrics, trace } from \"@opentelemetry/api\";\n\nimport pkg from \"../package.json\" with { type: \"json\" };\n\n/**\n * OpenTelemetry tracer for this package. Resolved once against the global\n * `TracerProvider`; a no-op provider (the default until an app registers an\n * SDK) makes every span call below a no-op too.\n */\nconst tracer = trace.getTracer(pkg.name, pkg.version);\n\n/**\n * Records one authorization check, tagged with `permit.decision: \"allow\" |\n * \"deny\"`.\n *\n * Resolves the meter and counter fresh on every call instead of caching\n * them at module scope: unlike `trace.getTracer()`, `metrics.getMeter()`\n * has no proxy indirection — a reference grabbed before an app registers\n * its `MeterProvider` (the common case, since ESM imports resolve before\n * the importing module's own SDK-bootstrap code runs) would stay a no-op\n * forever. Repeated `createCounter` calls with the same name are cheap and\n * idempotent, so this costs nothing meaningful.\n */\nconst recordPermitCheck = (decision: \"allow\" | \"deny\"): void => {\n metrics\n .getMeter(pkg.name, pkg.version)\n .createCounter(\"permit.checks\", {\n description: \"Number of authorization checks made, tagged by decision.\",\n })\n .add(1, { \"permit.decision\": decision });\n};\n\n/**\n * Wraps one authorization check in an `INTERNAL` span named\n * `permit.check {permission}`, setting `permit.decision` and recording the\n * `permit.checks` counter from `run`'s boolean result.\n */\nexport const withCheckSpan = async (\n permission: string,\n run: () => Promise<boolean>,\n): Promise<boolean> => {\n const span = tracer.startSpan(`permit.check ${permission}`, {\n kind: SpanKind.INTERNAL,\n });\n\n try {\n return await context.with(trace.setSpan(context.active(), span), async () => {\n const allowed = await run();\n const decision = allowed ? \"allow\" : \"deny\";\n span.setAttribute(\"permit.decision\", decision);\n recordPermitCheck(decision);\n return allowed;\n });\n } finally {\n span.end();\n }\n};\n","/**\n * Policy creation and composition: `createPolicy`, `mergePoliciesAnd`, and\n * `mergePoliciesOr`.\n *\n * @module @zap-studio/permit/policy\n */\n\nimport type { StandardSchemaV1 } from \"@zap-studio/validation\";\n\nimport { createStandardValidator } from \"@zap-studio/validation\";\n\nimport type {\n Actions,\n Context,\n InferAction,\n InferResource,\n PermitConfig,\n Policy,\n Resources,\n} from \"./types.ts\";\n\nimport { withCheckSpan } from \"./_otel.ts\";\nimport { PolicyError } from \"./errors.ts\";\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\n * segments) or `resourceType` is not one of `actions`' keys.\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<TResources, TActions, K> & string}`,\n actions: TActions,\n): { action: InferAction<TResources, TActions, K>; resourceType: K } | null => {\n const isValidResourceKey = (value: string): value is K & string =>\n Object.keys(actions).includes(value);\n\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 !isValidResourceKey(resourceTypeValue)\n ) {\n return null;\n }\n\n return {\n action: actionValue,\n resourceType: resourceTypeValue,\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, logger } = 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 logger?.warn(`Resource validation failed for ${String(resourceType)}: ${String(error)}`, {\n error,\n resourceType: String(resourceType),\n });\n return null;\n }\n };\n\n const hasAllowedAction = <K extends keyof TResources & keyof TActions>(\n resourceType: K,\n action: InferAction<TResources, 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<TResources, 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 const allowed = policyFn(context, action, resource) === \"allow\";\n\n if (allowed) {\n logger?.debug(\"permission allowed\", {\n action,\n resourceType: String(resourceType),\n });\n } else {\n logger?.info(\"permission denied\", {\n action,\n resourceType: String(resourceType),\n });\n }\n\n return allowed;\n } catch (error) {\n logger?.warn(\n `Policy evaluation error for ${String(resourceType)}.${action}: ${String(error)}`,\n {\n action,\n error,\n resourceType: String(resourceType),\n },\n );\n return false;\n }\n };\n\n for (const key in resources) {\n if (!Object.hasOwn(resources, key)) {\n continue;\n }\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<TResources, TActions, K> & string}`,\n resource: InferResource<TResources, K>,\n ): Promise<boolean> {\n return await withCheckSpan(permission, async () => {\n const parsedPermission = parsePermission<TResources, TActions, K>(permission, actions);\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(resourceType, resource);\n if (validatedResource === null) {\n return false;\n }\n\n return evaluatePolicy(context, resourceType, action, validatedResource);\n });\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: \"and\" | \"or\",\n): Policy<TContext, TResources, TActions> => ({\n async can<K extends keyof TResources & keyof TActions>(\n context: TContext,\n permission: `${K & string}:${InferAction<TResources, TActions, K> & string}`,\n resource: InferResource<TResources, K>,\n ): Promise<boolean> {\n return await withCheckSpan(permission, async () => {\n if (policies.length === 0) {\n return false;\n }\n\n const settled = await Promise.allSettled(\n policies.map(async (policy) => await policy.can(context, permission, resource)),\n );\n\n const results = settled.map((result) => {\n if (result.status === \"fulfilled\") {\n return result.value;\n }\n return false;\n });\n\n return strategy === \"and\" ? results.every(Boolean) : results.some(Boolean);\n });\n },\n});\n\n/**\n * Merges multiple policies into one, requiring every policy to allow.\n * If any policy denies, the merged policy denies. Policies are evaluated\n * in parallel; every policy is invoked regardless of outcome.\n *\n * @example\n * ```ts\n * const basePolicy = createPolicy({ ... });\n * const adminPolicy = createPolicy({ ... });\n *\n * const merged = mergePoliciesAnd(basePolicy, adminPolicy);\n * // Both policies must allow for the action to be permitted\n * ```\n */\nexport const mergePoliciesAnd = <\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> => mergePoliciesWithStrategy(policies, \"and\");\n\n/**\n * Merges multiple policies into one, requiring at least one policy to allow.\n * If every policy denies, the merged policy denies. Policies are evaluated\n * in parallel; every policy is invoked regardless of outcome.\n *\n * @example\n * ```ts\n * const guestPolicy = createPolicy({ ... });\n * const memberPolicy = createPolicy({ ... });\n *\n * const merged = mergePoliciesOr(guestPolicy, memberPolicy);\n * // If either policy allows, the action is permitted\n * ```\n */\nexport const mergePoliciesOr = <\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> => mergePoliciesWithStrategy(policies, \"or\");\n"],"mappings":"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"}
package/dist/policy.d.ts CHANGED
@@ -55,7 +55,7 @@ import { Actions, Context, PermitConfig, Policy, Resources } from "./types.js";
55
55
  * await policy.can(ctx, "post:write", post); // depends on ctx.user.id
56
56
  * ```
57
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>;
58
+ export 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
59
  /**
60
60
  * Merges multiple policies into one, requiring every policy to allow.
61
61
  * If any policy denies, the merged policy denies. Policies are evaluated
@@ -70,7 +70,7 @@ declare const createPolicy: <TContext extends Context, TResources extends Resour
70
70
  * // Both policies must allow for the action to be permitted
71
71
  * ```
72
72
  */
73
- declare const mergePoliciesAnd: <TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(...policies: Policy<TContext, TResources, TActions>[]) => Policy<TContext, TResources, TActions>;
73
+ export declare const mergePoliciesAnd: <TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(...policies: Policy<TContext, TResources, TActions>[]) => Policy<TContext, TResources, TActions>;
74
74
  /**
75
75
  * Merges multiple policies into one, requiring at least one policy to allow.
76
76
  * If every policy denies, the merged policy denies. Policies are evaluated
@@ -85,7 +85,6 @@ declare const mergePoliciesAnd: <TContext extends Context, TResources extends Re
85
85
  * // If either policy allows, the action is permitted
86
86
  * ```
87
87
  */
88
- declare const mergePoliciesOr: <TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(...policies: Policy<TContext, TResources, TActions>[]) => Policy<TContext, TResources, TActions>;
88
+ export declare const mergePoliciesOr: <TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>>(...policies: Policy<TContext, TResources, TActions>[]) => Policy<TContext, TResources, TActions>;
89
89
  //#endregion
90
- export { createPolicy, mergePoliciesAnd, mergePoliciesOr };
91
90
  //# sourceMappingURL=policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","names":[],"sources":["../src/policy.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiHa,eACX,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,aAE/C,QAAQ,aAAa,UAAU,YAAY,cAC1C,OAAO,UAAU,YAAY;;;;;;;;;;;;;;;cAmKnB,mBACX,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,gBAE5C,UAAU,OAAO,UAAU,YAAY,gBACzC,OAAO,UAAU,YAAY;;;;;;;;;;;;;;;cAgBnB,kBACX,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,gBAE5C,UAAU,OAAO,UAAU,YAAY,gBACzC,OAAO,UAAU,YAAY"}
1
+ {"version":3,"file":"policy.d.ts","names":[],"sources":["../src/policy.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAiHa,eACX,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,aAE/C,QAAQ,aAAa,UAAU,YAAY,cAC1C,OAAO,UAAU,YAAY;;;;;;;;;;;;;;;qBAmKnB,mBACX,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,gBAE5C,UAAU,OAAO,UAAU,YAAY,gBACzC,OAAO,UAAU,YAAY;;;;;;;;;;;;;;;qBAgBnB,kBACX,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,gBAE5C,UAAU,OAAO,UAAU,YAAY,gBACzC,OAAO,UAAU,YAAY"}
package/dist/policy.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import "./errors.js";
2
- import { n as mergePoliciesAnd, r as mergePoliciesOr, t as createPolicy } from "./policy-BBfZBocp.js";
2
+ import { n as mergePoliciesAnd, r as mergePoliciesOr, t as createPolicy } from "./policy-Dby1nu6_.js";
3
3
  export { createPolicy, mergePoliciesAnd, mergePoliciesOr };
package/dist/types.d.ts CHANGED
@@ -11,7 +11,7 @@ import { Logger } from "@zap-studio/logger";
11
11
  * const decision: Decision = "allow";
12
12
  * ```
13
13
  */
14
- type Decision = "allow" | "deny";
14
+ export type Decision = "allow" | "deny";
15
15
  /**
16
16
  * Represents the context in which a policy decision is made.
17
17
  * Can include user information, environment, or any relevant data.
@@ -21,7 +21,7 @@ type Decision = "allow" | "deny";
21
21
  * type AppContext = Context<{ user: { id: string; role: string } }>;
22
22
  * ```
23
23
  */
24
- type Context<TContext = unknown> = TContext;
24
+ export type Context<TContext = unknown> = TContext;
25
25
  /**
26
26
  * Represents a role within the system.
27
27
  *
@@ -30,7 +30,7 @@ type Context<TContext = unknown> = TContext;
30
30
  * type AppRole = Role<"guest" | "user" | "admin">;
31
31
  * ```
32
32
  */
33
- type Role<TRole extends string = string> = TRole;
33
+ export type Role<TRole extends string = string> = TRole;
34
34
  /**
35
35
  * Represents a role hierarchy within the system.
36
36
  * Maps each role to an array of roles it inherits from.
@@ -46,7 +46,7 @@ type Role<TRole extends string = string> = TRole;
46
46
  * };
47
47
  * ```
48
48
  */
49
- type RoleHierarchy<TRole extends Role = Role> = Record<TRole, TRole[]>;
49
+ export type RoleHierarchy<TRole extends Role = Role> = Record<TRole, TRole[]>;
50
50
  /**
51
51
  * Type helper for defining resource schemas using Standard Schema.
52
52
  * Use with `satisfies` to ensure type safety when defining resources.
@@ -62,7 +62,7 @@ type RoleHierarchy<TRole extends Role = Role> = Record<TRole, TRole[]>;
62
62
  * } satisfies Resources;
63
63
  * ```
64
64
  */
65
- type Resources<TResourceKey extends string = string> = Record<TResourceKey, StandardSchemaV1>;
65
+ export type Resources<TResourceKey extends string = string> = Record<TResourceKey, StandardSchemaV1>;
66
66
  /**
67
67
  * Type helper for defining actions per resource.
68
68
  * Use with `satisfies` to ensure keys match the resource definitions.
@@ -77,7 +77,7 @@ type Resources<TResourceKey extends string = string> = Record<TResourceKey, Stan
77
77
  * } as const satisfies Actions<typeof resources>;
78
78
  * ```
79
79
  */
80
- type Actions<TResources extends Resources> = { [K in keyof TResources]: readonly string[]; };
80
+ export type Actions<TResources extends Resources> = { [K in keyof TResources]: readonly string[]; };
81
81
  /**
82
82
  * Infers the output type from a Standard Schema.
83
83
  *
@@ -86,7 +86,7 @@ type Actions<TResources extends Resources> = { [K in keyof TResources]: readonly
86
86
  * type Post = InferResource<typeof resources, "post">;
87
87
  * ```
88
88
  */
89
- type InferResource<TResources extends Resources, TResourceKey extends keyof TResources> = StandardSchemaV1.InferOutput<TResources[TResourceKey]>;
89
+ export type InferResource<TResources extends Resources, TResourceKey extends keyof TResources> = StandardSchemaV1.InferOutput<TResources[TResourceKey]>;
90
90
  /**
91
91
  * Infers the action union type for a specific resource.
92
92
  *
@@ -96,7 +96,7 @@ type InferResource<TResources extends Resources, TResourceKey extends keyof TRes
96
96
  * // "read" | "write" | "delete"
97
97
  * ```
98
98
  */
99
- type InferAction<TResources extends Resources, TActions extends Actions<TResources>, K extends keyof TActions> = TActions[K][number];
99
+ export type InferAction<TResources extends Resources, TActions extends Actions<TResources>, K extends keyof TActions> = TActions[K][number];
100
100
  /**
101
101
  * Infers the permission-string union for all resource/action combinations.
102
102
  *
@@ -106,7 +106,7 @@ type InferAction<TResources extends Resources, TActions extends Actions<TResourc
106
106
  * // "post:read" | "post:write" | "comment:read"
107
107
  * ```
108
108
  */
109
- type InferPermission<TResources extends Resources, TActions extends Actions<TResources>> = { [K in keyof TResources & keyof TActions]: `${K & string}:${InferAction<TResources, TActions, K> & string}`; }[keyof TResources & keyof TActions];
109
+ export type InferPermission<TResources extends Resources, TActions extends Actions<TResources>> = { [K in keyof TResources & keyof TActions]: `${K & string}:${InferAction<TResources, TActions, K> & string}`; }[keyof TResources & keyof TActions];
110
110
  /**
111
111
  * A function that determines whether a given action on a resource is allowed in a specific context.
112
112
  *
@@ -116,7 +116,7 @@ type InferPermission<TResources extends Resources, TActions extends Actions<TRes
116
116
  * post.visibility === "public" ? "allow" : "deny";
117
117
  * ```
118
118
  */
119
- type PolicyFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => Decision;
119
+ export type PolicyFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => Decision;
120
120
  /**
121
121
  * A function that evaluates a condition for a given action and resource in a specific context.
122
122
  *
@@ -126,11 +126,11 @@ type PolicyFn<TContext extends Context, TAction extends string = string, TResour
126
126
  * context.user.id === post.authorId;
127
127
  * ```
128
128
  */
129
- type ConditionFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => boolean;
129
+ export type ConditionFn<TContext extends Context, TAction extends string = string, TResource = unknown> = (context: TContext, action: TAction, resource: TResource) => boolean;
130
130
  /**
131
131
  * Call signatures for {@link hasRole}, preserving the with/without hierarchy overloads.
132
132
  */
133
- interface HasRoleFn {
133
+ export interface HasRoleFn {
134
134
  /**
135
135
  * Checks membership in `role` only, with no inherited roles.
136
136
  *
@@ -166,7 +166,7 @@ interface HasRoleFn {
166
166
  * };
167
167
  * ```
168
168
  */
169
- type ActionPolicyMap<TContext extends Context, TAction extends string = string, TResource = unknown> = { [A in TAction]?: PolicyFn<TContext, A, TResource>; };
169
+ export type ActionPolicyMap<TContext extends Context, TAction extends string = string, TResource = unknown> = { [A in TAction]?: PolicyFn<TContext, A, TResource>; };
170
170
  /**
171
171
  * Defines the rules for each resource and action combination.
172
172
  * Each resource key maps to an object where each action key maps to a policy function.
@@ -187,7 +187,7 @@ type ActionPolicyMap<TContext extends Context, TAction extends string = string,
187
187
  * };
188
188
  * ```
189
189
  */
190
- type Rules<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> = { [K in keyof TResources & keyof TActions]: ActionPolicyMap<TContext, InferAction<TResources, TActions, K>, InferResource<TResources, K>>; };
190
+ export type Rules<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> = { [K in keyof TResources & keyof TActions]: ActionPolicyMap<TContext, InferAction<TResources, TActions, K>, InferResource<TResources, K>>; };
191
191
  /**
192
192
  * Configuration object for creating a permit policy.
193
193
  *
@@ -202,7 +202,7 @@ type Rules<TContext extends Context, TResources extends Resources = Resources, T
202
202
  * };
203
203
  * ```
204
204
  */
205
- interface PermitConfig<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> {
205
+ export interface PermitConfig<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> {
206
206
  /**
207
207
  * Allowed actions per resource type. Determines which `resource:action`
208
208
  * permission strings are valid to check with `Policy.can(...)`.
@@ -241,12 +241,11 @@ interface PermitConfig<TContext extends Context, TResources extends Resources =
241
241
  * await policy.can(ctx, "post:read", postData); // true or false
242
242
  * ```
243
243
  */
244
- interface Policy<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> {
244
+ export interface Policy<TContext extends Context, TResources extends Resources = Resources, TActions extends Actions<TResources> = Actions<TResources>> {
245
245
  /**
246
246
  * Determines if the specified action is permitted on the resource in the given context.
247
247
  */
248
248
  can: <K extends keyof TResources & keyof TActions>(context: TContext, permission: `${K & string}:${InferAction<TResources, TActions, K> & string}`, resource: InferResource<TResources, K>) => Promise<boolean>;
249
249
  }
250
250
  //#endregion
251
- export { ActionPolicyMap, Actions, ConditionFn, Context, Decision, HasRoleFn, InferAction, InferPermission, InferResource, PermitConfig, Policy, PolicyFn, Resources, Role, RoleHierarchy, Rules };
252
251
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;KAmBY;;;;;;;;;;KAWA,QAAQ,sBAAsB;;;;;;;;;KAU9B,KAAK,iCAAiC;;;;;;;;;;;;;;;;KAiBtC,cAAc,cAAc,OAAO,QAAQ,OAAO,OAAO;;;;;;;;;;;;;;;;KAiBzD,UAAU,wCAAwC,OAC5D,cACA;;;;;;;;;;;;;;;KAiBU,QAAQ,mBAAmB,gBACpC,WAAW;;;;;;;;;KAWF,cACV,mBAAmB,WACnB,2BAA2B,cACzB,iBAAiB,YAAY,WAAW;;;;;;;;;;KAWhC,YACV,mBAAmB,WACnB,iBAAiB,QAAQ,aACzB,gBAAgB,YACd,SAAS;;;;;;;;;;KAWD,gBAAgB,mBAAmB,WAAW,iBAAiB,QAAQ,kBAE/E,WAAW,mBAAmB,cAC1B,cAAc,YAAY,YAAY,UAAU,uBAChD,mBAAmB;;;;;;;;;;KAWf,SACV,iBAAiB,SACjB,iCACA,wBACG,SAAS,UAAU,QAAQ,SAAS,UAAU,cAAc;;;;;;;;;;KAWrD,YACV,iBAAiB,SACjB,iCACA,wBACG,SAAS,UAAU,QAAQ,SAAS,UAAU;;;;UAKlC;;;;;;GAMd;IAAmB,MAAM,OAAO;KAAU,iCAAiC,qBAC1E,MAAM,OACL,YAAY,UAAU,SAAS;;;;;;;;GAShC;IAAmB,MAAM,QAAQ;KACjC,iCACA,qBACA,cAAc,OAAO,MAErB,MAAM,OACN,WAAW,cAAc,SACxB,YAAY,UAAU,SAAS;;;;;;;;;;;;;;;;;;KAmBxB,gBACV,iBAAiB,SACjB,iCACA,0BAEC,KAAK,WAAW,SAAS,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;KAuB7B,MACV,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,kBAE9C,WAAW,mBAAmB,WAAW,gBACxC,UACA,YAAY,YAAY,UAAU,IAClC,cAAc,YAAY;;;;;;;;;;;;;;;UAkBb,aACf,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ;;;;;EAM/C,SAAS;;;;;;;EAOT,SAAS;;;;;EAKT,WAAW;;;;;EAKX,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,YAAY,UAAU,eAC/D,UAAU,cAAc,YAAY,OACjC"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;YAmBY;;;;;;;;;;YAWA,QAAQ,sBAAsB;;;;;;;;;YAU9B,KAAK,iCAAiC;;;;;;;;;;;;;;;;YAiBtC,cAAc,cAAc,OAAO,QAAQ,OAAO,OAAO;;;;;;;;;;;;;;;;YAiBzD,UAAU,wCAAwC,OAC5D,cACA;;;;;;;;;;;;;;;YAiBU,QAAQ,mBAAmB,gBACpC,WAAW;;;;;;;;;YAWF,cACV,mBAAmB,WACnB,2BAA2B,cACzB,iBAAiB,YAAY,WAAW;;;;;;;;;;YAWhC,YACV,mBAAmB,WACnB,iBAAiB,QAAQ,aACzB,gBAAgB,YACd,SAAS;;;;;;;;;;YAWD,gBAAgB,mBAAmB,WAAW,iBAAiB,QAAQ,kBAE/E,WAAW,mBAAmB,cAC1B,cAAc,YAAY,YAAY,UAAU,uBAChD,mBAAmB;;;;;;;;;;YAWf,SACV,iBAAiB,SACjB,iCACA,wBACG,SAAS,UAAU,QAAQ,SAAS,UAAU,cAAc;;;;;;;;;;YAWrD,YACV,iBAAiB,SACjB,iCACA,wBACG,SAAS,UAAU,QAAQ,SAAS,UAAU;;;;iBAKlC;;;;;;GAMd;IAAmB,MAAM,OAAO;KAAU,iCAAiC,qBAC1E,MAAM,OACL,YAAY,UAAU,SAAS;;;;;;;;GAShC;IAAmB,MAAM,QAAQ;KACjC,iCACA,qBACA,cAAc,OAAO,MAErB,MAAM,OACN,WAAW,cAAc,SACxB,YAAY,UAAU,SAAS;;;;;;;;;;;;;;;;;;YAmBxB,gBACV,iBAAiB,SACjB,iCACA,0BAEC,KAAK,WAAW,SAAS,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;YAuB7B,MACV,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ,kBAE9C,WAAW,mBAAmB,WAAW,gBACxC,UACA,YAAY,YAAY,UAAU,IAClC,cAAc,YAAY;;;;;;;;;;;;;;;iBAkBb,aACf,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ;;;;;EAM/C,SAAS;;;;;;;EAOT,SAAS;;;;;EAKT,WAAW;;;;;EAKX,OAAO,MAAM,UAAU,YAAY;;;;;;;;;;;;;;;;;iBAkBpB,OACf,iBAAiB,SACjB,mBAAmB,YAAY,WAC/B,iBAAiB,QAAQ,cAAc,QAAQ;;;;EAK/C,MAAM,gBAAgB,mBAAmB,UACvC,SAAS,UACT,eAAe,cAAc,YAAY,YAAY,UAAU,eAC/D,UAAU,cAAc,YAAY,OACjC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zap-studio/permit",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "private": false,
5
5
  "description": "A type-safe, declarative, tree-shakeable authorization library for TypeScript with Standard Schema support.",
6
6
  "keywords": [
@@ -48,18 +48,18 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@opentelemetry/api": "^1.9.1",
51
- "@opentelemetry/context-async-hooks": "^2.10.0",
52
- "@opentelemetry/sdk-metrics": "^2.10.0",
53
- "@opentelemetry/sdk-trace-base": "^2.10.0",
51
+ "@opentelemetry/context-async-hooks": "^2.11.0",
52
+ "@opentelemetry/sdk-metrics": "^2.11.0",
53
+ "@opentelemetry/sdk-trace-base": "^2.11.0",
54
54
  "@zap-studio/logger": "2.1.0",
55
55
  "@zap-studio/typescript": "0.0.0",
56
- "tsdown": "^0.22.14",
56
+ "tsdown": "^0.23.0",
57
57
  "typescript": "^7.0.2",
58
- "vitest": "^4.1.11"
58
+ "vitest": "^5.0.0"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@opentelemetry/api": "^1.9.1",
62
- "@zap-studio/logger": "2.1.0"
62
+ "@zap-studio/logger": "^2.1.0"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "@zap-studio/logger": {