UNPKG

matchable

Version:

A utility to define and match on tagged unions (like enums with payloads) — safely.

189 lines (182 loc) 5.47 kB
/** * A mapping of variant names to their payload factory functions. * * Each key defines a variant constructor that returns an object payload. * Used as the input to `matchable()`. */ type VariantMap = Record<string, (...args: any[]) => Record<string, any>>; /** * Infers the union type from a `VariantMap`. * * Wraps each payload in a `{ tag: ... }` object for pattern matching. */ type InferUnion<M extends VariantMap> = { [K in keyof M]: M[K] extends (...args: any[]) => infer R ? R & { tag: K; } : never; }[keyof M & string]; /** * A handler map for `match()`, where each tag maps to a function. * * Can optionally include a `default` fallback. */ type MatchHandlers<U extends { tag: string; }, R> = { [K in Exclude<U["tag"], "default">]?: (value: Extract<U, { tag: K; }>) => R; } & { default?: (value: U) => R; }; /** * A more flexible match handler shape that allows partial matching * as long as a `default` handler is provided. */ type MatchWithDefault<U extends { tag: string; }, R> = MatchHandlers<U, R> | (Partial<Omit<MatchHandlers<U, R>, "default">> & { default: (value: U) => R; }); /** * Extracts the union type from a matchable instance. * * @example * const Result = matchable({ * Ok: (value: number) => ({ value }), * Err: (message: string) => ({ message }), * }); * * type ResultType = MatchableOf<typeof Result>; * // => { tag: "Ok"; value: number } | { tag: "Err"; message: string } */ type MatchableOf<T> = T extends { match: (value: infer U, ...args: any[]) => any; } ? U : never; /** * Extracts a specific variant from a matchable union. * * @example * type OkVariant = VariantOf<typeof Result, "Ok">; * // => { tag: "Ok"; value: number } */ type VariantOf<T, Tag extends string> = Extract<MatchableOf<T>, { tag: Tag; }>; /** * Extracts the union of all possible `tag` values from a matchable instance. * * @example * const Result = matchable({ * Ok: (value: number) => ({ value }), * Err: (error: string) => ({ error }), * }); * * type ResultTags = TagsOf<typeof Result>; * // => "Ok" | "Err" */ type TagsOf<T> = MatchableOf<T> extends { tag: infer Tag; } ? Tag : never; /** * Merges multiple match handlers into a single handler map, * preserving the shape expected by `.match()`. * * Useful when multiple tags share logic and should map * to the same function. You can still provide a `default` * fallback for any unhandled tags. * * @param matchable - A matchable instance with `_tags` and `match()` * * @param handlers - A partial set of handlers for specific tags, plus optional `default` * * @returns A `MatchWithDefault` handler map compatible with `match()` * * @example * const handlers = group(Result, { * Ok: handleSuccess, * Cached: handleSuccess, * Err: handleError, * }); * * const message = Result.match(value, handlers); */ declare function group<T extends { match: (value: any, handlers: any) => any; }, U extends MatchableOf<T>, R>(matchable: T & { _tags: readonly string[]; }, handlers: { [K in Exclude<U["tag"], "default">]?: (value: Extract<U, { tag: K; }>) => R; } & { default?: (value: U) => R; }): MatchWithDefault<U, R>; /** * Validates if a value matches one of the known tags. * * Useful for safely checking data loaded from storage, APIs, etc. * * @param matchable - The matchable instance with `_tags` * * @param value - The value to check * * @returns `true` if the value has a valid tag */ declare function isValid<T extends { _tags: readonly string[]; }>(matchable: T, value: unknown): value is { tag: T["_tags"][number]; }; declare const MATCHABLE_INTERNAL_ID: unique symbol; declare function serialize<M extends VariantMap>(value: InferUnion<M>): string; /** * Creates a tagged union and a type-safe matcher. * * Each key in `variants` becomes a constructor function that returns * an object with a `tag` and a payload. Use `match()` to handle variants * exhaustively, or include a `default` handler for partial matching. * * @param variants - A map of variant names to factory functions. * * @returns An object with: * - constructors for each variant * - a `match()` function for safe pattern matching * - a `is` object with type guards * - `_tags` listing supported tags * * @example * const Result = matchable({ * Ok: (value: number) => ({ value }), * Err: (message: string) => ({ message }), * }); * * const result = Result.Ok(42); * const msg = Result.match(result, { * Ok: ({ value }) => `✅ ${value}`, * Err: ({ message }) => `❌ ${message}`, * }); */ declare function matchable<const M extends VariantMap>(variants: M): { [K in keyof M & string]: (...args: Parameters<M[K]>) => ReturnType<M[K]> & { __matchable_id__: typeof MATCHABLE_INTERNAL_ID; tag: K; }; } & { _tags: (keyof M & string)[]; deserialize: (json: string) => { tag: keyof M & string; }; is: { [K_1 in keyof M & string]: (value: unknown) => value is { tag: K_1; }; } & { Valid: (value: unknown) => value is { tag: keyof M & string; }; }; match: <R>(value: InferUnion<M> & { __matchable_id__?: symbol; }, handlers: MatchWithDefault<InferUnion<M> & { __matchable_id__?: symbol; }, R>) => R; serialize: typeof serialize; }; export { type MatchableOf, type TagsOf, type VariantOf, group, isValid, matchable };