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.
- package/dist/dts-generator.cjs +264 -42
- package/dist/dts-generator.cjs.map +2 -2
- package/dist/dts-generator.d.ts +32 -3
- package/dist/dts-generator.mjs +263 -42
- package/dist/dts-generator.mjs.map +2 -2
- 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 +17 -6
- 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 +5 -8
- package/src/dts-generator.ts +411 -55
- package/src/extra/uuid.ts +1 -1
- package/src/types.ts +18 -6
- package/src/utilities.ts +4 -5
package/dist/utilities.mjs.map
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/utilities.ts"],
|
|
4
|
-
"mappings": ";AAAA,SAAS,gBAAgB;
|
|
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.
|
|
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.
|
|
82
|
-
"@plugjs/tsd": "^0.
|
|
83
|
-
"@types/chai": "^4.3.
|
|
84
|
-
"chai": "^4.3.
|
|
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": {
|
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,335 @@ 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
|
-
|
|
104
|
+
if (! references.has(validator)) references.set(validator, name)
|
|
105
|
+
}
|
|
66
106
|
|
|
67
|
-
|
|
68
|
-
const types
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
257
|
+
/* Pretty print our DTS */
|
|
87
258
|
return ts.createPrinter().printList(
|
|
88
259
|
ts.ListFormat.SourceFileStatements,
|
|
89
|
-
ts.factory.createNodeArray(
|
|
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
|
|
110
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
}
|