/* eslint-disable max-lines */ import { type Opaque, type Primitive, type Simplify, type SnakeCase, type UnwrapOpaque, } from 'type-fest'; import { __throw } from './misc.js'; export type WithUndefined = { [K in keyof T]?: undefined }; export type { SnakeCase as CamelToSnakeCase, CamelCase as SnakeToCamelCase, ReadonlyDeep, } from 'type-fest'; /** * Equivalent to SnakeCasePropertiesDeep from type-fest, except that it also * recursively transforms the keys of objects in array/tuple types. */ export type SnakeCasedPropertiesDeepWithArrays = T extends | readonly [] | readonly [...never[]] ? readonly [] : T extends readonly [infer U, ...infer V] ? readonly [ SnakeCasedPropertiesDeepWithArrays, ...SnakeCasedPropertiesDeepWithArrays, ] : T extends readonly [...infer U, infer V] ? readonly [ ...SnakeCasedPropertiesDeepWithArrays, SnakeCasedPropertiesDeepWithArrays, ] : T extends ReadonlyArray ? ReadonlyArray> : T extends object ? { [K in keyof T as SnakeCase]: SnakeCasedPropertiesDeepWithArrays } : T; export type StringKeys = keyof T & string; export type RequiredWithoutNull = { [P in keyof T]-?: Exclude; }; export type Mutable = T extends readonly (infer U)[] ? U[] : { -readonly [K in keyof T]: T[K] }; /** * Synchronous iterables can be used as async iterables (i.e., code can await * the values they yield and it's a no-op), so functions that sometimes need to * accept async iterables should also be able to be called with a sync iterable; * this makes that easier to type. */ export type IterableOrAsyncIterable = Iterable | AsyncIterable; /** * Extracts the public method names from a type T, returning a union of string * literal types. Also works on plain objects, for which it returns the name of * any function-holding properties. */ export type PublicMethodNames = { // eslint-disable-next-line @typescript-eslint/no-explicit-any [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; }[keyof T]; /** * Returns a value cast to be typed as a compatible opaque type. * * @template OpaqueType The opaque type to cast the given value `value` to. * @param value The value that is to be cast to the given opaque type. */ export function instantiateOpaqueType>( value: UnwrapOpaque, ): OpaqueType { return value as OpaqueType; } /** * Takes a value typed as an opaque type and returns it cast to its runtime * representation. * * @param value The opaque value that is to be casted to its runtime type. */ export function unwrapOpaqueValue>( value: OpaqueType, ): UnwrapOpaque { return value as UnwrapOpaque; } // A convenient helper for building types that have different values based on // some boolean generic. export type If = Cond extends true ? IfTrue : Cond extends false ? IfFalse : IfTrue | IfFalse; export type NullableKeysOf = { [K in keyof T]-?: null extends T[K] ? K : never; }[keyof T]; export type Bind1< F extends (arg0: A0, ...args: never[]) => unknown, A0 = never, > = F extends (arg0: A0, ...args: infer Args) => infer R ? (...args: Args) => R : never; export type Bind2< F extends (arg0: A0, arg1: A1, ...args: never[]) => unknown, A0 = never, A1 = never, > = F extends (arg0: A0, arg1: A1, ...args: infer Args) => infer R ? (...args: Args) => R : never; export type Bind3< F extends (arg0: A0, arg1: A1, arg2: A2, ...args: never[]) => unknown, A0 = never, A1 = never, A2 = never, > = F extends (arg0: A0, arg1: A1, arg2: A2, ...args: infer Args) => infer R ? (...args: Args) => R : never; export type Bind4< F extends ( arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: never[] ) => unknown, A0 = never, A1 = never, A2 = never, A3 = never, > = F extends ( arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: infer Args ) => infer R ? (...args: Args) => R : never; /** * Takes a tuple type, and returns a new tuple type with the first N elements * removed. */ export type DropN = N extends 0 ? T : T extends readonly [unknown, ...infer U] // @ts-ignore ? DropN>>> : never; // Types for doing math in TS, by representing each natural number as a tuple // type with n elements, and exploiting its ability to track a tuple's length // as a number literal type. type Zero = readonly []; type SomeNat = readonly [...unknown[]]; type Succ = readonly [...N, unknown]; type Dec = N extends readonly [unknown, ...infer T] ? T : never; type Nat2Number = N['length']; type Number2Nat< I extends number, N extends SomeNat = Zero, > = I extends Nat2Number ? N : Number2Nat>; // type NumericToNat = I extends `${infer T extends number}` // ? Number2Nat // : never; // type Min2 = (( // ...args: N // ) => any) extends (...args: M) => any // ? N // : M; // type Min = T extends [ // infer M, // infer N, // ...infer R extends SomeNat[], // ] // ? //@ts-ignore // Min2> // : T extends [infer M, infer N] // ? Min2 // : T extends [infer M] // ? M // : Zero; // type Max2 = (( // ...args: N // ) => any) extends (...args: M) => any // ? M // : N; // type MaxOfUnion = Nat2Number< // Parameters< // UnionToIntersection<{ [K in It]: (...it: Number2Nat) => any }[It]> // > // >; // type MinOfUnion = Exclude> extends never // ? It // : MinOfUnion>>; // type Add = readonly [...N, ...M]; // type Multiply = M extends Zero // ? Zero // : Add>>; // type Subtract = M extends Zero // ? N // : Subtract, Dec>; /** * If you have an object type w/ a call signature but also some extra properties * (e.g., `{ (): Promise, restoreOriginal: () => void }`), this returns * just the call signature (`() => Promise` in the example above). */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export type CallSignature any> = ( ...args: Parameters ) => ReturnType; export type NonEmptyString = Opaque; export function isNonEmptyString(it: unknown): it is NonEmptyString { return typeof it === 'string' && it !== ''; } export function tryParseNonEmptyString(it: unknown) { return isNonEmptyString(it) ? it : __throw(new Error('Was not an NonEmptyString')); } export function areAllValuesNonEmptyStrings( it: T, ): it is { [K in keyof T]: T[K] & NonEmptyString } { return Object.values(it).every(isNonEmptyString); } export function areAllArrayValuesNonEmptyString( it: readonly T[], ): it is readonly (T & NonEmptyString)[] { return it.every(isNonEmptyString); } export type NonEmptyArray = [T, ...T[]]; // This overload is needed to help TS reduce the resulting type properly. I.e., // if `isNonEmptyArray` is called with a mutable `T[]`, the result will be: // `T[] & readonly NonEmptyArray`, which TS can't simplify neatly to // NonEmptyArray. export function isNonEmptyArray(arr: Array): arr is NonEmptyArray; export function isNonEmptyArray( arr: ReadonlyArray, ): arr is Readonly>; export function isNonEmptyArray( arr: Readonly>, ): arr is Readonly> { return arr.length > 0; } /* eslint-disable @typescript-eslint/no-explicit-any */ export type UnionToIntersection = ( T extends any ? (x: T) => any : never ) extends (x: infer R) => any ? R : never; /* eslint-enable @typescript-eslint/no-explicit-any */ export type RenameEach< Union, Renames extends { [K in string]: keyof Union }, > = Union extends object ? { [K in keyof Renames]: Union[Renames[K]] } : never; /** * This type is exactly like Pick, except that it takes a union type as its * first argument, and returns a union of the picked types. Concretely: * * ``` * Pick< * { id: string, x: string, y: any } | { id: number, x: number, y: any }, * 'id' | 'x' * > * ``` * * returns * * ``` * { id: string | number, x: string | number } * ``` * * Whereas `PickEach` preserves the original structure, giving * * { id: string, x: string } | { id: number, x: number } * * The difference is that the second type, correctly, rejects an object like * `{ id: 'string', x: 4 }`. * * This works by exploiting that fact that conditional types distribute over * unions in TS. */ export type PickEach = Union extends unknown ? Pick : never; /** * This type is exactly like Omit, except that it takes a union type as its * first argument, and returns a union of the omitted types. See {@link PickEach} * docblock for a concrete example of this behavior. */ export type OmitEach = Union extends unknown ? Omit : never; /** * CollapseCases takes a type T that's a union of object types, and returns a * single object type where the type of each key is the union of the types for * that key across all the cases of T. * * For example, if you have: * * type A = { id: 'A' } * type B = { id: 'B' } * type C = { id: 'C' } * * type Item = * | { id: string; type: A; } * | { id: string; type: B; } * | { id: string; type: C; } * * Then, `CollapseCases` will be: `{ id: string, type: A | B | C }`. * * This can be useful because Typescript checks assignability case by case, * so a type like `{ id: string, type: A | B | C }` would not be assignable * to Item, because TS can only see that it's not known to be assignable to * any of Item's individual cases. * * The fix, assuming `x` has type `{ id: string, type: A | B | C }`, * would be something like `x satisfies CollapseCases as Item`. * * Note: in cases where a key only appears in some of the cases, the * result type will have that key as optional, and typed as `unknown`. * E.g., `CollapseCases<{ a: string } | { b: string }>` will be * `{ a?: unknown, b?: unknown }`. This is the correct type because the first * type in the union can be satisfied with any value in its key for `b`, and * vice-versa. I.e., `{ a: 'hello', b: true }` and `{ a: 42, b: 'hello' }` are * both legal assignments to `{ a: string } | { b: string }` (ignoring * excess property checking heuristics that only sometimes apply), as they * satisfy the first and second case respectively. */ export type CollapseCases = Simplify< Pick & Partial>> >; /** * Returns all the keys from a union of object types. * * I.e., with a `type T = { a: number, b: string } | { a: boolean }`, * * I.e., `keyof T` will normally only return `'a'`, because `keyof` with an * object type union returns the keys that exist in _every_ constituent of the * union. However, AllKeys will return `'a' | 'b'`. */ export type AllKeys = T extends object ? keyof T : never; /** * Returns the first type parameter, while giving a type error if the first * parameter's type isn't assignable to the second parameter's type. * * This is like a type-level equivalent of the `satisfies` operator: in the same * way that you'd write `exprOfTypeT satisfies U` to get the `exprOfTypeT` typed * as `T` while ensuring that the type is a `U`, you can write `Satisfies` * to get a type `T` while ensuring that it's assignable to `U`. */ export type Satisfies = T; /** * Returns the first type parameter, while enforcing that it's a supertype of * the second parameter. */ export type SatisfiedBy = T; /** * This type allows you to make sure that every case in one tagged union has a * corresponding case in another tagged union. For example, imagine you have: * * type Job = * | { type: 'USER_INITIATED', startedAt: Date }; * | { type: 'CRON', startedAt: Date, schedule: string }; * * Now, imagine somewhere else you want to define (say) the shape of db rows * that'll store a job. In this type, the keys are cased differently, and you * might attach some extra metadata, like so: * * type JobRow = * | { type: 'USER_INITIATED', started_at: Date, initiated_by: UserId } * | { type: 'CRON', started_at: Date, schedule: string }; * * Still, you want every `type` of Job to have a corresponding `type` in JobRow; * otherwise, some jobs may not be able to be stored correctly. But, the * question is: how can you make TS enforce that? * * The answer is to use this type, like so: * * type JobRow2 = TaggedUnionFromCases< * { type: Job['type'] }, * { * USER_INITIATED: { started_at: Date, initiated_by: UserId }, * CRON: { started_at: Date, schedule: string } * } * > * * The JobRow2 type will be exactly equal to the original JobRow type, but TS * will give an error if the object type passed as the second type parameter * doesn't have a key for every `type` value in Job. * * To unpack the above, the first type parameter holds an object type whose * single key will be used as the discriminator key in the final type, and whose * value is the union of all the required values of the discriminator key. * * Then, the second type parameter is an object type whose keys are the possible * values of the discriminator key, and whose values are the corresponding * fields that should be present in that case. * * This, honestly, is not the most intuitive API, but it's the best I could come * up with. */ export type TaggedUnionFromCases< TagWithValues extends object, Map extends { [K in TagValues]: unknown }, TagKey extends keyof TagWithValues = keyof TagWithValues, TagValues extends TagWithValues[TagKey] & (string | number | symbol) = TagWithValues[TagKey] & (string | number | symbol), > = { [K in TagValues]: Simplify<{ [K2 in TagKey]: K } & Map[K]> }[TagValues]; /** * Same as CollapseCases, but does it recursively on object types. Useful when * the type with the discriminated union is nested inside another object. * * For example with: * * type T = * | { name: 'A', data: { value: 'C' | 'D' } } * | { name: 'B', data: { value: 'E' | 'F' } } * * `CollapseCasesDeep` will be: * * { name: 'A' | 'B', data: { value: 'C' | 'D' | 'E' | 'F' } } * * Whereas `CollapseCases` would only be: * * { name: 'A' | 'B', data: { value: 'C' | 'D' } | { value: 'E' | 'F' } } */ export type CollapseCasesDeep = Simplify< { // For all the keys in T that are in _any_ case (if T is a union) but not // in _every_ case (which is what `keyof T` returns), make them optional. [K in Exclude, keyof T>]?: T[K] extends object ? CollapseCasesDeep : T[K]; } & { // Meanwhile, the keys from every case are required in the final object // type. `AllKeys & keyof T` here might seem redundant, and it is, in the // sense that it's always equal to just `keyof T`. However, it's necessary // to prevent a TS rule from kicking in whereby, if T is a union, TS will // silently distribute in the { [K in keyof T]: ... } type, which we don't // want. [K in AllKeys & keyof T]: T[K] extends object ? CollapseCasesDeep : T[K]; } >; /** * This type allows you to replace a type within a complex type. It works * recursively, so it can handle nested types as well. * * @template Type The original type. * @template Search The type to be replaced. Any time it -- or a subtype of it * -- is found (deeply) within @see {Type}, it will be replaced. * @template Replacement The type to replace with. * * @example * ReplaceDeep<{ a: "x" | "y", b: { c: number } }, string, number> will return * { a: number, b: { c: number } }. The type of `a` is replaced b/c "x" | "y" * is a subtype of `string`, even though it's not exactly `string`. * * @example * ReplaceDeep<{ a: string, b: { c: number } }, "x", number> will return * the first type parameter without doing any replacements. The type of `a` is * _not_ replaced, b/c string is not assignable to (i.e., isn't necesssarily) * the "x" that's being searched for. */ export type ReplaceDeep< Type, Search, Replacement, IncludeFunctions extends boolean = false, > = Type extends Search ? Replacement : Type extends Opaque ? Opaque, Search, Replacement>, Tag> : Type extends Primitive | Date | RegExp ? Type : // Treat Promises and AsyncIterables specially because, while it's possible // to do replacement totally structurally, that's likely undesirable and might // push up against TS limits deep in the replacement Type extends Promise ? Promise> : Type extends AsyncIterable ? Simplify< AsyncIterable> & Omit > : IncludeFunctions extends true ? Type extends (...args: infer Args) => infer R ? ( ...args: ReplaceDeep & unknown[] ) => ReplaceDeep : ReplaceObjectDeep : ReplaceObjectDeep; type ReplaceObjectDeep< Type, Search, Replacement, IncludeFunctions extends boolean = false, > = Type extends object ? { [Key in keyof Type]: ReplaceDeep< Type[Key], Search, Replacement, IncludeFunctions >; } : never;