justus 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dts-generator.cjs +267 -42
- package/dist/dts-generator.cjs.map +2 -2
- package/dist/dts-generator.d.ts +32 -3
- package/dist/dts-generator.mjs +266 -42
- package/dist/dts-generator.mjs.map +2 -2
- package/dist/extra/arn.cjs +12 -9
- package/dist/extra/arn.cjs.map +1 -1
- package/dist/extra/arn.d.ts +17 -15
- package/dist/extra/arn.mjs +12 -9
- package/dist/extra/arn.mjs.map +1 -1
- package/dist/extra/uuid.cjs.map +1 -1
- package/dist/extra/uuid.d.ts +1 -1
- package/dist/extra/uuid.mjs.map +1 -1
- package/dist/types.cjs +3 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.ts +13 -2
- package/dist/types.mjs +3 -1
- package/dist/types.mjs.map +1 -1
- package/dist/utilities.cjs +2 -0
- package/dist/utilities.cjs.map +1 -1
- package/dist/utilities.d.ts +1 -5
- package/dist/utilities.mjs +2 -0
- package/dist/utilities.mjs.map +1 -1
- package/package.json +4 -4
- package/src/dts-generator.ts +414 -56
- package/src/extra/arn.ts +61 -40
- package/src/extra/uuid.ts +1 -1
- package/src/types.ts +14 -2
- package/src/utilities.ts +4 -5
package/src/dts-generator.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import ts from 'typescript'
|
|
2
2
|
|
|
3
3
|
import { assertSchema } from './errors'
|
|
4
|
+
import { EAN13Validator } from './extra/ean13'
|
|
4
5
|
import { URLValidator } from './extra/url'
|
|
6
|
+
import { UUIDValidator } from './extra/uuid'
|
|
5
7
|
import { getValidator } from './utilities'
|
|
6
8
|
import { AnyValidator } from './validators/any'
|
|
7
9
|
import { AnyArrayValidator, ArrayValidator } from './validators/array'
|
|
@@ -16,8 +18,35 @@ import { AnyStringValidator, StringValidator } from './validators/string'
|
|
|
16
18
|
import { TupleValidator } from './validators/tuple'
|
|
17
19
|
import { AllOfValidator, OneOfValidator } from './validators/union'
|
|
18
20
|
|
|
21
|
+
|
|
22
|
+
import type { TypeNode } from 'typescript'
|
|
19
23
|
import type { Validation, Validator } from './types'
|
|
20
24
|
|
|
25
|
+
/* ========================================================================== *
|
|
26
|
+
* QUICK SIMPLE DEEP-EQUALITY *
|
|
27
|
+
* ========================================================================== */
|
|
28
|
+
|
|
29
|
+
/** Check that two of our generated types are equal */
|
|
30
|
+
function typeEqual(a: TypeNode, b: TypeNode): boolean {
|
|
31
|
+
function eq(a: any, b: any): boolean {
|
|
32
|
+
if ((typeof a == 'object' && a != null) &&
|
|
33
|
+
(typeof b == 'object' && b != null) ) {
|
|
34
|
+
for (const key in a) {
|
|
35
|
+
if (! eq(a[key], b[key])) return false
|
|
36
|
+
}
|
|
37
|
+
for (const key in b) {
|
|
38
|
+
/* coverage ignore if */
|
|
39
|
+
if (! eq(a[key], b[key])) return false
|
|
40
|
+
}
|
|
41
|
+
return true
|
|
42
|
+
} else {
|
|
43
|
+
return a === b
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return eq(a, b)
|
|
48
|
+
}
|
|
49
|
+
|
|
21
50
|
/* ========================================================================== *
|
|
22
51
|
* LOCAL TYPES *
|
|
23
52
|
* ========================================================================== */
|
|
@@ -25,7 +54,8 @@ import type { Validation, Validator } from './types'
|
|
|
25
54
|
/** A function taking a `Validator` and producing its `TypeNode`. */
|
|
26
55
|
type TypeGenerator<V extends Validator = Validator> = (
|
|
27
56
|
validator: V,
|
|
28
|
-
references: ReadonlyMap<Validator, string
|
|
57
|
+
references: ReadonlyMap<Validator, string>,
|
|
58
|
+
isInput: boolean,
|
|
29
59
|
) => ts.TypeNode
|
|
30
60
|
|
|
31
61
|
/** The generic constructor of a `Validator` instance. */
|
|
@@ -48,66 +78,337 @@ export function registerTypeGenerator<V extends Validator>(
|
|
|
48
78
|
generators.set(validator, generator)
|
|
49
79
|
}
|
|
50
80
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Generate typings (validated or input type) for the given `Validation`s.
|
|
83
|
+
*
|
|
84
|
+
* When `isInput` is `false` (the default) then the _validated_ type will be
|
|
85
|
+
* generated (that is, optional fields with default values will be considered
|
|
86
|
+
* as defined).
|
|
87
|
+
*
|
|
88
|
+
* When `isInput` is `true` then the _input_ type will be generated (that is,
|
|
89
|
+
* optional fields will be considered as optional).
|
|
90
|
+
*/
|
|
91
|
+
export function generateTypes(
|
|
92
|
+
validations: Record<string, Validation>,
|
|
93
|
+
isInput: boolean = false,
|
|
94
|
+
): string {
|
|
95
|
+
/* Mapping from names to validators */
|
|
57
96
|
const validators = new Map<string, Validator>()
|
|
97
|
+
/* Reverse mapping of first validator to their exported name */
|
|
58
98
|
const references = new Map<Validator, string>()
|
|
59
99
|
|
|
60
|
-
|
|
100
|
+
/* Convert all our input validations into proper validators we can examine */
|
|
101
|
+
for (const [ name, validation ] of Object.entries(validations)) {
|
|
61
102
|
const validator = getValidator(validation)
|
|
62
|
-
// References will be added only once, first one takes precedence!
|
|
63
|
-
if (! references.has(validator)) references.set(validator, name)
|
|
64
103
|
validators.set(name, validator)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// Create the array of type alias declarations to be printed and exported
|
|
68
|
-
const types: ts.TypeAliasDeclaration[] = []
|
|
69
|
-
for (const [ name, validator ] of validators.entries()) {
|
|
70
|
-
// Clone our references map, and remove the validator being exported. This
|
|
71
|
-
// will make sure that we don't have any loops in our types
|
|
72
|
-
const referenceable = new Map(references)
|
|
73
|
-
if (referenceable.get(validator) === name) {
|
|
74
|
-
referenceable.delete(validator)
|
|
75
|
-
}
|
|
104
|
+
if (! references.has(validator)) references.set(validator, name)
|
|
105
|
+
}
|
|
76
106
|
|
|
77
|
-
|
|
78
|
-
|
|
107
|
+
/* Now convert all our validators into TypeScript `TypeNode`s */
|
|
108
|
+
const types = generateTypeNodes(validators, references, isInput)
|
|
79
109
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
110
|
+
/* Then convert all our `TypeNode`s into alias declarations */
|
|
111
|
+
const aliases: ts.TypeAliasDeclaration[] = []
|
|
112
|
+
for (const [ name, type ] of types.entries()) {
|
|
113
|
+
const alias = ts.factory.createTypeAliasDeclaration(exportModifiers, name, [], type)
|
|
114
|
+
aliases.push(alias)
|
|
84
115
|
}
|
|
85
116
|
|
|
86
|
-
|
|
117
|
+
/* And finally print out all our type aliases */
|
|
87
118
|
return ts.createPrinter().printList(
|
|
88
119
|
ts.ListFormat.SourceFileStatements,
|
|
89
|
-
ts.factory.createNodeArray(
|
|
120
|
+
ts.factory.createNodeArray(aliases),
|
|
90
121
|
ts.createSourceFile('types.d.ts', '', ts.ScriptTarget.Latest))
|
|
91
122
|
}
|
|
92
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Generate a full declaration map for the specified validations.
|
|
126
|
+
*
|
|
127
|
+
* The full declaration map will include validated and input types and the
|
|
128
|
+
* declaration of the validator itself. For example:
|
|
129
|
+
*
|
|
130
|
+
* ```ts
|
|
131
|
+
* const testValidator = object({ test: optional(string, 'myValue' ) })
|
|
132
|
+
* generateDeclarations({ testValidator })
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* Will result in the following declaration to be emitted:
|
|
136
|
+
*
|
|
137
|
+
* ```ts
|
|
138
|
+
* export type Test: { test: string }
|
|
139
|
+
* export type TestInput: { test?: string }
|
|
140
|
+
* export const testValidator: Validator<Test, TestInput>
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function generateDeclarations(validations: Record<string, Validation>): string {
|
|
144
|
+
/* Array of names: the exported constant, the input type, and the validated output type */
|
|
145
|
+
const names: { name: string, output: string, input: string }[] = []
|
|
146
|
+
/* Map of all validators for validated output type generation, and name references */
|
|
147
|
+
const outputValidators = new Map<string, Validator>()
|
|
148
|
+
const outputReferences = new Map<Validator, string>()
|
|
149
|
+
/* Map of all validators for input type generation, and name references */
|
|
150
|
+
const inputValidators = new Map<string, Validator>()
|
|
151
|
+
const inputReferences = new Map<Validator, string>()
|
|
152
|
+
|
|
153
|
+
/* Go through _all_ validations one by one and prepare names and validators */
|
|
154
|
+
for (const [ name, validation ] of Object.entries(validations)) {
|
|
155
|
+
/* Prep the name prefix for input and output types */
|
|
156
|
+
const prefix = /validator$/i.test(name) ? name.slice(0, -9) :
|
|
157
|
+
/validation$/i.test(name) ? name.slice(0, -10) :
|
|
158
|
+
name
|
|
159
|
+
/* Output and input name */
|
|
160
|
+
const output = `${prefix.slice(0, 1).toUpperCase()}${prefix.slice(1)}`
|
|
161
|
+
const input = `${prefix.slice(0, 1).toUpperCase()}${prefix.slice(1)}Input`
|
|
162
|
+
/* Validator from validation */
|
|
163
|
+
const validator = getValidator(validation)
|
|
164
|
+
|
|
165
|
+
/* Remember names and validators */
|
|
166
|
+
names.push({ name, output, input })
|
|
167
|
+
outputValidators.set(output, validator)
|
|
168
|
+
inputValidators.set(input, validator)
|
|
169
|
+
if (! outputReferences.has(validator)) outputReferences.set(validator, output)
|
|
170
|
+
if (! inputReferences.has(validator)) inputReferences.set(validator, input)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* Generate all output and input types */
|
|
174
|
+
const outputTypes = generateTypeNodes(outputValidators, outputReferences, false)
|
|
175
|
+
const inputTypes = generateTypeNodes(inputValidators, inputReferences, true)
|
|
176
|
+
|
|
177
|
+
/* Array of all statements of the DTS, starting with a comment */
|
|
178
|
+
const statements: ts.Statement[] = []
|
|
179
|
+
|
|
180
|
+
/* Go through each validation, exporting types and variable declarations */
|
|
181
|
+
for (const { name, input, output } of names) {
|
|
182
|
+
/* Get output and input types, asserting their existance */
|
|
183
|
+
const outputType = outputTypes.get(output)
|
|
184
|
+
const inputType = inputTypes.get(input)
|
|
185
|
+
const validation = validations[name]
|
|
186
|
+
const validator = outputValidators.get(output)
|
|
187
|
+
assertSchema(!! outputType, `No output type "${output}" generated for validation "${name}"`)
|
|
188
|
+
assertSchema(!! inputType, `No input type "${input}" generated for validation "${name}"`)
|
|
189
|
+
assertSchema(!! validator, `No validator for "${name}"`)
|
|
190
|
+
|
|
191
|
+
/* Check if input and output types are equal */
|
|
192
|
+
const sameType = typeEqual(inputType, outputType)
|
|
193
|
+
|
|
194
|
+
/* The input can be a simple *reference* to the output type, if those are equal */
|
|
195
|
+
const inputAlias = sameType ? ts.factory.createTypeReferenceNode(output) : inputType
|
|
196
|
+
|
|
197
|
+
/* Type alias declarations for output and input types:
|
|
198
|
+
* > export type MyType = ....
|
|
199
|
+
* > export type MyTypeInput = ... _or_ MyType
|
|
200
|
+
*/
|
|
201
|
+
const outputDeclaration = ts.factory.createTypeAliasDeclaration(exportModifiers, output, [], outputType)
|
|
202
|
+
const inputDeclaration = ts.factory.createTypeAliasDeclaration(exportModifiers, input, [], inputAlias)
|
|
203
|
+
|
|
204
|
+
/* Variable declaration type */
|
|
205
|
+
const variableDeclarationType = generateVariableDeclarationType(validation, validator, outputReferences)
|
|
206
|
+
|
|
207
|
+
/* Variable statement: export const myTypeValidator = ... */
|
|
208
|
+
const variableDeclaration =
|
|
209
|
+
ts.factory.createVariableStatement(
|
|
210
|
+
exportModifiers, // "export"
|
|
211
|
+
ts.factory.createVariableDeclarationList([
|
|
212
|
+
ts.factory.createVariableDeclaration(
|
|
213
|
+
name, // ..................................... "myTypeValidator"
|
|
214
|
+
undefined, // no exclamation token
|
|
215
|
+
variableDeclarationType,
|
|
216
|
+
),
|
|
217
|
+
], ts.NodeFlags.Const), // ......................... "const"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
/* Comments for the generated nodes */
|
|
221
|
+
ts.addSyntheticLeadingComment(
|
|
222
|
+
outputDeclaration,
|
|
223
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
224
|
+
` ${`----- ${name} `.padEnd(74, '-')} `,
|
|
225
|
+
true, // newline
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
ts.addSyntheticLeadingComment(
|
|
229
|
+
outputDeclaration,
|
|
230
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
231
|
+
`* Validated type for {@link ${name}} `,
|
|
232
|
+
true, // newline
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
ts.addSyntheticLeadingComment(
|
|
236
|
+
inputDeclaration,
|
|
237
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
238
|
+
`* Input type for {@link ${name}} `,
|
|
239
|
+
true, // newline
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
ts.addSyntheticLeadingComment(
|
|
243
|
+
variableDeclaration,
|
|
244
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
245
|
+
`* The \`${name}\` validator `,
|
|
246
|
+
true, // newline
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
/* Push our statements */
|
|
250
|
+
statements.push(
|
|
251
|
+
outputDeclaration,
|
|
252
|
+
inputDeclaration,
|
|
253
|
+
variableDeclaration,
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Pretty print our DTS */
|
|
258
|
+
const dts = ts.createPrinter().printList(
|
|
259
|
+
ts.ListFormat.SourceFileStatements,
|
|
260
|
+
ts.factory.createNodeArray(statements),
|
|
261
|
+
ts.createSourceFile('types.d.ts', '', ts.ScriptTarget.Latest))
|
|
262
|
+
/* Include a leading comment with the generation date and return */
|
|
263
|
+
return `// Generated on ${new Date().toUTCString()}\n\n${dts}`
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* ========================================================================== *
|
|
267
|
+
* VALIDATOR CONSTANT DECLARATIONS *
|
|
268
|
+
* ========================================================================== */
|
|
269
|
+
|
|
270
|
+
/** Check if the specified Validation (or function) is a Validator */
|
|
271
|
+
function isValidator(validation: Validation | Function): validation is Validator {
|
|
272
|
+
assertSchema(validation !== undefined, 'Found "undefined" validation in tree')
|
|
273
|
+
|
|
274
|
+
/* Accept only non-null objects or functions */
|
|
275
|
+
if (validation === null) return false
|
|
276
|
+
if ((typeof validation !== 'function') && (typeof validation !== 'object')) {
|
|
277
|
+
return false
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Arrays (tuples) are never a validator */
|
|
281
|
+
if (Array.isArray(validation)) return false
|
|
282
|
+
|
|
283
|
+
/* We must have a "validate" function which is NOT a validator itself: this
|
|
284
|
+
* is an edge case when a schema is defined as { validate: string } */
|
|
285
|
+
if (('validate' in validation) && (typeof validation.validate === 'function')) {
|
|
286
|
+
return ! isValidator(validation.validate)
|
|
287
|
+
} else {
|
|
288
|
+
return false
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Generate an inline type import from "justus" */
|
|
293
|
+
function generateJustusTypeImport(
|
|
294
|
+
typeName: string,
|
|
295
|
+
typeArguments: ts.TypeNode[] = [],
|
|
296
|
+
): ts.TypeNode {
|
|
297
|
+
return ts.factory.createImportTypeNode( // .................... "import"
|
|
298
|
+
ts.factory.createLiteralTypeNode(
|
|
299
|
+
ts.factory.createStringLiteral('justus'), // .......... "justus"
|
|
300
|
+
),
|
|
301
|
+
undefined, // import assertions
|
|
302
|
+
ts.factory.createIdentifier(typeName), // ................. "JustusType"
|
|
303
|
+
typeArguments) // ......................................... "<Arg, ...>"
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Generate the _type_ for a variable declaration associated with a validator */
|
|
307
|
+
function generateVariableDeclarationType(
|
|
308
|
+
validation: Validation,
|
|
309
|
+
validator: Validator,
|
|
310
|
+
references: Map<Validator, string>,
|
|
311
|
+
): ts.TypeNode {
|
|
312
|
+
/* Validation can be one of the following:
|
|
313
|
+
* - validator
|
|
314
|
+
* - constant (null, number, string, boolean, ...)
|
|
315
|
+
* - schema (any other object that is _not_ an array)
|
|
316
|
+
* - tuple (an array)
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
/* This will take care of validators: import("justus").Validator<MyType> */
|
|
320
|
+
if (isValidator(validation)) {
|
|
321
|
+
const validatedType = generateTypeNode(validator, references, false)
|
|
322
|
+
return generateJustusTypeImport('Validator', [ validatedType ])
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* This will take care of constants */
|
|
326
|
+
if (validator instanceof ConstantValidator) {
|
|
327
|
+
return generateTypeNode(validator, references, false)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* This will take care of schemas */
|
|
331
|
+
if (validator instanceof ObjectValidator) {
|
|
332
|
+
const properties: ts.PropertySignature[] = []
|
|
333
|
+
|
|
334
|
+
for (const [ key, valueValidator ] of validator.validators.entries()) {
|
|
335
|
+
const value = validator.schema[key]
|
|
336
|
+
const type = generateVariableDeclarationType(value, valueValidator, references)
|
|
337
|
+
|
|
338
|
+
properties.push(ts.factory.createPropertySignature(
|
|
339
|
+
readonlyModifiers,
|
|
340
|
+
key,
|
|
341
|
+
undefined, // no question mark
|
|
342
|
+
type))
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (validator.additionalProperties) {
|
|
346
|
+
const additional = validator.additionalProperties
|
|
347
|
+
const type = generateVariableDeclarationType(additional, additional, references)
|
|
348
|
+
|
|
349
|
+
properties.push(ts.factory.createPropertySignature(
|
|
350
|
+
readonlyModifiers,
|
|
351
|
+
ts.factory.createComputedPropertyName(
|
|
352
|
+
ts.factory.createPropertyAccessExpression(
|
|
353
|
+
ts.factory.createIdentifier('Symbol'),
|
|
354
|
+
'justusAdditionalValidator')),
|
|
355
|
+
undefined, // no question mark
|
|
356
|
+
type))
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return ts.factory.createTypeLiteralNode(properties)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/* Still to do: tuples */
|
|
363
|
+
assertSchema(false, `Unable to generate variable declaration for ${validator.constructor.name}`)
|
|
364
|
+
}
|
|
365
|
+
|
|
93
366
|
/* ========================================================================== *
|
|
94
367
|
* TYPE GENERATORS *
|
|
95
368
|
* ========================================================================== */
|
|
96
369
|
|
|
370
|
+
/** Generate all TypeScript `TypeNode` following the validators specified. */
|
|
371
|
+
function generateTypeNodes(
|
|
372
|
+
validators: ReadonlyMap<string, Validator>,
|
|
373
|
+
references: ReadonlyMap<Validator, string>,
|
|
374
|
+
isInput: boolean,
|
|
375
|
+
): Map<string, ts.TypeNode> {
|
|
376
|
+
/* Our types map, */
|
|
377
|
+
const types = new Map<string, ts.TypeNode>()
|
|
378
|
+
|
|
379
|
+
/* Walk through our validators map, and produce all `TypeNode`s */
|
|
380
|
+
for (const [ name, validator ] of validators.entries()) {
|
|
381
|
+
/* Here we _clone_ our references map, and remove the validator being
|
|
382
|
+
* exported, if it has the same name. This will make sure that we don't
|
|
383
|
+
* have any loops in our types or things like `type Foo = Foo`. */
|
|
384
|
+
const referenceable = new Map(references)
|
|
385
|
+
if (referenceable.get(validator) === name) referenceable.delete(validator)
|
|
386
|
+
|
|
387
|
+
types.set(name, generateTypeNode(validator, referenceable, isInput))
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* Return our new map */
|
|
391
|
+
return types
|
|
392
|
+
}
|
|
393
|
+
|
|
97
394
|
/** Generate a TypeScript `TypeNode` for the given validator instance. */
|
|
98
395
|
function generateTypeNode(
|
|
99
396
|
validator: Validator,
|
|
100
397
|
references: ReadonlyMap<Validator, string>,
|
|
398
|
+
isInput: boolean,
|
|
101
399
|
): ts.TypeNode {
|
|
102
400
|
const reference = references.get(validator)
|
|
103
401
|
if (reference) return ts.factory.createTypeReferenceNode(reference)
|
|
104
402
|
|
|
105
403
|
const generator = generators.get(validator) || generators.get(validator.constructor)
|
|
106
404
|
assertSchema(!! generator, `Type generator for "${validator.constructor.name}" not found`)
|
|
107
|
-
const type = generator(validator, references)
|
|
405
|
+
const type = generator(validator, references, isInput)
|
|
108
406
|
|
|
109
|
-
// If the validator is not optional
|
|
110
|
-
|
|
407
|
+
// If the validator is not optional (or has no default value and we're
|
|
408
|
+
// generating an _input_ type), then we return the type straight
|
|
409
|
+
if (!(validator.optional || (isInput && (validator.defaultValue !== undefined)))) {
|
|
410
|
+
return type
|
|
411
|
+
}
|
|
111
412
|
|
|
112
413
|
// If the type would result in "never | undefined" simply return "undefined"
|
|
113
414
|
if (type === neverType) return undefinedType
|
|
@@ -128,6 +429,7 @@ function generateTypeNode(
|
|
|
128
429
|
const anyType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)
|
|
129
430
|
const anyArrayType = ts.factory.createArrayTypeNode(anyType)
|
|
130
431
|
const booleanType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword)
|
|
432
|
+
const dateType = ts.factory.createTypeReferenceNode('Date')
|
|
131
433
|
const numberType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
|
|
132
434
|
const neverType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword)
|
|
133
435
|
const stringType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
|
|
@@ -142,6 +444,10 @@ const recordType = ts.factory.createMappedTypeNode(
|
|
|
142
444
|
|
|
143
445
|
// "Optional" modifier (the "?" token )
|
|
144
446
|
const optionalKeyword = ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
447
|
+
// "export" modifier for declarations
|
|
448
|
+
const exportModifiers = [ ts.factory.createModifier(ts.SyntaxKind.ExportKeyword) ]
|
|
449
|
+
// "readonly" modifier for declarations
|
|
450
|
+
const readonlyModifiers = [ ts.factory.createModifier(ts.SyntaxKind.ReadonlyKeyword ) ]
|
|
145
451
|
|
|
146
452
|
/* ========================================================================== */
|
|
147
453
|
|
|
@@ -152,20 +458,27 @@ registerTypeGenerator(AnyArrayValidator, () => anyArrayType)
|
|
|
152
458
|
registerTypeGenerator(AnyNumberValidator, () => numberType)
|
|
153
459
|
registerTypeGenerator(AnyObjectValidator, () => recordType)
|
|
154
460
|
registerTypeGenerator(AnyStringValidator, () => stringType)
|
|
155
|
-
registerTypeGenerator(BooleanValidator, () => booleanType)
|
|
156
461
|
registerTypeGenerator(NeverValidator, () => neverType)
|
|
157
|
-
registerTypeGenerator(DateValidator, () => ts.factory.createTypeReferenceNode('Date'))
|
|
158
|
-
registerTypeGenerator(URLValidator, () => ts.factory.createTypeReferenceNode('URL'))
|
|
159
462
|
|
|
160
463
|
/* ========================================================================== */
|
|
161
464
|
|
|
162
465
|
// Complex generator functions...
|
|
163
466
|
|
|
164
|
-
registerTypeGenerator(ArrayValidator, (validator, references) => {
|
|
165
|
-
const itemType = generateTypeNode(validator.items, references)
|
|
467
|
+
registerTypeGenerator(ArrayValidator, (validator, references, isInput) => {
|
|
468
|
+
const itemType = generateTypeNode(validator.items, references, isInput)
|
|
166
469
|
return ts.factory.createArrayTypeNode(itemType)
|
|
167
470
|
})
|
|
168
471
|
|
|
472
|
+
registerTypeGenerator(BooleanValidator, (validator, _references, isInput) => {
|
|
473
|
+
return (isInput && validator.fromString) ?
|
|
474
|
+
ts.factory.createUnionTypeNode([
|
|
475
|
+
booleanType,
|
|
476
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('true')),
|
|
477
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('false')),
|
|
478
|
+
]) :
|
|
479
|
+
booleanType
|
|
480
|
+
})
|
|
481
|
+
|
|
169
482
|
registerTypeGenerator(ConstantValidator, (validator) => {
|
|
170
483
|
const literal =
|
|
171
484
|
typeof validator.constant === 'number' ? ts.factory.createNumericLiteral(validator.constant) :
|
|
@@ -179,7 +492,21 @@ registerTypeGenerator(ConstantValidator, (validator) => {
|
|
|
179
492
|
return ts.factory.createLiteralTypeNode(literal)
|
|
180
493
|
})
|
|
181
494
|
|
|
182
|
-
registerTypeGenerator(
|
|
495
|
+
registerTypeGenerator(DateValidator, (validator: DateValidator, _references, isInput) => {
|
|
496
|
+
return isInput ?
|
|
497
|
+
validator.format === 'iso' ? stringType :
|
|
498
|
+
validator.format === 'timestamp' ? numberType :
|
|
499
|
+
ts.factory.createUnionTypeNode([ dateType, numberType, stringType ]) :
|
|
500
|
+
dateType
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
registerTypeGenerator(NumberValidator, (validator: NumberValidator, _references, isInput) => {
|
|
504
|
+
if (isInput) {
|
|
505
|
+
return validator.fromString ?
|
|
506
|
+
ts.factory.createUnionTypeNode([ numberType, stringType ]) :
|
|
507
|
+
numberType
|
|
508
|
+
}
|
|
509
|
+
|
|
183
510
|
if (! validator.brand) return numberType
|
|
184
511
|
|
|
185
512
|
const signature = ts.factory.createPropertySignature(undefined, `__brand_${validator.brand}`, undefined, neverType)
|
|
@@ -187,21 +514,21 @@ registerTypeGenerator(NumberValidator, (validator: NumberValidator) => {
|
|
|
187
514
|
return ts.factory.createIntersectionTypeNode([ numberType, literal ])
|
|
188
515
|
})
|
|
189
516
|
|
|
190
|
-
registerTypeGenerator(OptionalValidator, (validator: OptionalValidator, references) => {
|
|
517
|
+
registerTypeGenerator(OptionalValidator, (validator: OptionalValidator, references, isInput: boolean) => {
|
|
191
518
|
// return the wrappeed type. The '... | undefined' part of the optional will
|
|
192
519
|
// be added in 'generateTypeNode' above, as _any_ validator can be optional
|
|
193
|
-
return generateTypeNode(validator.validator, references)
|
|
520
|
+
return generateTypeNode(validator.validator, references, isInput)
|
|
194
521
|
})
|
|
195
522
|
|
|
196
|
-
registerTypeGenerator(StringValidator, (validator: StringValidator) => {
|
|
197
|
-
if (! validator.brand) return stringType
|
|
523
|
+
registerTypeGenerator(StringValidator, (validator: StringValidator, _references, isInput) => {
|
|
524
|
+
if ((! validator.brand) || (isInput)) return stringType
|
|
198
525
|
|
|
199
526
|
const signature = ts.factory.createPropertySignature(undefined, `__brand_${validator.brand}`, undefined, neverType)
|
|
200
527
|
const literal = ts.factory.createTypeLiteralNode([ signature ])
|
|
201
528
|
return ts.factory.createIntersectionTypeNode([ stringType, literal ])
|
|
202
529
|
})
|
|
203
530
|
|
|
204
|
-
registerTypeGenerator(TupleValidator, (validator: TupleValidator<any>, references) => {
|
|
531
|
+
registerTypeGenerator(TupleValidator, (validator: TupleValidator<any>, references, isInput) => {
|
|
205
532
|
const members = validator.members
|
|
206
533
|
|
|
207
534
|
// count how many rest parameters do we have..
|
|
@@ -218,9 +545,9 @@ registerTypeGenerator(TupleValidator, (validator: TupleValidator<any>, reference
|
|
|
218
545
|
// if we have zero or one rest parameter, things are easy...
|
|
219
546
|
if (count < 2) {
|
|
220
547
|
const types = members.map(({ single, validator }) => {
|
|
221
|
-
const memberType = generateTypeNode(validator, references)
|
|
548
|
+
const memberType = generateTypeNode(validator, references, isInput)
|
|
222
549
|
|
|
223
|
-
if (single) return generateTypeNode(validator, references)
|
|
550
|
+
if (single) return generateTypeNode(validator, references, isInput)
|
|
224
551
|
|
|
225
552
|
const arrayType = ts.factory.createArrayTypeNode(memberType)
|
|
226
553
|
return ts.factory.createRestTypeNode(arrayType)
|
|
@@ -232,11 +559,11 @@ registerTypeGenerator(TupleValidator, (validator: TupleValidator<any>, reference
|
|
|
232
559
|
// We have two or more rest parameters... we need combine everything between
|
|
233
560
|
// the first and the last one in a giant union!
|
|
234
561
|
const before = members.slice(0, first)
|
|
235
|
-
.map(({ validator }) => generateTypeNode(validator, references))
|
|
562
|
+
.map(({ validator }) => generateTypeNode(validator, references, isInput))
|
|
236
563
|
const types = members.slice(first, next)
|
|
237
|
-
.map(({ validator }) => generateTypeNode(validator, references))
|
|
564
|
+
.map(({ validator }) => generateTypeNode(validator, references, isInput))
|
|
238
565
|
const after = members.slice(next)
|
|
239
|
-
.map(({ validator }) => generateTypeNode(validator, references))
|
|
566
|
+
.map(({ validator }) => generateTypeNode(validator, references, isInput))
|
|
240
567
|
|
|
241
568
|
const union = ts.factory.createUnionTypeNode(types)
|
|
242
569
|
const array = ts.factory.createArrayTypeNode(union)
|
|
@@ -245,22 +572,26 @@ registerTypeGenerator(TupleValidator, (validator: TupleValidator<any>, reference
|
|
|
245
572
|
return ts.factory.createTupleTypeNode([ ...before, rest, ...after ])
|
|
246
573
|
})
|
|
247
574
|
|
|
248
|
-
registerTypeGenerator(AllOfValidator, (validator, references) => {
|
|
249
|
-
const members = validator.validators.map((validator) => generateTypeNode(validator, references))
|
|
575
|
+
registerTypeGenerator(AllOfValidator, (validator, references, isInput) => {
|
|
576
|
+
const members = validator.validators.map((validator) => generateTypeNode(validator, references, isInput))
|
|
250
577
|
return ts.factory.createIntersectionTypeNode(members)
|
|
251
578
|
})
|
|
252
579
|
|
|
253
|
-
registerTypeGenerator(OneOfValidator, (validator, references) => {
|
|
254
|
-
const members = validator.validators.map((validator) => generateTypeNode(validator, references))
|
|
580
|
+
registerTypeGenerator(OneOfValidator, (validator, references, isInput) => {
|
|
581
|
+
const members = validator.validators.map((validator) => generateTypeNode(validator, references, isInput))
|
|
255
582
|
return ts.factory.createUnionTypeNode(members)
|
|
256
583
|
})
|
|
257
584
|
|
|
258
|
-
registerTypeGenerator(ObjectValidator, (validator, references) => {
|
|
585
|
+
registerTypeGenerator(ObjectValidator, (validator, references, isInput) => {
|
|
259
586
|
const properties: ts.PropertySignature[] = []
|
|
260
587
|
|
|
261
588
|
for (const [ key, valueValidator ] of validator.validators.entries()) {
|
|
262
|
-
const type = generateTypeNode(valueValidator, references)
|
|
263
|
-
|
|
589
|
+
const type = generateTypeNode(valueValidator, references, isInput)
|
|
590
|
+
|
|
591
|
+
// the optional keyword (question mark) is added when either the validator
|
|
592
|
+
// is optional or, when in input mode, there is no default value
|
|
593
|
+
const optional = (isInput && valueValidator.defaultValue !== undefined) ||
|
|
594
|
+
valueValidator.optional
|
|
264
595
|
|
|
265
596
|
const signature = ts.factory.createPropertySignature(
|
|
266
597
|
undefined,
|
|
@@ -272,7 +603,7 @@ registerTypeGenerator(ObjectValidator, (validator, references) => {
|
|
|
272
603
|
}
|
|
273
604
|
|
|
274
605
|
if (validator.additionalProperties) {
|
|
275
|
-
const propertyType = generateTypeNode(validator.additionalProperties, references)
|
|
606
|
+
const propertyType = generateTypeNode(validator.additionalProperties, references, isInput)
|
|
276
607
|
|
|
277
608
|
const extra = ts.factory.createMappedTypeNode(
|
|
278
609
|
undefined, // readonly
|
|
@@ -290,3 +621,30 @@ registerTypeGenerator(ObjectValidator, (validator, references) => {
|
|
|
290
621
|
return ts.factory.createTypeLiteralNode(properties)
|
|
291
622
|
}
|
|
292
623
|
})
|
|
624
|
+
|
|
625
|
+
/* ===== EXTRA TYPES ======================================================== */
|
|
626
|
+
|
|
627
|
+
registerTypeGenerator(EAN13Validator, (_validator, _references, isInput) => {
|
|
628
|
+
if (isInput) return ts.factory.createUnionTypeNode([ numberType, stringType ])
|
|
629
|
+
|
|
630
|
+
const signature = ts.factory.createPropertySignature(undefined, '__ean_13', undefined, neverType)
|
|
631
|
+
const literal = ts.factory.createTypeLiteralNode([ signature ])
|
|
632
|
+
return ts.factory.createIntersectionTypeNode([ stringType, literal ])
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
registerTypeGenerator(UUIDValidator, (_validator, _references, isInput) => {
|
|
636
|
+
if (isInput) return stringType
|
|
637
|
+
|
|
638
|
+
const signature = ts.factory.createPropertySignature(undefined, '__uuid', undefined, neverType)
|
|
639
|
+
const literal = ts.factory.createTypeLiteralNode([ signature ])
|
|
640
|
+
return ts.factory.createIntersectionTypeNode([ stringType, literal ])
|
|
641
|
+
})
|
|
642
|
+
|
|
643
|
+
registerTypeGenerator(URLValidator, (_validator, _references, isInput) => {
|
|
644
|
+
const urlType = ts.factory.createTypeReferenceNode('URL')
|
|
645
|
+
if (isInput) {
|
|
646
|
+
return ts.factory.createUnionTypeNode([ urlType, stringType ])
|
|
647
|
+
} else {
|
|
648
|
+
return urlType
|
|
649
|
+
}
|
|
650
|
+
})
|