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.
@@ -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
- /** Generate typings for the given `Validation`s. */
52
- export function generateTypes(validations: Record<string, Validation>): string {
53
- // Create two maps (one mapping "string -> validator" and another mapping
54
- // "validator -> string"). The first map will serve as our "exports" map,
55
- // while the second will make sure that any exported validator gets referenced
56
- // in the generated DTS, rather than being re-generated
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
- Object.entries(validations).forEach(([ name, validation ]) => {
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
- // Generate the type of the validator, with our stripped reference table
78
- const type = generateTypeNode(validator, referenceable)
107
+ /* Now convert all our validators into TypeScript `TypeNode`s */
108
+ const types = generateTypeNodes(validators, references, isInput)
79
109
 
80
- // Create a type alias declaration with the name of the export
81
- const modifiers = [ ts.factory.createModifier(ts.SyntaxKind.ExportKeyword) ]
82
- const decl = ts.factory.createTypeAliasDeclaration(modifiers, name, [], type)
83
- types.push(decl)
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
- // Print out all our type aliases
117
+ /* And finally print out all our type aliases */
87
118
  return ts.createPrinter().printList(
88
119
  ts.ListFormat.SourceFileStatements,
89
- ts.factory.createNodeArray(types),
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, then we return the type straight
110
- if (! validator.optional) return type
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(NumberValidator, (validator: NumberValidator) => {
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
- const optional = valueValidator.optional
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
+ })