justus 0.5.1 → 0.5.3

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,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/utilities.ts"],
4
- "mappings": ";AAAA,SAAS,gBAAgB;AAalB,SAAS,aAAa,YAAmC;AAE9D,MAAI,eAAe;AAAM,WAAO,KAAK,SAAS,IAAI,UAAU,GAAG,IAAI;AAGnE,MAAW,WAAY,OAAO,eAAe,GAAG;AAC9C,WAAc,WAAY,OAAO,eAAe;AAAA,EAClD;AAGA,UAAQ,OAAO,YAAY;AAAA,IAEzB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,KAAK,SAAS,IAAI,UAAU,GAAG,UAAU;AAAA,IAGlD,KAAK;AAEH,UAAI,MAAM,QAAQ,UAAU;AAAG,eAAO,KAAK,SAAS,IAAI,OAAO,GAAG,UAAU;AAE5E,aAAO,KAAK,SAAS,IAAI,QAAQ,GAAG,UAAoB;AAAA,IAG1D;AACE,YAAM,IAAI,UAAU,4BAA4B,OAAO,UAAU,GAAG;AAAA,EACxE;AACF;",
4
+ "mappings": ";AAAA,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AASlB,SAAS,aAAa,YAAmC;AAC9D,eAAa,eAAe,QAAW,uCAAuC;AAG9E,MAAI,eAAe;AAAM,WAAO,KAAK,SAAS,IAAI,UAAU,GAAG,IAAI;AAGnE,MAAW,WAAY,OAAO,eAAe,GAAG;AAC9C,WAAc,WAAY,OAAO,eAAe;AAAA,EAClD;AAGA,UAAQ,OAAO,YAAY;AAAA,IAEzB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,KAAK,SAAS,IAAI,UAAU,GAAG,UAAU;AAAA,IAGlD,KAAK;AAEH,UAAI,MAAM,QAAQ,UAAU;AAAG,eAAO,KAAK,SAAS,IAAI,OAAO,GAAG,UAAU;AAE5E,aAAO,KAAK,SAAS,IAAI,QAAQ,GAAG,UAAoB;AAAA,IAG1D;AACE,YAAM,IAAI,UAAU,4BAA4B,OAAO,UAAU,GAAG;AAAA,EACxE;AACF;",
5
5
  "names": []
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "justus",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "A JavaScript validation library, with types!",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -78,13 +78,10 @@
78
78
  "author": "Juit Developers <developers@juit.com>",
79
79
  "license": "Apache-2.0",
80
80
  "devDependencies": {
81
- "@plugjs/build": "^0.4.33",
82
- "@plugjs/tsd": "^0.4.32",
83
- "@types/chai": "^4.3.5",
84
- "chai": "^4.3.8",
85
- "typescript": "^5.2.2"
86
- },
87
- "optionalDependencies": {
81
+ "@plugjs/build": "^0.5.7",
82
+ "@plugjs/tsd": "^0.5.7",
83
+ "@types/chai": "^4.3.9",
84
+ "chai": "^4.3.10",
88
85
  "typescript": "^5.2.2"
89
86
  },
90
87
  "directories": {
@@ -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,335 @@ 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
- })
104
+ if (! references.has(validator)) references.set(validator, name)
105
+ }
66
106
 
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
- }
107
+ /* Now convert all our validators into TypeScript `TypeNode`s */
108
+ const types = generateTypeNodes(validators, references, isInput)
109
+
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)
115
+ }
116
+
117
+ /* And finally print out all our type aliases */
118
+ return ts.createPrinter().printList(
119
+ ts.ListFormat.SourceFileStatements,
120
+ ts.factory.createNodeArray(aliases),
121
+ ts.createSourceFile('types.d.ts', '', ts.ScriptTarget.Latest))
122
+ }
76
123
 
77
- // Generate the type of the validator, with our stripped reference table
78
- const type = generateTypeNode(validator, referenceable)
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
+ }
79
172
 
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)
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
+ )
84
255
  }
85
256
 
86
- // Print out all our type aliases
257
+ /* Pretty print our DTS */
87
258
  return ts.createPrinter().printList(
88
259
  ts.ListFormat.SourceFileStatements,
89
- ts.factory.createNodeArray(types),
260
+ ts.factory.createNodeArray(statements),
90
261
  ts.createSourceFile('types.d.ts', '', ts.ScriptTarget.Latest))
91
262
  }
92
263
 
