import { isBooleanConstructor, isNumberConstructor, isStringConstructor, isDateConstructor } from './utils' import type { DateInstanceType, MarkReadOnlyDeep } from './types' export type ShallowPrettier = T extends object | any[] ? { [key in keyof T]: T[key] } : T export abstract class Schema { static create(this: T, value: TypeOf) { return value } static displayName?: string static namespace?: string abstract __type: unknown } export type Primitives = NumberConstructor | StringConstructor | BooleanConstructor | DateConstructor export type SchemaCtor = Primitives | (new () => T) export type TypeOf = T extends DateConstructor ? DateInstanceType : T extends Primitives ? ReturnType : T extends new () => { __type: infer U } ? U : T extends Schema ? T['__type'] : never export class Number extends Schema { __type!: number } export class String extends Schema { __type!: string } export class Boolean extends Schema { __type!: boolean } export class ID extends Schema { __type!: string } export class Int extends Schema { __type!: number } export class Float extends Schema { __type!: number } export class Date extends Schema { __type!: DateInstanceType } export abstract class ListType extends Schema { __type!: TypeOf[] abstract Item: SchemaCtor } export const List = (Item: T) => { return class List extends ListType { Item = toSchemaCtor(Item) } } export type SchemaField = key extends '__type' ? never : T[key] extends undefined ? never : T[key] extends SchemaCtorInput | FieldInfo | undefined ? key : never export type TypeOfField = T extends FieldInfo ? TypeOf : T extends SchemaCtorInput ? TypeOfSchemaCtorInput : T extends undefined ? undefined : never export abstract class ObjectType extends Schema { __type!: { [key in keyof this as SchemaField]: TypeOfField } } export abstract class UnionType extends Schema { __type!: TypeOf abstract Items: SchemaCtor[] } export const Union = (...Items: T) => { return class Union extends UnionType { Items = toSchemaCtors(Items) } } export type UnionToIntersection = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never export type TypeOfIntersect = UnionToIntersection> export abstract class IntersectType extends Schema { __type!: TypeOfIntersect abstract Items: SchemaCtor[] } export const Intersect = (...Items: T) => { return class Intersect extends IntersectType { Items = toSchemaCtors(Items) } } export type Literals = number | string | boolean | null | undefined export abstract class LiteralType extends Schema { __type!: this['value'] abstract value: Literals } export const Literal = (value: T) => { return class Literal extends LiteralType { value = value } } export const Null = Literal(null) export const Undefined = Literal(undefined) export abstract class NullableType extends Schema { __type!: TypeOf | null abstract Item: SchemaCtor } export const isNullableType = (input: any): input is new () => NullableType => { return input?.prototype instanceof NullableType } export const Nullable = (Item: T) => { return class Nullable extends NullableType { Item = toSchemaCtor(Item) } } export abstract class OptionalType extends Schema { __type!: TypeOf | undefined abstract Item: SchemaCtor } export const isOptionalType = (input: any): input is new () => OptionalType => { return input?.prototype instanceof OptionalType } export const Optional = (Item: T) => { return class Optional extends OptionalType { Item = toSchemaCtor(Item) } } export const Type = '__type' as const export type FieldInfo = { __type: SchemaCtor description?: string deprecated?: string } export type FieldDescriptor = SchemaCtor | FieldInfo export type FieldDescriptors = { [key: string]: FieldDescriptor | FieldDescriptors } export type TypeOfFieldDescriptor = T extends SchemaCtor ? TypeOf : T extends FieldInfo ? TypeOf : never export type TypeOfFieldDescriptors = { [key in keyof T]: T[key] extends FieldDescriptor ? TypeOfFieldDescriptor : T[key] extends FieldDescriptors ? ShallowPrettier> : never } export abstract class StructType extends Schema { __type!: ShallowPrettier> abstract descriptors: FieldDescriptors } export const Struct = (descriptors: T) => { return class Struct extends StructType { descriptors = descriptors } } export abstract class RecordType extends Schema { __type!: { /** * workaround for: * if the key part missing keyword `this`, `this` in the value part will not be recognized as the current type */ [key in keyof this as string]: TypeOf } abstract Item: SchemaCtor } export const Record = (Item: T) => { return class Record extends RecordType { Item = toSchemaCtor(Item) } } export class Any extends Schema { __type!: any } export class Unknown extends Schema { __type!: unknown } export class Never extends Schema { __type!: never } export type JsonType = | number | string | boolean | null | undefined | JsonType[] | { toJSON(): string } | { [key: string]: JsonType } export class Json extends Schema { __type!: JsonType } export abstract class StrictType extends Schema { __type!: TypeOf abstract Item: SchemaCtor } export const Strict = (Item: T) => { return class Strict extends StrictType { Item = toSchemaCtor(Item) } } export abstract class NonStrictType extends Schema { __type!: TypeOf abstract Item: SchemaCtor } export const NonStrict = (Item: T) => { return class Strict extends NonStrictType { Item = toSchemaCtor(Item) } } export abstract class ReadOnlyType extends Schema { __type!: Readonly> abstract Item: SchemaCtor } export const ReadOnly = (Item: T) => { return class ReadOnly extends ReadOnlyType { Item = toSchemaCtor(Item) } } export abstract class ReadOnlyDeepType extends Schema { __type!: MarkReadOnlyDeep> abstract Item: SchemaCtor } export const ReadOnlyDeep = (Item: T) => { return class Strict extends ReadOnlyDeepType { Item = toSchemaCtor(Item) } } /* eslint-disable */ export type SchemaTypeOf = T extends NumberConstructor ? Number : T extends StringConstructor ? String : T extends BooleanConstructor ? Boolean : T extends DateConstructor ? Date : T extends new () => infer S ? S : never /* eslint-enable */ export const getSchemaCtor = (Ctor: T): SchemaTypeOf => { if (isNumberConstructor(Ctor)) { return Number as SchemaTypeOf } if (isStringConstructor(Ctor)) { return String as SchemaTypeOf } if (isBooleanConstructor(Ctor)) { return Boolean as SchemaTypeOf } if (isDateConstructor(Ctor)) { return Date as SchemaTypeOf } return Ctor as SchemaTypeOf } export const isSchemaCtor = (input: any): input is SchemaCtor => { if ( isNumberConstructor(input) || isStringConstructor(input) || isBooleanConstructor(input) || isDateConstructor(input) ) { return true } return input?.prototype instanceof Schema } export const isFieldDescriptor = (input: any): input is FieldDescriptor => { return isSchemaCtor(input?.[Type] ?? input) } export const isFieldDescriptors = (input: any): input is FieldDescriptors => { return !!(input && typeof input === 'object') } export type SchemaCtorInput = SchemaCtor | FieldDescriptors export type TypeOfSchemaCtorInput = T extends SchemaCtor ? TypeOf : T extends FieldDescriptors ? ShallowPrettier> : never export type ToSchemaCtor = T extends SchemaCtor ? T : T extends FieldDescriptors ? new () => { __type: ShallowPrettier> } : never export type SchemaCtorInputs = | SchemaCtorInput[] | { [key: string]: SchemaCtorInput } export type ToSchemaCtors = { [key in keyof T]: T[key] extends SchemaCtorInput ? ToSchemaCtor : never } export const toSchemaCtor = (Item: T) => { if (isSchemaCtor(Item)) { return Item as ToSchemaCtor } return Struct(Item as FieldDescriptors) as unknown as ToSchemaCtor } export const toSchemaCtors = (Inputs: T): ToSchemaCtors => { if (Array.isArray(Inputs)) { // @ts-ignore: ignore return Inputs.map(toSchemaCtor) } if (Inputs && typeof Inputs === 'object') { const result = {} as ToSchemaCtors for (const key in Inputs) { // @ts-ignore: ignore result[key] = toSchemaCtor(Inputs[key]) } return result } throw new Error(`Unknown inputs: ${Inputs}`) } /* eslint-enable */ export type InstanceTypeOf = T extends NumberConstructor ? number : T extends StringConstructor ? string : T extends BooleanConstructor ? boolean : T extends DateConstructor ? Date : T extends new () => infer R ? R : never /* eslint-enable */ const instanceWeakMap = new WeakMap() export const getInstance = (Ctor: T): InstanceTypeOf => { if (isNumberConstructor(Ctor)) { return getInstance(Number) as InstanceTypeOf } if (isStringConstructor(Ctor)) { return getInstance(String) as InstanceTypeOf } if (isBooleanConstructor(Ctor)) { return getInstance(Boolean) as InstanceTypeOf } if (isDateConstructor(Ctor)) { return getInstance(Date) as InstanceTypeOf } if (instanceWeakMap.has(Ctor)) { return instanceWeakMap.get(Ctor)! as InstanceTypeOf } const instance = new Ctor() instanceWeakMap.set(Ctor, instance as Schema) return instance as InstanceTypeOf } export type TypeOfTuple = T extends [] ? [] : T extends [SchemaCtor, ...infer Rest] ? [TypeOf, ...TypeOfTuple] : [] export abstract class TupleType extends Schema { __type!: TypeOfTuple abstract Items: SchemaCtor[] } export const Tuple = (...Items: T) => { return class Tuple extends TupleType { Items = toSchemaCtors(Items) } }