@beignet/core
Version:
Core framework primitives for Beignet
234 lines • 7.86 kB
TypeScript
/**
* @beignet/core/entitlements
*
* Provider-neutral product access primitives for Beignet applications.
*/
type MaybePromise<T> = T | Promise<T>;
/**
* Stable app-defined capability key, such as "issues.create".
*/
export type EntitlementKey = string;
/**
* Principal whose product access is being checked.
*/
export type EntitlementSubject = {
/**
* App-defined subject type, commonly "tenant" or "account".
*/
type: string;
/**
* Stable subject identifier.
*/
id: string;
};
/**
* Input for checking whether a subject has access to a capability.
*/
export type EntitlementCheckInput<TKey extends EntitlementKey = EntitlementKey> = {
/**
* Capability being checked.
*/
entitlement: TKey;
/**
* Tenant, account, or other app-owned subject whose product access is checked.
*/
subject: EntitlementSubject;
};
/**
* An entitlement decision that allows the capability.
*/
export type EntitlementAllowedDecision = {
allowed: true;
};
/**
* An entitlement decision that denies the capability.
*/
export type EntitlementDeniedDecision = {
allowed: false;
reason?: string;
code?: string;
details?: unknown;
};
/**
* Normalized entitlement decision.
*/
export type EntitlementDecision = EntitlementAllowedDecision | EntitlementDeniedDecision;
/**
* Value an entitlement resolver may return.
*/
export type EntitlementResolverResult = boolean | EntitlementDecision;
/**
* Entitlement port method that produced a decision.
*/
export type EntitlementDecisionSource = "can" | "inspect" | "require";
/**
* Optional context for entitlement checks.
*/
export type EntitlementCheckOptions<TContext = unknown> = {
/**
* Application context used for the check. Observers can use this to copy
* request correlation fields without coupling the resolver to app context.
*/
ctx?: TContext;
/**
* Source to report to observers. Port methods set this automatically.
*/
source?: EntitlementDecisionSource;
};
/**
* App-facing product access port.
*/
export type EntitlementsPort<TKey extends EntitlementKey = EntitlementKey, TContext = unknown> = {
/**
* Return only whether the capability is allowed.
*/
can(input: EntitlementCheckInput<TKey>, options?: EntitlementCheckOptions<TContext>): Promise<boolean>;
/**
* Return the full allow/deny decision without throwing.
*/
inspect(input: EntitlementCheckInput<TKey>, options?: EntitlementCheckOptions<TContext>): Promise<EntitlementDecision>;
};
/**
* Function that decides whether an entitlement is granted.
*/
export type EntitlementResolver<TKey extends EntitlementKey = EntitlementKey> = (input: EntitlementCheckInput<TKey>) => MaybePromise<EntitlementResolverResult>;
/**
* Best-effort entitlement decision observation emitted by an entitlements port.
*
* Observers are diagnostic only. They do not participate in entitlement
* control flow and thrown/rejected observer errors are ignored.
*/
export type EntitlementDecisionObservation<TContext = unknown, TKey extends EntitlementKey = EntitlementKey> = {
/**
* Port helper that produced the decision.
*/
source: EntitlementDecisionSource;
/**
* Application context passed by the caller, when available.
*/
ctx?: TContext;
/**
* Capability being evaluated.
*/
entitlement: TKey;
/**
* Subject whose product access is checked.
*/
subject: EntitlementSubject;
/**
* Normalized decision, when resolver evaluation returned normally.
*/
decision?: EntitlementDecision;
/**
* Error thrown by the resolver, when evaluation failed.
*/
error?: unknown;
/**
* Resolver duration, excluding observer work.
*/
durationMs: number;
/**
* Correlation fields copied from the context when present.
*/
requestId?: string;
traceId?: string;
spanId?: string;
parentSpanId?: string;
traceparent?: string;
};
/**
* Best-effort observer called after each entitlement decision or resolver
* error.
*/
export type EntitlementDecisionObserver<TContext = unknown, TKey extends EntitlementKey = EntitlementKey> = (observation: EntitlementDecisionObservation<TContext, TKey>) => MaybePromise<void>;
/**
* Options for `createEntitlements(...)`.
*/
export type CreateEntitlementsOptions<TKey extends EntitlementKey = EntitlementKey, TContext = unknown> = {
/**
* Resolver that maps app-owned product state to an entitlement decision.
*/
inspect: EntitlementResolver<TKey>;
/**
* Optional best-effort observer for entitlement decisions.
*
* This hook is diagnostic only: it cannot change decisions or thrown errors.
*/
onDecision?: EntitlementDecisionObserver<TContext, TKey>;
};
/**
* Static grant map keyed by subject key.
*/
export type StaticEntitlementGrantMap<TKey extends EntitlementKey = EntitlementKey> = Record<string, readonly TKey[]>;
/**
* Options for `createStaticEntitlements(...)`.
*/
export type CreateStaticEntitlementsOptions<TKey extends EntitlementKey = EntitlementKey, TContext = unknown> = {
/**
* Granted capabilities keyed by subject. The default key is
* `${subject.type}:${subject.id}`.
*/
grants: StaticEntitlementGrantMap<TKey>;
/**
* Optional subject key mapper.
*/
subjectKey?: (subject: EntitlementSubject) => string;
/**
* Default denial decision fields.
*/
denied?: Omit<EntitlementDeniedDecision, "allowed">;
/**
* Optional best-effort observer for entitlement decisions.
*/
onDecision?: EntitlementDecisionObserver<TContext, TKey>;
};
/**
* Options accepted by `requireEntitlement(...)`.
*/
export type RequireEntitlementOptions<TKey extends EntitlementKey = EntitlementKey> = {
/**
* Create the error to throw instead of the framework default.
*/
error?: (decision: EntitlementDeniedDecision, input: EntitlementCheckInput<TKey>) => unknown;
};
/**
* Context shape consumed by `requireEntitlement(...)`.
*/
export type EntitlementsContext<TKey extends EntitlementKey = EntitlementKey> = {
ports: {
entitlements: EntitlementsPort<TKey>;
};
};
/**
* Error thrown by `requireEntitlement(...)` when product access is denied.
*/
export declare class EntitlementRequiredError extends Error {
readonly code: string;
readonly status = 403;
readonly details?: unknown;
readonly entitlement: string;
readonly subject: EntitlementSubject;
constructor(input: EntitlementCheckInput, decision?: EntitlementDeniedDecision);
}
/**
* Create an explicit allow decision.
*/
export declare function allowEntitlement(): EntitlementAllowedDecision;
/**
* Create an explicit deny decision.
*/
export declare function denyEntitlement(reasonOrDecision?: string | Omit<EntitlementDeniedDecision, "allowed">): EntitlementDeniedDecision;
/**
* Create an entitlement port from an app-owned resolver.
*/
export declare function createEntitlements<TKey extends EntitlementKey = EntitlementKey, TContext = unknown>(options: CreateEntitlementsOptions<TKey, TContext>): EntitlementsPort<TKey, TContext>;
/**
* Create a static entitlement port for tests, starters, and simple apps.
*/
export declare function createStaticEntitlements<TKey extends EntitlementKey = EntitlementKey, TContext = unknown>(options: CreateStaticEntitlementsOptions<TKey, TContext>): EntitlementsPort<TKey, TContext>;
/**
* Require an entitlement or throw a framework-owned 403 error.
*/
export declare function requireEntitlement<TKey extends EntitlementKey = EntitlementKey>(ctx: EntitlementsContext<TKey>, input: EntitlementCheckInput<TKey>, options?: RequireEntitlementOptions<TKey>): Promise<EntitlementAllowedDecision>;
export {};
//# sourceMappingURL=index.d.ts.map