264
+ /* ========================================================================== *
265
+ * VALIDATOR CONSTANT DECLARATIONS *
266
+ * ========================================================================== */
267
+
268
+ /** Check if the specified Validation (or function) is a Validator */
269
+ function isValidator(validation: Validation | Function): validation is Validator {
270
+ assertSchema(validation !== undefined, 'Found "undefined" validation in tree')
271
+
272
+ /* Accept only non-null objects or functions */
273
+ if (validation === null) return false
274
+ if ((typeof validation !== 'function') && (typeof validation !== 'object')) {
275
+ return false
276
+ }
277
+
278
+ /* Arrays (tuples) are never a validator */
279
+ if (Array.isArray(validation)) return false
280
+
281
+ /* We must have a "validate" function which is NOT a validator itself: this
282
+ * is an edge case when a schema is defined as { validate: string } */
283
+ if (('validate' in validation) && (typeof validation.validate === 'function')) {
284
+ return ! isValidator(validation.validate)
285
+ } else {
286
+ return false
287
+ }
288
+ }
289
+
290
+ /** Generate an inline type import from "justus" */
291
+ function generateJustusTypeImport(
292
+ typeName: string,
293
+ typeArguments: ts.TypeNode[] = [],
294
+ ): ts.TypeNode {
295
+ return ts.factory.createImportTypeNode( // .................... "import"
296
+ ts.factory.createLiteralTypeNode(
297
+ ts.factory.createStringLiteral('justus'), // .......... "justus"
298
+ ),
299
+ undefined, // import assertions
300
+ ts.factory.createIdentifier(typeName), // ................. "JustusType"
301
+ typeArguments) // ......................................... "<Arg, ...>"
302
+ }
303
+
304
+ /** Generate the _type_ for a variable declaration associated with a validator */
305
+ function generateVariableDeclarationType(
306
+ validation: Validation,
307
+ validator: Validator,
308
+ references: Map<Validator, string>,
309
+ ): ts.TypeNode {
310
+ /* Validation can be one of the following:
311
+ * - validator
312
+ * - constant (null, number, string, boolean, ...)
313
+ * - schema (any other object that is _not_ an array)
314
+ * - tuple (an array)
315
+ */
316
+
317
+ /* This will take care of validators: import("justus").Validator<MyType> */
318
+ if (isValidator(validation)) {
319
+ const validatedType = generateTypeNode(validator, references, false)
320
+ return generateJustusTypeImport('Validator', [ validatedType ])
321
+ }
322
+
323
+ /* This will take care of constants */
324
+ if (validator instanceof ConstantValidator) {
325
+ return generateTypeNode(validator, references, false)
326
+ }
327
+
328
+ /* This will take care of schemas */
329
+ if (validator instanceof ObjectValidator) {
330
+ const properties: ts.PropertySignature[] = []
331
+
332
+ for (const [ key, valueValidator ] of validator.validators.entries()) {
333
+ const value = validator.schema[key]
334
+ const type = generateVariableDeclarationType(value, valueValidator, references)
335
+
336
+ properties.push(ts.factory.createPropertySignature(
337
+ readonlyModifiers,
338
+ key,
339
+ undefined, // no question mark
340
+ type))
341
+ }
342
+
343
+ if (validator.additionalProperties) {
344
+ const additional = validator.additionalProperties
345
+ const type = generateVariableDeclarationType(additional, additional, references)
346
+
347
+ properties.push(ts.factory.createPropertySignature(
348
+ readonlyModifiers,
349
+ ts.factory.createComputedPropertyName(
350
+ ts.factory.createPropertyAccessExpression(
351
+ ts.factory.createIdentifier('Symbol'),
352
+ 'justusAdditionalValidator')),
353
+ undefined, // no question mark
354
+ type))
355
+ }
356
+
357
+ return ts.factory.createTypeLiteralNode(properties)
358
+ }
359
+
360
+ /* Still to do: tuples */
361
+ assertSchema(false, `Unable to generate variable declaration for ${validator.constructor.name}`)
362
+ }
363
+
93
364
  /* ========================================================================== *
94
365
  * TYPE GENERATORS *
95
366
  * ========================================================================== */
96
367
 
