/** * Propagation type. * * `IPropagation` is a type gathering all possible status codes and their body * data types as a discriminated union type. You can specify the status code and * its body data type just by using conditional statement like below. * * ```typescript * type Output = IPropagation<{ * 200: ISeller.IAuthorized; * 400: TypeGuardError.IProps; * }>; * * const output: Output = await sdk.sellers.authenticate.join(input); * if (output.success) { * // automatically casted to "ISeller.IAuthorized" type * const authorized: ISeller.IAuthorized = output.data; * } else if (output.status === 400) { * // The unknown-status fallback overlaps numeric statuses. Validate the * // body before treating it as TypeGuardError.IProps. * const error: unknown = output.data; * } else { * // unknown type when out of pre-defined status codes * const result: unknown = output.data; * } * ``` * * For reference, this `IPropagation` type is utilized by SDK library generated * by `@nestia/sdk`, when you've configured {@link INestiaConfig.propagate} to be * `true`. In that case, SDK functions generated by `@nestia/sdk` no more * returns response DTO typed data directly, but returns this `IPropagation` * typed object instead. * * @author Jeongho Nam - https://github.com/samchon * @template StatusMap Map of status code and its body data type. * @template Success Default success status code. * @evidence contracts/common.md#principled-implementation Each configured status becomes a branch whose success flag is true for configured success statuses, 200 and 201 by default. A false-success fallback with numeric status and unknown data represents unconfigured responses; TypeScript cannot subtract numeric literals from number, so this fallback also overlaps configured failure statuses and status-only narrowing cannot prove their payload shape. * @evidence contracts/common.md#clear-and-simple-design One mapped type indexed into a union, plus the fallback branch. * @evidence contracts/common.md#prohibited-implementation-shortcuts It is a type and adds no runtime behavior. * @evidence contracts/common.md#meaningful-documentation The comment gives an example of narrowing and says which generated SDK option uses the type. */ export type IPropagation< StatusMap extends { [P in IPropagation.Status]?: any; }, Success extends number = 200 | 201, > = | { [P in keyof StatusMap]: IPropagation.IBranch< P extends Success ? true : false, P, StatusMap[P] >; }[keyof StatusMap] | IPropagation.IBranch; export namespace IPropagation { /** * Type of configurable status codes. * * The special characters like `2XX`, `3XX`, `4XX`, `5XX` are meaning the * range of status codes. If `5XX` is specified, it means the status code is * in the range of `500` to `599`. * * @evidence contracts/common.md#principled-implementation A status is a number or one of the four range spellings, which is enough to configure both exact and ranged branches. * @evidence contracts/common.md#clear-and-simple-design A single union. * @evidence contracts/common.md#prohibited-implementation-shortcuts It is a type and adds no runtime behavior. * @evidence contracts/common.md#meaningful-documentation The comment defines the range spellings. */ export type Status = number | "2XX" | "3XX" | "4XX" | "5XX"; /** * Branch type of propagation. * * `IPropagation.IBranch` is a branch type composing `IPropagation` type, * which is gathering all possible status codes and their body data types as a * union type. * * @evidence contracts/common.md#principled-implementation A branch pairs a literal success flag with the status, the data, and the headers, and turns a range spelling into the union of its integers, so a narrowed status is a number the caller can compare. * @evidence contracts/common.md#clear-and-simple-design One record with a conditional on the status member. * @evidence contracts/common.md#prohibited-implementation-shortcuts It is a type and adds no runtime behavior. * @evidence contracts/common.md#meaningful-documentation The comment explains that it is the element of the propagation union. */ export interface IBranch { success: Success; status: StatusValue extends "2XX" | "3XX" | "4XX" | "5XX" ? StatusRange : StatusValue extends number ? StatusValue : never; data: BodyData; headers: Record; } /** * Range of status codes by the first digit, `"4XX"` for `400` to `499`. * * @evidence contracts/common.md#principled-implementation The first-digit spelling maps to the integers from its hundred up to the next hundred, computed by excluding a smaller enumeration from a larger one. * @evidence contracts/common.md#clear-and-simple-design One conditional over the four spellings, using two private helper types. * @evidence contracts/common.md#prohibited-implementation-shortcuts It is a type and adds no runtime behavior. * @evidence contracts/common.md#meaningful-documentation The comment gives the range rule with an example. */ export type StatusRange = T extends "2XX" ? IntRange<200, 300> : T extends "3XX" ? IntRange<300, 400> : T extends "4XX" ? IntRange<400, 500> : IntRange<500, 600>; /** Integers from `F` up to, not including, `T`. */ type IntRange = Exclude< Enumerate, Enumerate >; type Enumerate< N extends number, Acc extends number[] = [], > = Acc["length"] extends N ? Acc[number] : Enumerate; }