UNPKG

betterthrow

Version:

Define the errors your program can generate, create beautiful unions.

353 lines (352 loc) 15.8 kB
import { ResultLikeSymbol, type ResultLikeUnion } from "retuple-symbols"; export type betterthrow = typeof betterthrow; export type error = typeof error; export type group = typeof group; export type ErrorCodes<T extends BetterThrowInfer> = T extends BetterThrowInfer<infer TCodes, any, any> ? TCodes : never; export type Errors<T extends BetterThrowInfer, K extends ErrorCodes<T> = ErrorCodes<T>> = T extends BetterThrowInfer<any, infer TInstances, any> ? Pick<TInstances, K>[K] : never; export type ErrorObjects<T extends BetterThrowInfer, K extends ErrorCodes<T> = ErrorCodes<T>> = T extends BetterThrowInfer<any, any, infer TObjects> ? Pick<TObjects, K>[K] : never; export declare function betterthrow<TKind extends string = "">(kind?: validKeyword<TKind, "class kind">): ClassBuilderPrefix<TKind extends "" ? null : TKind>; export declare function group(): GroupBuilderContext<"">; export declare function group<TPrefix extends string>(prefix: TPrefix): GroupBuilderContext<TPrefix>; export declare function error<TCode extends string>(code: validKeyword<TCode, "error code">): ErrorBuilderContext<TCode, { message: string; }>; export declare function error<TCode extends string, TMessage extends string>(code: validKeyword<TCode, "error code">, message: nonEmptyString<TMessage>): ErrorBuilderContext<TCode, { message?: string; }>; export declare const RESERVED_WORDS: readonly ["__proto__", "prototype", "constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf", "name", "message", "cause", "stack", "toJSON", "toPlainObject", "kind", "code"]; declare const INTERNAL: unique symbol; declare const INFER: unique symbol; declare abstract class BetterThrowError extends Error { code: string; kind: string | null; constructor(message: string, cause?: unknown); [ResultLikeSymbol](): ResultLikeUnion<never, this>; toPlainObject(): PlainObject<this>; } type INFER = typeof INFER; type RESERVED_WORDS = (typeof RESERVED_WORDS)[number]; type BaseContext = Record<string, any>; type ErrorMap = Record<string, ErrorInfer>; type ErrorDefs = readonly [ErrorDefinitions, ...ErrorDefinitions[]]; interface ErrorDefinitions<TErrMap extends ErrorMap = any> { [INFER]: TErrMap; [INTERNAL](this: this): readonly ErrorSettings[]; } interface ErrorInfer<TKind extends string | null = any, TCode extends string = any, TCtx extends BaseContext = any, TData extends BaseContext = any, TMessage extends { message?: string; } = any, TStatic extends string = any, TStrict extends BaseContext = {}, TReadonly extends boolean = true> { kind: TKind; code: TCode; context: TCtx; data: TData; message: TMessage; static: TStatic; strict: TStrict; readonly: TReadonly; blank: { [K in keyof TCtx]?: never; }; } interface BetterThrowInfer<TCodes extends string = any, TInstances extends Record<string, BaseContext> = any, TObjects extends Record<string, BaseContext> = any> { [INFER]: { codes: TCodes; instances: TInstances; objects: TObjects; }; } type MessageOption<TCtx = any> = string | ((ctx: TCtx) => string); type MessagesSetting = { type: "function"; message: (ctx: BaseContext) => string; } | { type: "string"; message: string; } | { type: "object"; message: Record<string, MessageOption>; }; type DataOption = BaseContext | (() => BaseContext); type DataChain = readonly DataOption[]; type JsonFormatFn<TErrMap extends ErrorMap> = (error: BetterThrowError & InstanceProps<TErrMap[keyof TErrMap]>) => unknown; interface ClassSettings { kind: string | null; prefix: string; data: BaseContext | (() => BaseContext) | undefined; errors: readonly ErrorDefinitions[]; messages: MessagesSetting | undefined; json: JsonFormatFn<any> | boolean; props: readonly string[] | undefined; } interface GroupSettings { prefix: string; errors: readonly ErrorDefinitions[]; data: BaseContext | (() => BaseContext) | undefined; } interface ErrorSettings { code: string; message: string | undefined; chain: DataChain | undefined; } type ConstructContext<TErr extends ErrorInfer> = pretty<TErr["context"] & TErr["message"] & TErr["strict"] & { cause?: unknown; }>; type MessageContext<TErrMap extends ErrorMap> = { [K in keyof TErrMap]: InstanceProps<TErrMap[K]>; }[keyof TErrMap]; type BlankContext<TErrMap extends ErrorMap> = unionToIntersection<TErrMap[keyof TErrMap]["blank"]>; type InstanceTypes<TErrMap extends ErrorMap> = { [K in keyof TErrMap]: BetterThrowError & InstanceProps<TErrMap[K]>; }; type InstanceProps<TErr extends ErrorInfer> = pretty<{ kind: TErr["kind"]; code: TErr["code"]; } & mergeToData<TErr["context"], TErr["data"]>>; type ConstructArgs<TErr extends ErrorInfer> = Partial<ConstructContext<TErr>> extends ConstructContext<TErr> ? [params?: ConstructContext<TErr>] : [params: ConstructContext<TErr>]; type StaticProps<TErrMap extends ErrorMap> = pretty<{ [TCode in keyof TErrMap & string as toPascalCase<TErrMap[TCode]["static"]>]: { new (...args: ConstructArgs<TErrMap[TCode]>): BetterThrowError & InstanceProps<TErrMap[TCode]>; }; } & { readonly kind: TErrMap[keyof TErrMap]["kind"]; readonly codes: readonly (keyof TErrMap & string)[]; is: (value: unknown) => value is { [TCode in keyof TErrMap & string]: BetterThrowError & InstanceProps<TErrMap[TCode]>; }[keyof TErrMap & string]; } & BetterThrowInfer<keyof TErrMap & string, InstanceTypes<TErrMap>, { [K in keyof TErrMap]: PlainObject<InstanceProps<TErrMap[K]>>; }>>; type PlainObject<T extends BaseContext> = pretty<Omit<T, ResultLikeSymbol | "is" | Exclude<RESERVED_WORDS, "kind" | "code" | "message">>>; /** * @TODO */ declare class ClassBuilderBuild<TErrMap extends ErrorMap> { protected settings: ClassSettings; constructor(settings: ClassSettings); /** * ## Build * * @TODO */ build({ classNames }?: { classNames?: boolean; }): { new <TCode extends keyof TErrMap = keyof TErrMap>(params: { code: TCode; } & ConstructContext<TErrMap[TCode]>): InstanceTypes<TErrMap>[TCode]; } & StaticProps<TErrMap>; } /** * @TODO */ declare class ClassBuilderTypes<TErrMap extends ErrorMap> extends ClassBuilderBuild<TErrMap> { /** * ## Types * * @TODO */ types<TInput extends "exact" | "extended", TOutput extends "exact" | "extended">(this: ClassBuilderTypes<TErrMap>, types: { input?: TInput; output?: TOutput; }): ClassBuilderBuild<{ [K in keyof TErrMap]: ErrorInfer<TErrMap[K]["kind"], TErrMap[K]["code"], TErrMap[K]["context"], TOutput extends "extended" ? pretty<TErrMap[K]["data"] & Omit<BlankContext<TErrMap>, keyof TErrMap[K]["data"]>> : TErrMap[K]["data"], TErrMap[K]["message"], TErrMap[K]["static"], TInput extends "extended" ? pretty<Omit<BlankContext<TErrMap>, keyof TErrMap[K]["context"]>> : TErrMap[K]["strict"]>; }>; } declare class ClassBuilderFilter<TErrMap extends ErrorMap> extends ClassBuilderTypes<TErrMap> { /** * ## Filter * * @TODO */ filter(this: ClassBuilderFilter<TErrMap>, filter: Record<keyof BlankContext<TErrMap>, true>): ClassBuilderTypes<TErrMap>; } /** * @TODO */ declare class ClassBuilderJson<TErrMap extends ErrorMap> extends ClassBuilderFilter<TErrMap> { /** * ## JSON * * @TODO */ json(this: ClassBuilderJson<TErrMap>, enabled: boolean): ClassBuilderFilter<TErrMap>; json(this: ClassBuilderJson<TErrMap>, formatter: JsonFormatFn<TErrMap>): ClassBuilderFilter<TErrMap>; } /** * @TODO */ declare class ClassBuilderMessages<TErrMap extends ErrorMap> extends ClassBuilderJson<TErrMap> { /** * ## Messages * * @TODO */ messages<const TMsgs extends { readonly [K in keyof TErrMap & string]?: MessageOption<Readonly<InstanceProps<TErrMap[K]>>>; }>(this: ClassBuilderMessages<TErrMap>, message: TMsgs): ClassBuilderJson<{ [K in keyof TErrMap]: ErrorInfer<TErrMap[K]["kind"], TErrMap[K]["code"], TErrMap[K]["context"], TErrMap[K]["data"], K extends keyof TMsgs ? { message?: string; } : TErrMap[K]["message"], TErrMap[K]["static"]>; }>; messages(this: ClassBuilderMessages<TErrMap>, messages: (ctx: Readonly<MessageContext<TErrMap>>) => string): ClassBuilderJson<{ [K in keyof TErrMap]: ErrorInfer<TErrMap[K]["kind"], TErrMap[K]["code"], TErrMap[K]["context"], TErrMap[K]["data"], { message?: string; }, TErrMap[K]["static"]>; }>; messages<TMessage extends string>(this: ClassBuilderMessages<TErrMap>, messages: nonEmptyString<TMessage>): ClassBuilderJson<{ [K in keyof TErrMap]: ErrorInfer<TErrMap[K]["kind"], TErrMap[K]["code"], TErrMap[K]["context"], TErrMap[K]["data"], { message?: string; }, TErrMap[K]["static"]>; }>; private getMessagesSetting; } /** * @TODO */ declare class ClassBuilderErrors<TKind extends string | null, TPrefix extends string, TCtx extends BaseContext, TData extends BaseContext> { protected settings: ClassSettings; constructor(settings: ClassSettings); /** * ## Errors * * @TODO */ errors<TErrs extends ErrorDefs>(...errors: TErrs): ClassBuilderMessages<{ [K in keyof toErrMap<TErrs> & string as toPrefixed<TPrefix, K>]: ErrorInfer<TKind, toPrefixed<TPrefix, K>, pretty<mergeToContext<overwrite<TCtx, toErrMap<TErrs>[K]["context"]>, overwrite<TData, toErrMap<TErrs>[K]["data"]>>>, pretty<overwrite<TData, toErrMap<TErrs>[K]["data"]>>, toErrMap<TErrs>[K]["message"], toErrMap<TErrs>[K]["static"]>; }>; } /** * @TODO */ declare class ClassBuilderData<TKind extends string | null, TPrefix extends string, TCtx extends BaseContext> extends ClassBuilderErrors<TKind, TPrefix, TCtx, {}> { /** * ## Data * * @TODO */ data<const TData extends BaseContext>(data: (TData & validObjectKeys<TData, "class", "data">) | (() => TData)): ClassBuilderErrors<TKind, TPrefix, mergeToContext<TCtx, validObjectKeys<TData, "class", "data">>, validObjectKeys<TData, "class", "data">>; } /** * @TODO */ declare class ClassBuilderContext<TKind extends string | null, TPrefix extends string> extends ClassBuilderData<TKind, TPrefix, {}> { /** * ## Context * * @TODO */ context<TCtx extends BaseContext = {}>(): ClassBuilderData<TKind, TPrefix, validObjectKeys<TCtx, "class", "context">>; } /** * @TODO */ declare class ClassBuilderPrefix<TKind extends string | null> extends ClassBuilderContext<TKind, ""> { /** * ## Prefix * * @TODO */ prefix<TPrefix extends string>(prefix: TPrefix): ClassBuilderContext<TKind, TPrefix>; } /** * ## Group Definition * * @TODO */ declare class GroupDefinition<TErrMap extends ErrorMap> implements ErrorDefinitions<TErrMap> { protected settings: GroupSettings; [INFER]: TErrMap; constructor(settings: GroupSettings); [INTERNAL](): readonly ErrorSettings[]; } /** * @TODO */ declare class GroupBuilderErrors<TPrefix extends string, TCtx extends BaseContext, TData extends BaseContext> { protected settings: GroupSettings; constructor(settings: GroupSettings); /** * ## Errors * * @TODO */ errors<TErrs extends ErrorDefs>(...errors: TErrs): GroupDefinition<{ [K in keyof toErrMap<TErrs> & string as toPrefixed<TPrefix, K>]: ErrorInfer<never, toPrefixed<TPrefix, K>, pretty<mergeToContext<overwrite<TCtx, toErrMap<TErrs>[K]["context"]>, overwrite<TData, toErrMap<TErrs>[K]["data"]>>>, pretty<overwrite<TData, toErrMap<TErrs>[K]["data"]>>, toErrMap<TErrs>[K]["message"], toPrefixed<TPrefix, K>>; }>; } /** * @TODO */ declare class GroupBuilderData<TPrefix extends string, TCtx extends BaseContext> extends GroupBuilderErrors<TPrefix, TCtx, {}> { /** * ## Data * * @TODO */ data<const TData extends BaseContext>(data: (TData & validObjectKeys<TData, "group", "data", TPrefix>) | (() => TData)): GroupBuilderErrors<TPrefix, mergeToContext<TCtx, validObjectKeys<TData, "group", "data", TPrefix>>, validObjectKeys<TData, "group", "data", TPrefix>>; } declare class GroupBuilderContext<TPrefix extends string> extends GroupBuilderData<TPrefix, {}> { /** * ## Context * * @TODO */ context<TCtx extends BaseContext = {}>(): GroupBuilderData<TPrefix, validObjectKeys<TCtx, "group", "context", TPrefix>>; } /** * ## Error Definition * * @TODO */ declare class ErrorDefinition<TErrMap extends ErrorMap> implements ErrorDefinitions<TErrMap> { protected settings: ErrorSettings; [INFER]: TErrMap; constructor(settings: ErrorSettings); [INTERNAL](): [ErrorSettings]; } declare class ErrorBuilderData<TCode extends string, TCtx extends BaseContext, TMessage extends { message?: string; }> extends ErrorDefinition<{ [K in TCode]: ErrorInfer<never, TCode, TCtx, {} /** @TODO data */, TMessage, TCode>; }> { /** * ## Data * * @TODO */ data<const TData extends BaseContext>(data: (TData & validObjectKeys<TData, "error", "data", TCode>) | (() => TData)): ErrorDefinition<{ [K in TCode]: ErrorInfer<never, TCode, mergeToContext<TCtx, validObjectKeys<TData, "error", "data", TCode>>, validObjectKeys<TData, "error", "data", TCode>, TMessage, TCode>; }>; } declare class ErrorBuilderContext<TCode extends string, TMessage extends { message?: string; }> extends ErrorBuilderData<TCode, {}, TMessage> { /** * ## Context * * @TODO */ context<TCtx extends BaseContext = {}>(): ErrorBuilderData<TCode, validObjectKeys<TCtx, "error", "context", TCode>, TMessage>; } declare function toPascalCase(code: string): string; type toErrMap<TErrs extends ErrorDefs> = unionToIntersection<TErrs[number][INFER]>; type mergeToContext<TCtx extends BaseContext, TData extends BaseContext> = { [K in keyof TCtx & keyof TData]?: TCtx[K]; } & Omit<TCtx, keyof TData>; type mergeToData<TCtx extends BaseContext, TData extends BaseContext> = { [K in keyof TCtx & keyof TData]: Exclude<TCtx[K], undefined> | TData[K]; } & Omit<TCtx, keyof TData> & Omit<TData, keyof TCtx>; type toPrefixed<TPrefix extends string, TCode extends string> = TPrefix extends "" ? TCode : `${TPrefix}_${TCode}`; type toPascalCase<T extends string> = T extends `${infer TWord}_${infer Rest}` ? `${Capitalize<Lowercase<TWord>>}${toPascalCase<Rest>}` : Capitalize<Lowercase<T>>; type validKeyword<T extends string, MTarget extends "class kind" | "class prefix" | "group prefix" | "error code"> = T extends `${string} ${string}` ? `BetterThrow: ${Capitalize<MTarget>} '${T}' must not contain spaces.` : nonEmptyString<T>; type nonEmptyString<T extends string> = T extends "" ? never : T; type validObjectKeys<TCtx extends BaseContext, MTarget extends "class" | "group" | "error", MObject extends "context" | "data", MName extends string = ""> = pretty<{ [K in keyof TCtx as K extends RESERVED_WORDS ? `BetterThrow: '${K}' is reserved and can't be used as a ${MObject} property (on ${MTarget}${getNameMessage<MName>}).` : K extends symbol ? `BetterThrow: Symbols can't be used as a ${MObject} property (on ${MTarget}${getNameMessage<MName>}).` : K]: K extends RESERVED_WORDS | symbol ? never : TCtx[K]; }>; type getNameMessage<MName extends string> = MName extends "" ? "" : ` '${MName}'`; type pretty<T> = { -readonly [K in keyof T]: T[K]; } & {}; type overwrite<TBase extends BaseContext, TExt extends BaseContext> = Omit<TBase, keyof TExt> & TExt; type unionToIntersection<TUnion> = (TUnion extends any ? (union: TUnion) => any : never) extends (intersection: infer TIntersection extends Record<string, any>) => any ? TIntersection : never; export {};