368
+ /** Generate all TypeScript `TypeNode` following the validators specified. */
369
+ function generateTypeNodes(
370
+ validators: ReadonlyMap<string, Validator>,
371
+ references: ReadonlyMap<Validator, string>,
372
+ isInput: boolean,
373
+ ): Map<string, ts.TypeNode> {
374
+ /* Our types map, */
375
+ const types = new Map<string, ts.TypeNode>()
376
+
377
+ /* Walk through our validators map, and produce all `TypeNode`s */
378
+ for (const [ name, validator ] of validators.entries()) {
379
+ /* Here we _clone_ our references map, and remove the validator being
380
+ * exported, if it has the same name. This will make sure that we don't
381
+ * have any loops in our types or things like `type Foo = Foo`. */
382
+ const referenceable = new Map(references)
383
+ if (referenceable.get(validator) === name) referenceable.delete(validator)
384
+
385
+ types.set(name, generateTypeNode(validator, referenceable, isInput))
386
+ }
387
+
388
+ /* Return our new map */
389
+ return types
390
+ }
391
+
97
392
  /** Generate a TypeScript `TypeNode` for the given validator instance. */
98
393
  function generateTypeNode(
99
394
  validator: Validator,
100
395
  references: ReadonlyMap<Validator, string>,
396
+ isInput: boolean,
101
397
  ): ts.TypeNode {
102
398
  const reference = references.get(validator)
103
399
  if (reference) return ts.factory.createTypeReferenceNode(reference)
104
400
 
105
401
  const generator = generators.get(validator) || generators.get(validator.constructor)
106
402
  assertSchema(!! generator, `Type generator for "${validator.constructor.name}" not found`)
107
- const type = generator(validator, references)
403
+ const type = generator(validator, references, isInput)
108
404
 
109
- // If the validator is not optional, then we return the type straight
110
- if (! validator.optional) return type
405
+ // If the validator is not optional (or has no default value and we're
406
+ // generating an _input_ type), then we return the type straight
407
+ if (!(validator.optional || (isInput && (validator.defaultValue !== undefined)))) {
408
+ return type
409
+ }
111
410
 
112
411
  // If the type would result in "never | undefined" simply return "undefined"
113
412
  if (type === neverType) return undefinedType
@@ -128,6 +427,7 @@ function generateTypeNode(
128
427
  const anyType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)
129
428
  const anyArrayType = ts.factory.createArrayTypeNode(anyType)
130
429
  const booleanType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword)
430
+ const dateType = ts.factory.createTypeReferenceNode('Date')
131
431
  const numberType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
132
432
  const neverType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword)
133
433
  const stringType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
