/** numeric strings */ type NumberType = "float32" | "float64" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" /** string strings */ type StringType = "string" | "timestamp" /** Generic JTD Schema without inference of the represented type */ export type SomeJTDSchemaType = ( | // ref {ref: string} // primitives | {type: NumberType | StringType | "boolean"} // enum | {enum: string[]} // elements | {elements: SomeJTDSchemaType} // values | {values: SomeJTDSchemaType} // properties | { properties: Record optionalProperties?: Record additionalProperties?: boolean } | { properties?: Record optionalProperties: Record additionalProperties?: boolean } // discriminator | {discriminator: string; mapping: Record} // empty // NOTE see the end of // https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-675156492 // eslint-disable-next-line @typescript-eslint/ban-types | {} ) & { nullable?: boolean metadata?: Record definitions?: Record } /** required keys of an object, not undefined */ type RequiredKeys = { [K in keyof T]-?: undefined extends T[K] ? never : K }[keyof T] /** optional or undifined-able keys of an object */ type OptionalKeys = { [K in keyof T]-?: undefined extends T[K] ? K : never }[keyof T] /** type is true if T is a union type */ type IsUnion_ = false extends ( T extends unknown ? ([U] extends [T] ? false : true) : never ) ? false : true type IsUnion = IsUnion_ /** type is true if T is identically E */ type TypeEquality = [T] extends [E] ? ([E] extends [T] ? true : false) : false /** type is true if T or null is identically E or null*/ type NullTypeEquality = TypeEquality /** gets only the string literals of a type or null if a type isn't a string literal */ type EnumString = [T] extends [never] ? null : T extends string ? string extends T ? null : T : null /** true if type is a union of string literals */ type IsEnum = null extends EnumString> ? false : true /** true only if all types are array types (not tuples) */ // NOTE relies on the fact that tuples don't have an index at 0.5, but arrays // have an index at every number type IsElements = false extends IsUnion ? [T] extends [readonly unknown[]] ? undefined extends T[0.5] ? false : true : false : false /** true if the the type is a values type */ type IsValues = false extends IsUnion> ? TypeEquality, string> : false /** true if type is a proeprties type and Union is false, or type is a discriminator type and Union is true */ type IsRecord = Union extends IsUnion> ? null extends EnumString> ? false : true : false /** actual schema */ export type JTDSchemaType = Record> = ( | // refs - where null wasn't specified, must match exactly (null extends EnumString ? never : | ({[K in keyof D]: [T] extends [D[K]] ? {ref: K} : never}[keyof D] & {nullable?: false}) // nulled refs - if ref is nullable and nullable is specified, then it can // match either null or non-null definitions | (null extends T ? { [K in keyof D]: [Exclude] extends [Exclude] ? {ref: K} : never }[keyof D] & {nullable: true} : never)) // empty - empty schemas also treat nullable differently in that it's now fully ignored | (unknown extends T ? {nullable?: boolean} : never) // all other types // numbers - only accepts the type number | ((true extends NullTypeEquality ? {type: NumberType} : // booleans - accepts the type boolean true extends NullTypeEquality ? {type: "boolean"} : // strings - only accepts the type string true extends NullTypeEquality ? {type: StringType} : // strings - only accepts the type Date true extends NullTypeEquality ? {type: "timestamp"} : // enums - only accepts union of string literals // TODO we can't actually check that everything in the union was specified true extends IsEnum ? {enum: EnumString>[]} : // arrays - only accepts arrays, could be array of unions to be resolved later true extends IsElements> ? T extends readonly (infer E)[] ? { elements: JTDSchemaType } : never : // values true extends IsValues ? T extends Record ? { values: JTDSchemaType } : never : // properties true extends IsRecord ? ([RequiredKeys>] extends [never] ? { properties?: Record } : { properties: {[K in RequiredKeys]: JTDSchemaType} }) & ([OptionalKeys>] extends [never] ? { optionalProperties?: Record } : { optionalProperties: { [K in OptionalKeys]: JTDSchemaType, D> } }) & { additionalProperties?: boolean } : // discriminator true extends IsRecord ? { [K in keyof Exclude]-?: Exclude[K] extends string ? { discriminator: K mapping: { // TODO currently allows descriminator to be present in schema [M in Exclude[K]]: JTDSchemaType< Omit, D > } } : never }[keyof Exclude] : never) & (null extends T ? { nullable: true } : {nullable?: false})) ) & { // extra properties metadata?: Record // TODO these should only be allowed at the top level definitions?: {[K in keyof D]: JTDSchemaType} } type JTDDataDef> = | // ref (S extends {ref: string} ? D extends {[K in S["ref"]]: infer V} ? JTDDataDef : never : // type S extends {type: NumberType} ? number : S extends {type: "boolean"} ? boolean : S extends {type: "string"} ? string : S extends {type: "timestamp"} ? string | Date : // enum S extends {enum: readonly (infer E)[]} ? string extends E ? never : [E] extends [string] ? E : never : // elements S extends {elements: infer E} ? JTDDataDef[] : // properties S extends { properties: Record optionalProperties?: Record additionalProperties?: boolean } ? {-readonly [K in keyof S["properties"]]-?: JTDDataDef} & { -readonly [K in keyof S["optionalProperties"]]+?: JTDDataDef< S["optionalProperties"][K], D > } & ([S["additionalProperties"]] extends [true] ? Record : unknown) : S extends { properties?: Record optionalProperties: Record additionalProperties?: boolean } ? {-readonly [K in keyof S["properties"]]-?: JTDDataDef} & { -readonly [K in keyof S["optionalProperties"]]+?: JTDDataDef< S["optionalProperties"][K], D > } & ([S["additionalProperties"]] extends [true] ? Record : unknown) : // values S extends {values: infer V} ? Record> : // discriminator S extends {discriminator: infer M; mapping: Record} ? [M] extends [string] ? { [K in keyof S["mapping"]]: JTDDataDef & {[KM in M]: K} }[keyof S["mapping"]] : never : // empty unknown) | (S extends {nullable: true} ? null : never) export type JTDDataType = S extends {definitions: Record} ? JTDDataDef : JTDDataDef>