declare const validSym: unique symbol declare const namedSym: unique symbol /** * @tsplus derive nominal */ export interface Brand { [namedSym]: { [k in K]: K } } export declare namespace Brand { export type valid = typeof validSym export type name = typeof namedSym export type Validated = A & Brand.Valid /** * @tsplus derive nominal */ export interface Valid { [validSym]: { [k in K]: A } } export type IsValidated

> = { [k in keyof P[Brand.valid]]: P extends P[Brand.valid][k] ? 0 : 1 }[keyof P[Brand.valid]] extends 0 ? unknown : never export type Unbranded

= P extends infer Q & Brands

? Q : P export type Unnamed

= P extends infer Q & Names

? Q : P export type Brands

= P extends Valid ? TypeLevel.UnionToIntersection< { [k in keyof P[Brand.valid]]: P extends P[Brand.valid][k] ? k extends string ? Valid : never : never }[keyof P[Brand.valid]] > : unknown export type Names

= P extends Brand ? TypeLevel.UnionToIntersection< { [k in keyof P[Brand.name]]: k extends string ? Brand : never }[keyof P[Brand.name]] > : unknown export interface FailedValidation { readonly _tag: "FailedValidation" readonly brands: string[] readonly message: string } /** * @tsplus type Brand.MakeValidated * @tsplus derive nominal */ export interface MakeValidated { make: (value: Unnamed>) => Either unsafeMake: (value: Unnamed>) => A } /** * @tsplus type Brand.Make * @tsplus derive nominal */ export interface Make { make: (value: Unnamed>) => A } /** * @tsplus type Brand.Validation * @tsplus derive nominal */ export interface Validation { readonly validate: (a: A) => a is A & Brand.Valid } export type ValidatedWith> = X extends Validation ? Validated : never /** * @tsplus type Brand/Ops */ export interface Ops { readonly validation: ( predicate: (a: A) => boolean ) => Brand.Validation } } export function validation( predicate: (a: A) => boolean ): Brand.Validation { return { validate: (value): value is Brand.Validated => predicate(value) } } export const Brand: Brand.Ops = { validation } export class FailedValidationException extends Error implements Brand.FailedValidation { readonly _tag = "FailedValidation" constructor(readonly brands: string[]) { super(`Failed Validation of brands: ${brands.join(", ")}`) } } export class FailedValidationError implements Brand.FailedValidation { readonly _tag = "FailedValidation" constructor(readonly brands: string[]) {} get message() { return `Failed Validation of brands: ${this.brands.join(", ")}` } } export type MinLen = Brand.Valid<{ length: number }, `MinLen(${N})`> export type MaxLen = Brand.Valid<{ length: number }, `MaxLen(${N})`> export type RangeLen = MinLen & MaxLen /** * @tsplus derive Brand.Validation<_, _> 10 */ export function deriveMinLenValidation( ...[min]: V extends `MinLen(${infer N extends number})` ? [N] : never ): Brand.Validation { return validation((b) => b.length >= min) } /** * @tsplus derive Brand.Validation<_, _> 10 */ export function deriveMaxLenValidation( ...[max]: V extends `MaxLen(${infer N extends number})` ? [N] : never ): Brand.Validation { return validation((b) => b.length <= max) } export type Regex = Brand.Validated /** * @tsplus derive Brand.Validation<_, _> 10 */ export function deriveRegexValidation( ...[regexStr]: V extends `Regex(${infer R extends string})` ? [R] : never ): Brand.Validation { return validation((b) => new RegExp(regexStr).test(b)) } export type Min = Brand.Validated export type Max = Brand.Validated export type Range = Min & Max /** * @tsplus derive Brand.Validation<_, _> 10 */ export function deriveMinValidation( ...[min]: V extends `Min(${infer N extends number})` ? [N] : never ): Brand.Validation { return validation((b) => b >= min) } /** * @tsplus derive Brand.Validation<_, _> 10 */ export function deriveMaxValidation( ...[max]: V extends `Max(${infer N extends number})` ? [N] : never ): Brand.Validation { return validation((b) => b <= max) } /** * @tsplus derive Brand.MakeValidated<_> 10 */ export function deriveMakeMakeValidated( ...[brands]: [ brands: A extends Brand.Valid ? { [k in (keyof A[Brand.valid]) & string]: Brand.Validation } : {} ] ): Brand.MakeValidated { const make = (value: Brand.Unnamed>): Either => { const failures: string[] = [] for (const brand of Object.keys(brands)) { if (!brands[brand]!.validate(value as any)) { failures.push(brand) } } if (failures.length > 0) { return Either.left(new FailedValidationError(failures)) } return Either.right(value) } const unsafeMake = (value: Brand.Unnamed>) => { const errorOrValue = make(value) if (errorOrValue.isLeft()) { throw new FailedValidationException(errorOrValue.left.brands) } return errorOrValue.right } return { make, unsafeMake } } /** * @tsplus derive Brand.Make<_> 10 */ export function deriveMake( ..._: [] ): Brand.Make { return { make: (a) => a } } /** @tsplus implicit */ export const Positive = Brand.validation((n: number) => n > 0) export type Positive = Brand.ValidatedWith /** @tsplus implicit */ export const Int = Brand.validation((n: number) => Number.isInteger(n)) export type Int = Brand.ValidatedWith /** @tsplus implicit */ export const Finite = Brand.validation((n: number) => Number.isFinite(n)) export type Finite = Brand.ValidatedWith /** @tsplus implicit */ export const UUID = Brand.validation((s) => /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi.test(s) ) export type UUID = Brand.ValidatedWith