@@ -142,6 +442,10 @@ const recordType = ts.factory.createMappedTypeNode(
142
442
 
143
443
  // "Optional" modifier (the "?" token )
144
444
  const optionalKeyword = ts.factory.createToken(ts.SyntaxKind.QuestionToken)
445
+ // "export" modifier for declarations
446
+ const exportModifiers = [ ts.factory.createModifier(ts.SyntaxKind.ExportKeyword) ]
447
+ // "readonly" modifier for declarations
448
+ const readonlyModifiers = [ ts.factory.createModifier(ts.SyntaxKind.ReadonlyKeyword ) ]
145
449
 
146
450
  /* ========================================================================== */
147
451
 
@@ -152,20 +456,27 @@ registerTypeGenerator(AnyArrayValidator, () => anyArrayType)
152
456
  registerTypeGenerator(AnyNumberValidator, () => numberType)
153
457
  registerTypeGenerator(AnyObjectValidator, () => recordType)
154
458
  registerTypeGenerator(AnyStringValidator, () => stringType)
155
- registerTypeGenerator(BooleanValidator, () => booleanType)
156
459
  registerTypeGenerator(NeverValidator, () => neverType)
157
- registerTypeGenerator(DateValidator, () => ts.factory.createTypeReferenceNode('Date'))
158
- registerTypeGenerator(URLValidator, () => ts.factory.createTypeReferenceNode('URL'))
159
460
 
160
461
  /* ========================================================================== */
161
462
 
162
463
  // Complex generator functions...
163
464
 
164
- registerTypeGenerator(ArrayValidator, (validator, references) => {
165
- const itemType = generateTypeNode(validator.items, references)
465
+ registerTypeGenerator(ArrayValidator, (validator, references, isInput) => {
466
+ const itemType = generateTypeNode(validator.items, references, isInput)
166
467
  return ts.factory.createArrayTypeNode(itemType)
167
468
  })
168
469
 
470
+ registerTypeGenerator(BooleanValidator, (validator, _references, isInput) => {
471
+ return (isInput && validator.fromString) ?
472
+ ts.factory.createUnionTypeNode([
473
+ booleanType,
474
+ ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('true')),
475
+ ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('false')),
476
+ ]) :
477
+ booleanType
478
+ })
479
+
169
480
  registerTypeGenerator(ConstantValidator, (validator) => {
170
481
  const literal =
171
482
  typeof validator.constant === 'number' ? ts.factory.createNumericLiteral(validator.constant) :
@@ -179,7 +490,21 @@ registerTypeGenerator(ConstantValidator, (validator) => {
179
490
  return ts.factory.createLiteralTypeNode(literal)
180
491
  })
181
492
 
182
- registerTypeGenerator(NumberValidator, (validator: NumberValidator) => {
493
+ registerTypeGenerator(DateValidator, (validator: DateValidator, _references, isInput) => {
494
+ return isInput ?
495
+ validator.format === 'iso' ? stringType :
496
+ validator.format === 'timestamp' ? numberType :
497
+ ts.factory.createUnionTypeNode([ dateType, numberType, stringType ]) :
498
+ dateType
499
+ })
500
+
501
+ registerTypeGenerator(NumberValidator, (validator: NumberValidator, _references, isInput) => {
502
+ if (isInput) {
503
+ return validator.fromString ?
504
+ ts.factory.createUnionTypeNode([ numberType, stringType ]) :
505
+ numberType
506
+ }
507
+
183
508
  if (! validator.brand) return numberType
184
509
 
185
510
  const signature = ts.factory.createPropertySignature(undefined, `__brand_${validator.brand}`, undefined, neverType)
@@ -187,21 +512,21 @@ registerTypeGenerator(NumberValidator, (validator: NumberValidator) => {
187
512
  return ts.factory.createIntersectionTypeNode([ numberType, literal ])
188
513
  })
189
514
 
190
- registerTypeGenerator(OptionalValidator, (validator: OptionalValidator, references) => {
515
+ registerTypeGenerator(OptionalValidator, (validator: OptionalValidator, references, isInput: boolean) => {
191
516
  // return the wrappeed type. The '... | undefined' part of the optional will
192
517
  // be added in 'generateTypeNode' above, as _any_ validator can be optional
193
- return generateTypeNode(validator.validator, references)
518
+ return generateTypeNode(validator.validator, references, isInput)
194
519
  })
195
520
 
196
- registerTypeGenerator(StringValidator, (validator: StringValidator) => {
197
- if (! validator.brand) return stringType
521
+ registerTypeGenerator(StringValidator, (validator: StringValidator, _references, isInput) => {
522
+ if ((! validator.brand) || (isInput)) return stringType
198
523
 
199
524
  const signature = ts.factory.createPropertySignature(undefined, `__brand_${validator.brand}`, undefined, neverType)
200
525
  const literal = ts.factory.createTypeLiteralNode([ signature ])
201
526
  return ts.factory.createIntersectionTypeNode([ stringType, literal ])
202
527
  })
203
528
 
204
- registerTypeGenerator(TupleValidator, (validator: TupleValidator<any>, references) => {
529
+ registerTypeGenerator(TupleValidator, (validator: TupleValidator<any>, references, isInput) => {
205
530
  const members = validator.members
206
531
 
207
532
  // count how many rest parameters do we have..
@@ -218,9 +543,9 @@ registerTypeGenerator(TupleValidator, (validator: TupleValidator<any>, reference
218
543
  // if we have zero or one rest parameter, things are easy...
219
544
  if (count < 2) {
220
545
  const types = members.map(({ single, validator }) => {
221
- const memberType = generateTypeNode(validator, references)
546
+ const memberType = generateTypeNode(validator, references, isInput)
222
547
 
223
- if (single) return generateTypeNode(validator, references)
548
+ if (single) return generateTypeNode(validator, references, isInput)
224
549
 
225
550
  const arrayType = ts.factory.createArrayTypeNode(memberType)
226
551
  return ts.factory.createRestTypeNode(arrayType)
@@ -232,11 +557,11 @@ registerTypeGenerator(TupleValidator, (validator: TupleValidator<any>, reference
232
557
  // We have two or more rest parameters... we need combine everything between
233
558
  // the first and the last one in a giant union!
234
559
  const before = members.slice(0, first)
235
- .map(({ validator }) => generateTypeNode(validator, references))
560
+ .map(({ validator }) => generateTypeNode(validator, references, isInput))
236
561
  const types = members.slice(first, next)
237
- .map(({ validator }) => generateTypeNode(validator, references))
562
+ .map(({ validator }) => generateTypeNode(validator, references, isInput))
238
563
  const after = members.slice(next)
239
- .map(({ validator }) => generateTypeNode(validator, references))
564
+ .map(({ validator }) => generateTypeNode(validator, references, isInput))
240
565
 
241
566
  const union = ts.factory.createUnionTypeNode(types)
242
567
  const array = ts.factory.createArrayTypeNode(union)
@@ -245,22 +570,26 @@ registerTypeGenerator(TupleValidator, (validator: TupleValidator<any>, reference
245
570
  return ts.factory.createTupleTypeNode([ ...before, rest, ...after ])
246
571
  })
247
572
 
248
- registerTypeGenerator(AllOfValidator, (validator, references) => {
249
- const members = validator.validators.map((validator) => generateTypeNode(validator, references))
573
+ registerTypeGenerator(AllOfValidator, (validator, references, isInput) => {
574
+ const members = validator.validators.map((validator) => generateTypeNode(validator, references, isInput))
250
575
  return ts.factory.createIntersectionTypeNode(members)
251
576
  })
252
577
 
253
- registerTypeGenerator(OneOfValidator, (validator, references) => {
254
- const members = validator.validators.map((validator) => generateTypeNode(validator, references))
578
+ registerTypeGenerator(OneOfValidator, (validator, references, isInput) => {
579
+ const members = validator.validators.map((validator) => generateTypeNode(validator, references, isInput))
255
580
  return ts.factory.createUnionTypeNode(members)
256
581
  })
257
582
 
258
- registerTypeGenerator(ObjectValidator, (validator, references) => {
583
+ registerTypeGenerator(ObjectValidator, (validator, references, isInput) => {
259
584
  const properties: ts.PropertySignature[] = []
260
585
 
261
586
  for (const [ key, valueValidator ] of validator.validators.entries()) {
262
- const type = generateTypeNode(valueValidator, references)
263
- const optional = valueValidator.optional
587
+ const type = generateTypeNode(valueValidator, references, isInput)
588
+
589
+ // the optional keyword (question mark) is added when either the validator
590
+ // is optional or, when in input mode, there is no default value
591
+ const optional = (isInput && valueValidator.defaultValue !== undefined) ||
592
+ valueValidator.optional
264
593
 
265
594
  const signature = ts.factory.createPropertySignature(
266
595
  undefined,
@@ -272,7 +601,7 @@ registerTypeGenerator(ObjectValidator, (validator, references) => {
272
601
  }
273
602
 
274
603
  if (validator.additionalProperties) {
275
- const propertyType = generateTypeNode(validator.additionalProperties, references)
604
+ const propertyType = generateTypeNode(validator.additionalProperties, references, isInput)
276
605
 
277
606
  const extra = ts.factory.createMappedTypeNode(
278
607
  undefined, // readonly
@@ -290,3 +619,30 @@ registerTypeGenerator(ObjectValidator, (validator, references) => {
290
619
  return ts.factory.createTypeLiteralNode(properties)
291
620
  }
292
621
  })
622
+
623
+ /* ===== EXTRA TYPES ======================================================== */
624
+
625
+ registerTypeGenerator(EAN13Validator, (_validator, _references, isInput) => {
626
+ if (isInput) return ts.factory.createUnionTypeNode([ numberType, stringType ])
627
+
628
+ const signature = ts.factory.createPropertySignature(undefined, '__ean_13', undefined, neverType)
629
+ const literal = ts.factory.createTypeLiteralNode([ signature ])
630
+ return ts.factory.createIntersectionTypeNode([ stringType, literal ])
631
+ })
632
+
633
+ registerTypeGenerator(UUIDValidator, (_validator, _references, isInput) => {
634
+ if (isInput) return stringType
635
+
636
+ const signature = ts.factory.createPropertySignature(undefined, '__uuid', undefined, neverType)
637
+ const literal = ts.factory.createTypeLiteralNode([ signature ])
638
+ return ts.factory.createIntersectionTypeNode([ stringType, literal ])
639
+ })
640
+
641
+ registerTypeGenerator(URLValidator, (_validator, _references, isInput) => {
642
+ const urlType = ts.factory.createTypeReferenceNode('URL')
643
+ if (isInput) {
644
+ return ts.factory.createUnionTypeNode([ urlType, stringType ])
645
+ } else {
646
+ return urlType
647
+ }
648
+ })
package/src/extra/uuid.ts CHANGED
@@ -4,7 +4,7 @@ export const UUID_EXPR = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a
4
4
 
5
5
  export type UUIDString = string & { __uuid: never }
6
6
 
7
- export class UUIDValidator extends StringValidator<UUIDString> {
7
+ export class UUIDValidator extends StringValidator<UUIDString, string> {
8
8
  constructor() {
9
9
  super({ minLength: 36, maxLength: 36, pattern: UUID_EXPR })
10
10
  }