/** * Base class for all domain errors, i.e. intended failures as opposed to * unexpected errors. Errors created by {@link createDomainError} extend this. */ export class DomainError extends Error { readonly code?: string; constructor(message: string, options?: ErrorOptions & { code?: string }) { super(message, options); if (options?.code !== undefined) this.code = options.code; } } export function createDomainError< TName extends string, TCode extends string, TArgs extends unknown[], >(name: TName, code: TCode, messageFactory: (...args: TArgs) => string) { const ErrorClass = class extends DomainError { override name: TName = name; override readonly code: TCode = code; constructor(...args: TArgs) { super(messageFactory(...args)); } }; Object.defineProperty(ErrorClass, "name", { value: name }); return ErrorClass; } export const InsufficientPermissionError = createDomainError( "InsufficientPermissionError", "INSUFFICIENT_PERMISSION", (actorId: string, requiredPermission: string) => `Actor ${actorId} lacks required permission: ${requiredPermission}`, ); /** * The caller has no actor, so there is nothing to attribute the work to. * * Returned by the gate in `defineCommand` and in the permission-gated form of * `defineQuery` — `createContext` only translates a resolver context and never * decides this. It arrives as a `Result` rather than as a thrown error because a * resolver that declares no `permission` is reachable by an anonymous caller, so * an absent actor is ordinary external input, not a broken invariant. * * The message is safe to surface as-is — unlike {@link InsufficientPermissionError}, * it embeds neither an actor id nor an internal permission scope. */ export const UnauthenticatedError = createDomainError( "UnauthenticatedError", "UNAUTHENTICATED", () => "Authentication is required", );