UNPKG

8.94 kBPlain TextView Raw
1/** numeric strings */
2type NumberType = "float32" | "float64" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32"
3
4/** string strings */
5type StringType = "string" | "timestamp"
6
7/** Generic JTD Schema without inference of the represented type */
8export type SomeJTDSchemaType = (
9 | // ref
10 {ref: string}
11 // primitives
12 | {type: NumberType | StringType | "boolean"}
13 // enum
14 | {enum: string[]}
15 // elements
16 | {elements: SomeJTDSchemaType}
17 // values
18 | {values: SomeJTDSchemaType}
19 // properties
20 | {
21 properties: Record<string, SomeJTDSchemaType>
22 optionalProperties?: Record<string, SomeJTDSchemaType>
23 additionalProperties?: boolean
24 }
25 | {
26 properties?: Record<string, SomeJTDSchemaType>
27 optionalProperties: Record<string, SomeJTDSchemaType>
28 additionalProperties?: boolean
29 }
30 // discriminator
31 | {discriminator: string; mapping: Record<string, SomeJTDSchemaType>}
32 // empty
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
36 | {}
37) & {
38 nullable?: boolean
39 metadata?: Record<string, unknown>
40 definitions?: Record<string, SomeJTDSchemaType>
41}
42
43/** required keys of an object, not undefined */
44type RequiredKeys<T> = {
45 [K in keyof T]-?: undefined extends T[K] ? never : K
46}[keyof T]
47
48/** optional or undifined-able keys of an object */
49type OptionalKeys<T> = {
50 [K in keyof T]-?: undefined extends T[K] ? K : never
51}[keyof T]
52
53/** type is true if T is a union type */
54type IsUnion_<T, U extends T = T> = false extends (
55 T extends unknown ? ([U] extends [T] ? false : true) : never
56)
57 ? false
58 : true
59type IsUnion<T> = IsUnion_<T>
60
61/** type is true if T is identically E */
62type TypeEquality<T, E> = [T] extends [E] ? ([E] extends [T] ? true : false) : false
63
64/** type is true if T or null is identically E or null*/
65type NullTypeEquality<T, E> = TypeEquality<T | null, E | null>
66
67/** gets only the string literals of a type or null if a type isn't a string literal */
68type EnumString<T> = [T] extends [never]
69 ? null
70 : T extends string
71 ? string extends T
72 ? null
73 : T
74 : null
75
76/** true if type is a union of string literals */
77type IsEnum<T> = null extends EnumString<Exclude<T, null>> ? false : true
78
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
82type IsElements<T> = false extends IsUnion<T>
83 ? [T] extends [readonly unknown[]]
84 ? undefined extends T[0.5]
85 ? false
86 : true
87 : false
88 : false
89
90/** true if the the type is a values type */
91type IsValues<T> = false extends IsUnion<Exclude<T, null>>
92 ? TypeEquality<keyof Exclude<T, null>, string>
93 : false
94
95/** true if type is a proeprties type and Union is false, or type is a discriminator type and Union is true */
96type IsRecord<T, Union extends boolean> = Union extends IsUnion<Exclude<T, null>>
97 ? null extends EnumString<keyof Exclude<T, null>>
98 ? false
99 : true
100 : false
101
102/** actual schema */
103export 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>
106 ? never
107 :
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
111 | (null extends T
112 ? {
113 [K in keyof D]: [Exclude<T, null>] extends [Exclude<D[K], null>]
114 ? {ref: K}
115 : never
116 }[keyof D] & {nullable: true}
117 : never))
118 // empty - empty schemas also treat nullable differently in that it's now fully ignored
119 | (unknown extends T ? {nullable?: boolean} : never)
120 // all other types // numbers - only accepts the type number
121 | ((true extends NullTypeEquality<T, number>
122 ? {type: NumberType}
123 : // booleans - accepts the type boolean
124 true extends NullTypeEquality<T, boolean>
125 ? {type: "boolean"}
126 : // strings - only accepts the type string
127 true extends NullTypeEquality<T, string>
128 ? {type: StringType}
129 : // strings - only accepts the type Date
130 true extends NullTypeEquality<T, Date>
131 ? {type: "timestamp"}
132 : // enums - only accepts union of string literals
133 // TODO we can't actually check that everything in the union was specified
134 true extends IsEnum<T>
135 ? {enum: EnumString<Exclude<T, null>>[]}
136 : // arrays - only accepts arrays, could be array of unions to be resolved later
137 true extends IsElements<Exclude<T, null>>
138 ? T extends readonly (infer E)[]
139 ? {
140 elements: JTDSchemaType<E, D>
141 }
142 : never
143 : // values
144 true extends IsValues<T>
145 ? T extends Record<string, infer V>
146 ? {
147 values: JTDSchemaType<V, D>
148 }
149 : never
150 : // properties
151 true extends IsRecord<T, false>
152 ? ([RequiredKeys<Exclude<T, null>>] extends [never]
153 ? {
154 properties?: Record<string, never>
155 }
156 : {
157 properties: {[K in RequiredKeys<T>]: JTDSchemaType<T[K], D>}
158 }) &
159 ([OptionalKeys<Exclude<T, null>>] extends [never]
160 ? {
161 optionalProperties?: Record<string, never>
162 }
163 : {
164 optionalProperties: {
165 [K in OptionalKeys<T>]: JTDSchemaType<Exclude<T[K], undefined>, D>
166 }
167 }) & {
168 additionalProperties?: boolean
169 }
170 : // discriminator
171 true extends IsRecord<T, true>
172 ? {
173 [K in keyof Exclude<T, null>]-?: Exclude<T, null>[K] extends string
174 ? {
175 discriminator: K
176 mapping: {
177 // TODO currently allows descriminator to be present in schema
178 [M in Exclude<T, null>[K]]: JTDSchemaType<
179 Omit<T extends {[C in K]: M} ? T : never, K>,
180 D
181 >
182 }
183 }
184 : never
185 }[keyof Exclude<T, null>]
186 : never) &
187 (null extends T
188 ? {
189 nullable: true
190 }
191 : {nullable?: false}))
192) & {
193 // extra properties
194 metadata?: Record<string, unknown>
195 // TODO these should only be allowed at the top level
196 definitions?: {[K in keyof D]: JTDSchemaType<D[K], D>}
197}
198
199type JTDDataDef<S, D extends Record<string, unknown>> =
200 | // ref
201 (S extends {ref: string}
202 ? D extends {[K in S["ref"]]: infer V}
203 ? JTDDataDef<V, D>
204 : never
205 : // type
206 S extends {type: NumberType}
207 ? number
208 : S extends {type: "boolean"}
209 ? boolean
210 : S extends {type: "string"}
211 ? string
212 : S extends {type: "timestamp"}
213 ? string | Date
214 : // enum
215 S extends {enum: readonly (infer E)[]}
216 ? string extends E
217 ? never
218 : [E] extends [string]
219 ? E
220 : never
221 : // elements
222 S extends {elements: infer E}
223 ? JTDDataDef<E, D>[]
224 : // properties
225 S extends {
226 properties: Record<string, unknown>
227 optionalProperties?: Record<string, unknown>
228 additionalProperties?: boolean
229 }
230 ? {-readonly [K in keyof S["properties"]]-?: JTDDataDef<S["properties"][K], D>} & {
231 -readonly [K in keyof S["optionalProperties"]]+?: JTDDataDef<
232 S["optionalProperties"][K],
233 D
234 >
235 } & ([S["additionalProperties"]] extends [true] ? Record<string, unknown> : unknown)
236 : S extends {
237 properties?: Record<string, unknown>
238 optionalProperties: Record<string, unknown>
239 additionalProperties?: boolean
240 }
241 ? {-readonly [K in keyof S["properties"]]-?: JTDDataDef<S["properties"][K], D>} & {
242 -readonly [K in keyof S["optionalProperties"]]+?: JTDDataDef<
243 S["optionalProperties"][K],
244 D
245 >
246 } & ([S["additionalProperties"]] extends [true] ? Record<string, unknown> : unknown)
247 : // values
248 S extends {values: infer V}
249 ? Record<string, JTDDataDef<V, D>>
250 : // discriminator
251 S extends {discriminator: infer M; mapping: Record<string, unknown>}
252 ? [M] extends [string]
253 ? {
254 [K in keyof S["mapping"]]: JTDDataDef<S["mapping"][K], D> & {[KM in M]: K}
255 }[keyof S["mapping"]]
256 : never
257 : // empty
258 unknown)
259 | (S extends {nullable: true} ? null : never)
260
261export type JTDDataType<S> = S extends {definitions: Record<string, unknown>}
262 ? JTDDataDef<S, S["definitions"]>
263 : JTDDataDef<S, Record<string, never>>