UNPKG

opinionated-machine

Version:

Very opinionated DI framework for fastify, built on top of awilix

51 lines (50 loc) 2.25 kB
/** * Type-level utility: extract a specific variant from a discriminated union. */ export type ExtractMetadata<TMetadata, TField extends keyof TMetadata, TValue extends TMetadata[TField]> = Extract<TMetadata, Record<TField, TValue>>; /** * A type guard function for a specific variant of event metadata. */ export type MetadataGuard<TMetadata, TVariant extends TMetadata> = (metadata: TMetadata) => metadata is TVariant; /** * Map of discriminant values to their corresponding type guard functions. * * Keyed by `TValues` — the actual list of values passed to `defineEventMetadata` — * not by the full `TMetadata[TField]` union. That way, accessing a guard for * an omitted variant is a type error instead of an `undefined` at runtime. */ export type MetadataGuards<TMetadata, TField extends keyof TMetadata, TValues extends TMetadata[TField] & (string | number)> = { [V in TValues]: MetadataGuard<TMetadata, ExtractMetadata<TMetadata, TField, V>>; }; /** * Create type-safe guard functions for a discriminated union metadata type. * * Returns an object mapping each discriminant value to a type guard function. * When a guard returns true, TypeScript narrows the metadata to the specific * variant, giving access to variant-specific fields. * * The double-invocation `defineEventMetadata<Type>()(field, values)` is needed * because TypeScript doesn't support partial type inference. * * @template TMetadata - The full discriminated union type * * @example * ```typescript * type EventMetadata = * | { scope: 'project'; projectId: string } * | { scope: 'team'; teamId: string } * | { scope: 'global' } * * const metadata = defineEventMetadata<EventMetadata>()('scope', [ * 'project', * 'team', * 'global', * ]) * * if (metadata.project(event.metadata)) { * // TypeScript narrows: event.metadata is { scope: 'project'; projectId: string } * event.metadata.projectId // string * } * ``` */ export declare function defineEventMetadata<TMetadata extends Record<string, unknown>>(): <TField extends keyof TMetadata & string, const TValues extends ReadonlyArray<TMetadata[TField] & (string | number)>>(field: TField, values: TValues) => MetadataGuards<TMetadata, TField, TValues[number]>;