2 type NumberType = "float32" | "float64" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32"
5 type StringType = "string" | "timestamp"
7 /** Generic JTD Schema without inference of the represented type */
8 export type SomeJTDSchemaType = (
12 | {type: NumberType | StringType | "boolean"}
16 | {elements: SomeJTDSchemaType}
18 | {values: SomeJTDSchemaType}
21 properties: Record<string, SomeJTDSchemaType>
22 optionalProperties?: Record<string, SomeJTDSchemaType>
23 additionalProperties?: boolean
26 properties?: Record<string, SomeJTDSchemaType>
27 optionalProperties: Record<string, SomeJTDSchemaType>
28 additionalProperties?: boolean
31 | {discriminator: string; mapping: Record<string, SomeJTDSchemaType>}
33 // NOTE see the end of
34 // https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-675156492
35 // eslint-disable-next-line @typescript-eslint/ban-types
39 metadata?: Record<string, unknown>
40 definitions?: Record<string, SomeJTDSchemaType>
43 /** required keys of an object, not undefined */
44 type RequiredKeys<T> = {
45 [K in keyof T]-?: undefined extends T[K] ? never : K
48 /** optional or undifined-able keys of an object */
49 type OptionalKeys<T> = {
50 [K in keyof T]-?: undefined extends T[K] ? K : never
53 /** type is true if T is a union type */
54 type IsUnion_<T, U extends T = T> = false extends (
55 T extends unknown ? ([U] extends [T] ? false : true) : never
59 type IsUnion<T> = IsUnion_<T>
61 /** type is true if T is identically E */
62 type TypeEquality<T, E> = [T] extends [E] ? ([E] extends [T] ? true : false) : false
64 /** type is true if T or null is identically E or null*/
65 type NullTypeEquality<T, E> = TypeEquality<T | null, E | null>
67 /** gets only the string literals of a type or null if a type isn't a string literal */
68 type EnumString<T> = [T] extends [never]
76 /** true if type is a union of string literals */
77 type IsEnum<T> = null extends EnumString<Exclude<T, null>> ? false : true
79 /** true only if all types are array types (not tuples) */
80 // NOTE relies on the fact that tuples don't have an index at 0.5, but arrays
81 // have an index at every number
82 type IsElements<T> = false extends IsUnion<T>
83 ? [T] extends [readonly unknown[]]
84 ? undefined extends T[0.5]
90 /** true if the the type is a values type */
91 type IsValues<T> = false extends IsUnion<Exclude<T, null>>
92 ? TypeEquality<keyof Exclude<T, null>, string>
95 /** true if type is a proeprties type and Union is false, or type is a discriminator type and Union is true */
96 type IsRecord<T, Union extends boolean> = Union extends IsUnion<Exclude<T, null>>
97 ? null extends EnumString<keyof Exclude<T, null>>
103 export type JTDSchemaType<T, D extends Record<string, unknown> = Record<string, never>> = (
104 | // refs - where null wasn't specified, must match exactly
105 (null extends EnumString<keyof D>
108 | ({[K in keyof D]: [T] extends [D[K]] ? {ref: K} : never}[keyof D] & {nullable?: false})
109 // nulled refs - if ref is nullable and nullable is specified, then it can
110 // match either null or non-null definitions
113 [K in keyof D]: [Exclude<T, null>] extends [Exclude<D[K], null>]
116 }[keyof D] & {nullable: true}
118 // empty - empty schemas also treat nullable differently in that it's now fully ignored
119 | (unknown extends T ? {nullable?: boolean} : never)
121 | ((// numbers - only accepts the type number
122 true extends NullTypeEquality<T, number>
124 : // booleans - accepts the type boolean
125 true extends NullTypeEquality<T, boolean>
127 : // strings - only accepts the type string
128 true extends NullTypeEquality<T, string>
130 : // strings - only accepts the type Date
131 true extends NullTypeEquality<T, Date>
132 ? {type: "timestamp"}
133 : // enums - only accepts union of string literals
134 // TODO we can't actually check that everything in the union was specified
135 true extends IsEnum<T>
136 ? {enum: EnumString<Exclude<T, null>>[]}
137 : // arrays - only accepts arrays, could be array of unions to be resolved later
138 true extends IsElements<Exclude<T, null>>
139 ? T extends readonly (infer E)[]
141 elements: JTDSchemaType<E, D>
145 true extends IsValues<T>
146 ? T extends Record<string, infer V>
148 values: JTDSchemaType<V, D>
152 true extends IsRecord<T, false>
153 ? ([RequiredKeys<Exclude<T, null>>] extends [never]
155 properties?: Record<string, never>
158 properties: {[K in RequiredKeys<T>]: JTDSchemaType<T[K], D>}
160 ([OptionalKeys<Exclude<T, null>>] extends [never]
162 optionalProperties?: Record<string, never>
165 optionalProperties: {
166 [K in OptionalKeys<T>]: JTDSchemaType<Exclude<T[K], undefined>, D>
169 additionalProperties?: boolean
172 true extends IsRecord<T, true>
174 [K in keyof Exclude<T, null>]-?: Exclude<T, null>[K] extends string
178 // TODO currently allows descriminator to be present in schema
179 [M in Exclude<T, null>[K]]: JTDSchemaType<
180 Omit<T extends {[C in K]: M} ? T : never, K>,
186 }[keyof Exclude<T, null>]
192 : {nullable?: false}))
195 metadata?: Record<string, unknown>
196 // TODO these should only be allowed at the top level
197 definitions?: {[K in keyof D]: JTDSchemaType<D[K], D>}
200 type JTDDataDef<S, D extends Record<string, unknown>> =
202 S extends {ref: string}
203 ? D extends {[K in S["ref"]]: infer V}
207 S extends {type: NumberType}
209 : S extends {type: "boolean"}
211 : S extends {type: "string"}
213 : S extends {type: "timestamp"}
216 S extends {enum: readonly (infer E)[]}
219 : [E] extends [string]
223 S extends {elements: infer E}
227 properties: Record<string, unknown>
228 optionalProperties?: Record<string, unknown>
229 additionalProperties?: boolean
231 ? {-readonly [K in keyof S["properties"]]-?: JTDDataDef<S["properties"][K], D>} & {
232 -readonly [K in keyof S["optionalProperties"]]+?: JTDDataDef<
233 S["optionalProperties"][K],
236 } & ([S["additionalProperties"]] extends [true] ? Record<string, unknown> : unknown)
238 properties?: Record<string, unknown>
239 optionalProperties: Record<string, unknown>
240 additionalProperties?: boolean
242 ? {-readonly [K in keyof S["properties"]]-?: JTDDataDef<S["properties"][K], D>} & {
243 -readonly [K in keyof S["optionalProperties"]]+?: JTDDataDef<
244 S["optionalProperties"][K],
247 } & ([S["additionalProperties"]] extends [true] ? Record<string, unknown> : unknown)
249 S extends {values: infer V}
250 ? Record<string, JTDDataDef<V, D>>
252 S extends {discriminator: infer M; mapping: Record<string, unknown>}
253 ? [M] extends [string]
255 [K in keyof S["mapping"]]: JTDDataDef<S["mapping"][K], D> & {[KM in M]: K}
256 }[keyof S["mapping"]]
260 | (S extends {nullable: true} ? null : never)
262 export type JTDDataType<S> = S extends {definitions: Record<string, unknown>}
263 ? JTDDataDef<S, S["definitions"]>
264 : JTDDataDef<S, Record<string, never>>