@webergency-utils/typechecker 0.1.10 → 0.2.1
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/LICENSE +21 -0
- package/README.md +86 -36
- package/dist/engine/generators.d.ts +2 -1
- package/dist/engine/generators.js +37 -11
- package/dist/engine/resolver.js +279 -68
- package/dist/engine/staticAsserts.js +2 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.js +19 -0
- package/dist/runtime/tags/format.d.ts +7 -0
- package/dist/runtime/tags/tag.d.ts +1 -1
- package/dist/runtime/tags.d.ts +1 -1
- package/dist/runtime/validators.d.ts +34 -10
- package/dist/runtime/validators.js +915 -212
- package/dist/transformer.js +5 -1
- package/package.json +12 -3
package/dist/engine/resolver.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from '../ts.js';
|
|
2
|
-
import { createPrimitiveCheck, createLiteralCheck, createArrayCheck, createUnionCheck, createObjectCheck, createDateCheck, createNullCheck, createUndefinedCheck, createIntersectionCheck, createTupleCheck, createRecordCheck, createRegExpCheck, createTemplateLiteralCheck, createConstrainedPrimitiveCheck, createSetCheck, createMapCheck } from './generators.js';
|
|
2
|
+
import { createPrimitiveCheck, createLiteralCheck, createArrayCheck, createUnionCheck, createObjectCheck, createDateCheck, createNullCheck, createUndefinedCheck, createIntersectionCheck, createTupleCheck, createRecordCheck, createRegExpCheck, createTemplateLiteralCheck, createConstrainedPrimitiveCheck, createSetCheck, createMapCheck, createInstanceOfCheck } from './generators.js';
|
|
3
3
|
import { createHash } from 'crypto';
|
|
4
4
|
function getStringLiteralValue(type) {
|
|
5
5
|
if (type.isStringLiteral()) {
|
|
@@ -48,6 +48,87 @@ function minifyTypeString(str) {
|
|
|
48
48
|
.replace(/:\s+/g, ':')
|
|
49
49
|
.replace(/\s+\|\s+/g, '|');
|
|
50
50
|
}
|
|
51
|
+
function isNativeEnumType(type) {
|
|
52
|
+
const flags = type.getFlags();
|
|
53
|
+
if (flags & ts.TypeFlags.Enum) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const symbol = type.getSymbol();
|
|
57
|
+
if (!symbol) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return (symbol.flags & (ts.SymbolFlags.RegularEnum | ts.SymbolFlags.ConstEnum)) !== 0;
|
|
61
|
+
}
|
|
62
|
+
function buildEnumValidator(type, checker, validatorsMap, requiredUtils) {
|
|
63
|
+
const symbol = type.getSymbol();
|
|
64
|
+
const checks = [];
|
|
65
|
+
if (symbol?.exports) {
|
|
66
|
+
symbol.exports.forEach(member => {
|
|
67
|
+
if (!(member.flags & ts.SymbolFlags.EnumMember)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const declaration = member.valueDeclaration || member.declarations?.[0];
|
|
71
|
+
if (!declaration) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const memberType = checker.getTypeOfSymbolAtLocation(member, declaration);
|
|
75
|
+
checks.push(buildValidator(memberType, checker, validatorsMap, requiredUtils));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (checks.length === 0 && type.isUnion()) {
|
|
79
|
+
const unionChecks = type.types.map(t => buildValidator(t, checker, validatorsMap, requiredUtils));
|
|
80
|
+
return createUnionCheck(unionChecks, requiredUtils, `Type<${minifyTypeString(checker.typeToString(type))}>`);
|
|
81
|
+
}
|
|
82
|
+
if (checks.length === 0) {
|
|
83
|
+
return createPrimitiveCheck('any', requiredUtils);
|
|
84
|
+
}
|
|
85
|
+
if (checks.length === 1) {
|
|
86
|
+
return checks[0];
|
|
87
|
+
}
|
|
88
|
+
return createUnionCheck(checks, requiredUtils, `Type<${minifyTypeString(checker.typeToString(type))}>`);
|
|
89
|
+
}
|
|
90
|
+
function isConstraintOnlyType(type, checker) {
|
|
91
|
+
const props = checker.getPropertiesOfType(type);
|
|
92
|
+
return props.length > 0 && props.every(p => p.getName().startsWith('__'));
|
|
93
|
+
}
|
|
94
|
+
function tryMergeObjectIntersection(types, checker, validatorsMap, requiredUtils, expected) {
|
|
95
|
+
const objectTypes = types.filter(t => {
|
|
96
|
+
if (isConstraintOnlyType(t, checker)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const flags = t.getFlags();
|
|
100
|
+
return ((flags & ts.TypeFlags.Object) !== 0) || t.isClassOrInterface();
|
|
101
|
+
});
|
|
102
|
+
if (objectTypes.length < 2) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const others = types.filter(t => !isConstraintOnlyType(t, checker) && !objectTypes.includes(t));
|
|
106
|
+
if (others.length > 0) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const propMap = new Map();
|
|
110
|
+
let indexValidator;
|
|
111
|
+
for (const t of objectTypes) {
|
|
112
|
+
const stringIndexInfo = checker.getIndexInfoOfType(t, ts.IndexKind.String);
|
|
113
|
+
if (stringIndexInfo) {
|
|
114
|
+
indexValidator = buildValidator(stringIndexInfo.type, checker, validatorsMap, requiredUtils);
|
|
115
|
+
}
|
|
116
|
+
for (const prop of checker.getPropertiesOfType(t)) {
|
|
117
|
+
const name = prop.getName();
|
|
118
|
+
if (name.startsWith('__')) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const declaration = prop.valueDeclaration || prop.declarations?.[0];
|
|
122
|
+
const propType = declaration ? checker.getTypeOfSymbolAtLocation(prop, declaration) : checker.getAnyType();
|
|
123
|
+
propMap.set(name, {
|
|
124
|
+
name,
|
|
125
|
+
isOptional: (prop.getFlags() & ts.SymbolFlags.Optional) !== 0,
|
|
126
|
+
validator: buildValidator(propType, checker, validatorsMap, requiredUtils)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return createObjectCheck([...propMap.values()], requiredUtils, expected, indexValidator);
|
|
131
|
+
}
|
|
51
132
|
export function buildValidator(type, checker, validatorsMap, requiredUtils) {
|
|
52
133
|
const hash = generateHash(type, checker);
|
|
53
134
|
if (validatorsMap.has(hash)) {
|
|
@@ -159,7 +240,7 @@ export function buildValidator(type, checker, validatorsMap, requiredUtils) {
|
|
|
159
240
|
}
|
|
160
241
|
}
|
|
161
242
|
if (fnName === '__function' || !fnName) {
|
|
162
|
-
throw new Error('[Webergency] Custom
|
|
243
|
+
throw new Error('[Webergency] Custom transform must reference a named function via typeof (e.g. transform.Custom<typeof myFunc>).');
|
|
163
244
|
}
|
|
164
245
|
if (filePath) {
|
|
165
246
|
requiredUtils.add(`custom:${fnName}:${filePath}`);
|
|
@@ -199,7 +280,7 @@ export function buildValidator(type, checker, validatorsMap, requiredUtils) {
|
|
|
199
280
|
}
|
|
200
281
|
}
|
|
201
282
|
if (fnName === '__function' || !fnName) {
|
|
202
|
-
throw new Error('[Webergency] Custom validator must reference a named function via typeof (e.g.
|
|
283
|
+
throw new Error('[Webergency] Custom validator must reference a named function via typeof (e.g. constraint.Custom<typeof myFunc>).');
|
|
203
284
|
}
|
|
204
285
|
if (filePath) {
|
|
205
286
|
requiredUtils.add(`custom:${fnName}:${filePath}`);
|
|
@@ -306,21 +387,27 @@ export function buildValidator(type, checker, validatorsMap, requiredUtils) {
|
|
|
306
387
|
baseValidator = buildValidator(nonConstraintTypes[0], checker, validatorsMap, requiredUtils);
|
|
307
388
|
}
|
|
308
389
|
else if (nonConstraintTypes.length > 1) {
|
|
309
|
-
const
|
|
310
|
-
baseValidator = createIntersectionCheck(
|
|
390
|
+
const merged = tryMergeObjectIntersection(nonConstraintTypes, checker, validatorsMap, requiredUtils, minifyTypeString(checker.typeToString(type)));
|
|
391
|
+
baseValidator = merged || createIntersectionCheck(nonConstraintTypes.map(t => buildValidator(t, checker, validatorsMap, requiredUtils)), requiredUtils);
|
|
311
392
|
}
|
|
312
393
|
if (baseValidator) {
|
|
313
394
|
result = createConstrainedPrimitiveCheck('any', constraints, requiredUtils, baseValidator);
|
|
314
395
|
}
|
|
315
396
|
else {
|
|
316
|
-
const
|
|
317
|
-
result = createIntersectionCheck(
|
|
397
|
+
const merged = tryMergeObjectIntersection(types, checker, validatorsMap, requiredUtils, minifyTypeString(checker.typeToString(type)));
|
|
398
|
+
result = merged || createIntersectionCheck(type.types.map(t => buildValidator(t, checker, validatorsMap, requiredUtils)), requiredUtils);
|
|
318
399
|
}
|
|
319
400
|
}
|
|
320
401
|
}
|
|
321
402
|
else {
|
|
322
|
-
const
|
|
323
|
-
|
|
403
|
+
const merged = tryMergeObjectIntersection(types, checker, validatorsMap, requiredUtils, minifyTypeString(checker.typeToString(type)));
|
|
404
|
+
if (merged) {
|
|
405
|
+
result = merged;
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
const checks = type.types.map(t => buildValidator(t, checker, validatorsMap, requiredUtils));
|
|
409
|
+
result = createIntersectionCheck(checks, requiredUtils);
|
|
410
|
+
}
|
|
324
411
|
}
|
|
325
412
|
}
|
|
326
413
|
else if (type.getSymbol()?.name === 'Date') {
|
|
@@ -338,6 +425,15 @@ export function buildValidator(type, checker, validatorsMap, requiredUtils) {
|
|
|
338
425
|
const valueType = type.typeArguments?.[1] || checker.getAnyType();
|
|
339
426
|
result = createMapCheck(buildValidator(keyType, checker, validatorsMap, requiredUtils), buildValidator(valueType, checker, validatorsMap, requiredUtils), requiredUtils);
|
|
340
427
|
}
|
|
428
|
+
else if (type.getSymbol()?.name === 'Promise') {
|
|
429
|
+
result = createInstanceOfCheck('Promise', requiredUtils);
|
|
430
|
+
}
|
|
431
|
+
else if (type.getSymbol()?.name && [
|
|
432
|
+
'Uint8Array', 'Uint16Array', 'Uint32Array', 'Int8Array', 'Int16Array', 'Int32Array',
|
|
433
|
+
'Float32Array', 'Float64Array', 'ArrayBuffer', 'SharedArrayBuffer', 'DataView', 'Buffer'
|
|
434
|
+
].includes(type.getSymbol().name)) {
|
|
435
|
+
result = createInstanceOfCheck(type.getSymbol().name, requiredUtils);
|
|
436
|
+
}
|
|
341
437
|
else if (flags & ts.TypeFlags.Null) {
|
|
342
438
|
result = createNullCheck(requiredUtils);
|
|
343
439
|
}
|
|
@@ -356,6 +452,12 @@ export function buildValidator(type, checker, validatorsMap, requiredUtils) {
|
|
|
356
452
|
else if (flags & ts.TypeFlags.Boolean) {
|
|
357
453
|
result = createPrimitiveCheck('boolean', requiredUtils);
|
|
358
454
|
}
|
|
455
|
+
else if (flags & ts.TypeFlags.Never) {
|
|
456
|
+
result = createPrimitiveCheck('never', requiredUtils);
|
|
457
|
+
}
|
|
458
|
+
else if (flags & ts.TypeFlags.ESSymbol || flags & ts.TypeFlags.UniqueESSymbol || type.intrinsicName === 'symbol') {
|
|
459
|
+
result = createPrimitiveCheck('symbol', requiredUtils);
|
|
460
|
+
}
|
|
359
461
|
else if (flags & ts.TypeFlags.TemplateLiteral) {
|
|
360
462
|
const templateType = type;
|
|
361
463
|
let regexStr = '^';
|
|
@@ -404,23 +506,32 @@ export function buildValidator(type, checker, validatorsMap, requiredUtils) {
|
|
|
404
506
|
const elementType = type.typeArguments?.[0] || checker.getAnyType();
|
|
405
507
|
result = createArrayCheck(buildValidator(elementType, checker, validatorsMap, requiredUtils), requiredUtils);
|
|
406
508
|
}
|
|
509
|
+
else if (type.getCallSignatures().length > 0 && type.getConstructSignatures().length === 0) {
|
|
510
|
+
result = createPrimitiveCheck('function', requiredUtils);
|
|
511
|
+
}
|
|
512
|
+
else if (isNativeEnumType(type)) {
|
|
513
|
+
result = buildEnumValidator(type, checker, validatorsMap, requiredUtils);
|
|
514
|
+
}
|
|
407
515
|
else {
|
|
408
516
|
const stringIndexInfo = checker.getIndexInfoOfType(type, ts.IndexKind.String);
|
|
409
|
-
|
|
517
|
+
const props = checker.getPropertiesOfType(type).map(prop => {
|
|
518
|
+
const declaration = prop.valueDeclaration || prop.declarations?.[0];
|
|
519
|
+
const propType = declaration ? checker.getTypeOfSymbolAtLocation(prop, declaration) : checker.getAnyType();
|
|
520
|
+
return {
|
|
521
|
+
name: prop.getName(),
|
|
522
|
+
isOptional: (prop.getFlags() & ts.SymbolFlags.Optional) !== 0,
|
|
523
|
+
validator: buildValidator(propType, checker, validatorsMap, requiredUtils)
|
|
524
|
+
};
|
|
525
|
+
});
|
|
526
|
+
if (stringIndexInfo && props.length === 0) {
|
|
410
527
|
result = createRecordCheck(buildValidator(stringIndexInfo.type, checker, validatorsMap, requiredUtils), requiredUtils);
|
|
411
528
|
}
|
|
412
|
-
else if (flags & ts.TypeFlags.Object || type.isClassOrInterface() || type.isTypeParameter()) {
|
|
413
|
-
const props = checker.getPropertiesOfType(type).map(prop => {
|
|
414
|
-
const declaration = prop.valueDeclaration || prop.declarations?.[0];
|
|
415
|
-
const propType = declaration ? checker.getTypeOfSymbolAtLocation(prop, declaration) : checker.getAnyType();
|
|
416
|
-
return {
|
|
417
|
-
name: prop.getName(),
|
|
418
|
-
isOptional: (prop.getFlags() & ts.SymbolFlags.Optional) !== 0,
|
|
419
|
-
validator: buildValidator(propType, checker, validatorsMap, requiredUtils)
|
|
420
|
-
};
|
|
421
|
-
});
|
|
529
|
+
else if (flags & ts.TypeFlags.Object || type.isClassOrInterface() || type.isTypeParameter() || stringIndexInfo) {
|
|
422
530
|
const typeName = checker.typeToString(type);
|
|
423
|
-
|
|
531
|
+
const indexValidator = stringIndexInfo
|
|
532
|
+
? buildValidator(stringIndexInfo.type, checker, validatorsMap, requiredUtils)
|
|
533
|
+
: undefined;
|
|
534
|
+
result = createObjectCheck(props, requiredUtils, typeName, indexValidator);
|
|
424
535
|
}
|
|
425
536
|
else {
|
|
426
537
|
result = createPrimitiveCheck('any', requiredUtils);
|
|
@@ -475,6 +586,38 @@ function buildStructuralSignature(type, checker, visited = new Set()) {
|
|
|
475
586
|
if (flags & ts.TypeFlags.Undefined || flags & ts.TypeFlags.Void) {
|
|
476
587
|
return 'undefined';
|
|
477
588
|
}
|
|
589
|
+
if (flags & ts.TypeFlags.Never) {
|
|
590
|
+
return 'never';
|
|
591
|
+
}
|
|
592
|
+
if (flags & ts.TypeFlags.Unknown) {
|
|
593
|
+
return 'unknown';
|
|
594
|
+
}
|
|
595
|
+
if (flags & ts.TypeFlags.Any) {
|
|
596
|
+
return 'any';
|
|
597
|
+
}
|
|
598
|
+
if (flags & ts.TypeFlags.ESSymbol || flags & ts.TypeFlags.UniqueESSymbol || type.intrinsicName === 'symbol') {
|
|
599
|
+
return 'symbol';
|
|
600
|
+
}
|
|
601
|
+
if (type.getCallSignatures().length > 0 && type.getConstructSignatures().length === 0) {
|
|
602
|
+
return 'function';
|
|
603
|
+
}
|
|
604
|
+
if (isNativeEnumType(type)) {
|
|
605
|
+
const symbol = type.getSymbol();
|
|
606
|
+
const members = [];
|
|
607
|
+
if (symbol?.exports) {
|
|
608
|
+
symbol.exports.forEach(member => {
|
|
609
|
+
if (!(member.flags & ts.SymbolFlags.EnumMember)) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const declaration = member.valueDeclaration || member.declarations?.[0];
|
|
613
|
+
if (!declaration) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
members.push(buildStructuralSignature(checker.getTypeOfSymbolAtLocation(member, declaration), checker, visited));
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
return `Enum<${symbol?.name || 'anonymous'}:${members.sort().join(',')}>`;
|
|
620
|
+
}
|
|
478
621
|
if (flags & ts.TypeFlags.TemplateLiteral) {
|
|
479
622
|
const templateType = type;
|
|
480
623
|
return `TemplateLiteral<${templateType.texts.join(',')}|${templateType.types.map(t => buildStructuralSignature(t, checker, visited)).join(',')}>`;
|
|
@@ -483,11 +626,37 @@ function buildStructuralSignature(type, checker, visited = new Set()) {
|
|
|
483
626
|
const elementType = type.typeArguments?.[0] || checker.getAnyType();
|
|
484
627
|
return `Array<${buildStructuralSignature(elementType, checker, visited)}>`;
|
|
485
628
|
}
|
|
629
|
+
const typeSymbolName = type.getSymbol()?.name || type.aliasSymbol?.getName();
|
|
630
|
+
if (typeSymbolName === 'Date') {
|
|
631
|
+
return 'Date';
|
|
632
|
+
}
|
|
633
|
+
if (typeSymbolName === 'RegExp') {
|
|
634
|
+
return 'RegExp';
|
|
635
|
+
}
|
|
636
|
+
if (typeSymbolName === 'Promise') {
|
|
637
|
+
const valueType = type.typeArguments?.[0] || checker.getAnyType();
|
|
638
|
+
return `Promise<${buildStructuralSignature(valueType, checker, visited)}>`;
|
|
639
|
+
}
|
|
640
|
+
if (typeSymbolName === 'Set') {
|
|
641
|
+
const elementType = type.typeArguments?.[0] || checker.getAnyType();
|
|
642
|
+
return `Set<${buildStructuralSignature(elementType, checker, visited)}>`;
|
|
643
|
+
}
|
|
644
|
+
if (typeSymbolName === 'Map') {
|
|
645
|
+
const keyType = type.typeArguments?.[0] || checker.getAnyType();
|
|
646
|
+
const valueType = type.typeArguments?.[1] || checker.getAnyType();
|
|
647
|
+
return `Map<${buildStructuralSignature(keyType, checker, visited)},${buildStructuralSignature(valueType, checker, visited)}>`;
|
|
648
|
+
}
|
|
649
|
+
if (typeSymbolName && [
|
|
650
|
+
'Uint8Array', 'Uint16Array', 'Uint32Array', 'Int8Array', 'Int16Array', 'Int32Array',
|
|
651
|
+
'Float32Array', 'Float64Array', 'ArrayBuffer', 'SharedArrayBuffer', 'DataView', 'Buffer'
|
|
652
|
+
].includes(typeSymbolName)) {
|
|
653
|
+
return typeSymbolName;
|
|
654
|
+
}
|
|
486
655
|
if (flags & ts.TypeFlags.Object || type.isClassOrInterface()) {
|
|
487
656
|
const props = checker.getPropertiesOfType(type);
|
|
657
|
+
const stringIndexInfo = checker.getIndexInfoOfType(type, ts.IndexKind.String);
|
|
488
658
|
if (props.length === 0) {
|
|
489
659
|
// Handle Record or empty object
|
|
490
|
-
const stringIndexInfo = checker.getIndexInfoOfType(type, ts.IndexKind.String);
|
|
491
660
|
if (stringIndexInfo) {
|
|
492
661
|
return `Record<${buildStructuralSignature(stringIndexInfo.type, checker, visited)}>`;
|
|
493
662
|
}
|
|
@@ -498,7 +667,10 @@ function buildStructuralSignature(type, checker, visited = new Set()) {
|
|
|
498
667
|
const isOptional = (prop.getFlags() & ts.SymbolFlags.Optional) !== 0;
|
|
499
668
|
return `${prop.getName()}${isOptional ? '?' : ''}:${buildStructuralSignature(propType, checker, visited)}`;
|
|
500
669
|
}).sort();
|
|
501
|
-
|
|
670
|
+
const indexSig = stringIndexInfo
|
|
671
|
+
? `;[string]:${buildStructuralSignature(stringIndexInfo.type, checker, visited)}`
|
|
672
|
+
: '';
|
|
673
|
+
return `Object{${propSigs.join(';')}${indexSig}}`;
|
|
502
674
|
}
|
|
503
675
|
return 'any';
|
|
504
676
|
}
|
|
@@ -679,42 +851,14 @@ function buildJsonSchemaInternal(type, checker, defs, visited, counts, circularH
|
|
|
679
851
|
const types = type.types;
|
|
680
852
|
let baseSchema = {};
|
|
681
853
|
const constraints = {};
|
|
854
|
+
const memberSchemas = [];
|
|
682
855
|
for (const sub of types) {
|
|
683
856
|
const sFlags = sub.getFlags();
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
else if (sFlags & ts.TypeFlags.BigInt) {
|
|
691
|
-
baseSchema = { type: 'integer' };
|
|
692
|
-
}
|
|
693
|
-
else if (sFlags & ts.TypeFlags.Boolean || sub.intrinsicName === 'boolean') {
|
|
694
|
-
baseSchema = { type: 'boolean' };
|
|
695
|
-
}
|
|
696
|
-
else if (sub.getSymbol()?.name === 'Date') {
|
|
697
|
-
baseSchema = { type: 'string', format: 'date-time' };
|
|
698
|
-
}
|
|
699
|
-
else if (sub.getSymbol()?.name === 'RegExp') {
|
|
700
|
-
baseSchema = { type: 'string', format: 'regex' };
|
|
701
|
-
}
|
|
702
|
-
else if (sub.getSymbol()?.name === 'Set') {
|
|
703
|
-
const elementType = sub.typeArguments?.[0] || checker.getAnyType();
|
|
704
|
-
baseSchema = { type: 'array', items: buildJsonSchemaInternal(elementType, checker, defs, visited, counts, circularHashes), uniqueItems: true };
|
|
705
|
-
}
|
|
706
|
-
else if (sub.getSymbol()?.name === 'Map') {
|
|
707
|
-
const valueType = sub.typeArguments?.[1] || checker.getAnyType();
|
|
708
|
-
baseSchema = { type: 'object', additionalProperties: buildJsonSchemaInternal(valueType, checker, defs, visited, counts, circularHashes) };
|
|
709
|
-
}
|
|
710
|
-
else if (checker.isArrayType(sub)) {
|
|
711
|
-
const elementType = sub.typeArguments?.[0] || checker.getAnyType();
|
|
712
|
-
baseSchema = { type: 'array', items: buildJsonSchemaInternal(elementType, checker, defs, visited, counts, circularHashes) };
|
|
713
|
-
}
|
|
714
|
-
const props = checker.getPropertiesOfType(sub);
|
|
715
|
-
for (const prop of props) {
|
|
716
|
-
const pName = prop.getName();
|
|
717
|
-
if (pName.startsWith('__')) {
|
|
857
|
+
const subProps = checker.getPropertiesOfType(sub);
|
|
858
|
+
const isConstraintPhantom = subProps.length > 0 && subProps.every(p => p.getName().startsWith('__'));
|
|
859
|
+
if (isConstraintPhantom) {
|
|
860
|
+
for (const prop of subProps) {
|
|
861
|
+
const pName = prop.getName();
|
|
718
862
|
const pType = checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration || prop.declarations?.[0]);
|
|
719
863
|
const actualType = stripUndefinedFromType(pType);
|
|
720
864
|
const val = getTagPropertyValue(pType);
|
|
@@ -775,36 +919,97 @@ function buildJsonSchemaInternal(type, checker, defs, visited, counts, circularH
|
|
|
775
919
|
constraints.requires = reqVal;
|
|
776
920
|
}
|
|
777
921
|
}
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
if (sFlags & ts.TypeFlags.String || sFlags & ts.TypeFlags.TemplateLiteral) {
|
|
925
|
+
baseSchema = { type: 'string' };
|
|
926
|
+
}
|
|
927
|
+
else if (sFlags & ts.TypeFlags.Number) {
|
|
928
|
+
baseSchema = { type: 'number' };
|
|
929
|
+
}
|
|
930
|
+
else if (sFlags & ts.TypeFlags.BigInt) {
|
|
931
|
+
baseSchema = { 'x-typescript-type': 'bigint' };
|
|
778
932
|
}
|
|
933
|
+
else if (sFlags & ts.TypeFlags.Boolean || sub.intrinsicName === 'boolean') {
|
|
934
|
+
baseSchema = { type: 'boolean' };
|
|
935
|
+
}
|
|
936
|
+
else if (sub.getSymbol()?.name === 'Date') {
|
|
937
|
+
baseSchema = { 'x-typescript-type': 'Date' };
|
|
938
|
+
}
|
|
939
|
+
else if (sub.getSymbol()?.name === 'RegExp') {
|
|
940
|
+
baseSchema = { 'x-typescript-type': 'RegExp' };
|
|
941
|
+
}
|
|
942
|
+
else if (sub.getSymbol()?.name === 'Set') {
|
|
943
|
+
const elementType = sub.typeArguments?.[0] || checker.getAnyType();
|
|
944
|
+
baseSchema = {
|
|
945
|
+
'x-typescript-type': 'Set',
|
|
946
|
+
items: buildJsonSchemaInternal(elementType, checker, defs, visited, counts, circularHashes)
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
else if (sub.getSymbol()?.name === 'Map') {
|
|
950
|
+
const keyType = sub.typeArguments?.[0] || checker.getAnyType();
|
|
951
|
+
const valueType = sub.typeArguments?.[1] || checker.getAnyType();
|
|
952
|
+
baseSchema = {
|
|
953
|
+
'x-typescript-type': 'Map',
|
|
954
|
+
key: buildJsonSchemaInternal(keyType, checker, defs, visited, counts, circularHashes),
|
|
955
|
+
value: buildJsonSchemaInternal(valueType, checker, defs, visited, counts, circularHashes)
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
else if (checker.isArrayType(sub)) {
|
|
959
|
+
const elementType = sub.typeArguments?.[0] || checker.getAnyType();
|
|
960
|
+
baseSchema = { type: 'array', items: buildJsonSchemaInternal(elementType, checker, defs, visited, counts, circularHashes) };
|
|
961
|
+
}
|
|
962
|
+
else if ((sFlags & ts.TypeFlags.Object) || sub.isClassOrInterface()) {
|
|
963
|
+
memberSchemas.push(buildJsonSchemaInternal(sub, checker, defs, visited, counts, circularHashes));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (memberSchemas.length > 1) {
|
|
967
|
+
return { allOf: memberSchemas, ...constraints };
|
|
968
|
+
}
|
|
969
|
+
if (memberSchemas.length === 1) {
|
|
970
|
+
return { ...memberSchemas[0], ...constraints };
|
|
779
971
|
}
|
|
780
972
|
return { ...baseSchema, ...constraints };
|
|
781
973
|
}
|
|
782
974
|
if (type.getSymbol()?.name === 'Date') {
|
|
783
|
-
return { type
|
|
975
|
+
return { 'x-typescript-type': 'Date' };
|
|
784
976
|
}
|
|
785
977
|
if (type.getSymbol()?.name === 'RegExp') {
|
|
786
|
-
return { type
|
|
978
|
+
return { 'x-typescript-type': 'RegExp' };
|
|
979
|
+
}
|
|
980
|
+
if (type.getSymbol()?.name === 'Promise') {
|
|
981
|
+
return { 'x-typescript-type': 'Promise' };
|
|
982
|
+
}
|
|
983
|
+
{
|
|
984
|
+
const typedName = type.getSymbol()?.name;
|
|
985
|
+
if (typedName && [
|
|
986
|
+
'Uint8Array', 'Uint16Array', 'Uint32Array', 'Int8Array', 'Int16Array', 'Int32Array',
|
|
987
|
+
'Float32Array', 'Float64Array', 'ArrayBuffer', 'SharedArrayBuffer', 'DataView', 'Buffer'
|
|
988
|
+
].includes(typedName)) {
|
|
989
|
+
return { 'x-typescript-type': typedName };
|
|
990
|
+
}
|
|
787
991
|
}
|
|
788
992
|
if (type.getSymbol()?.name === 'Set') {
|
|
789
993
|
const elementType = type.typeArguments?.[0] || checker.getAnyType();
|
|
790
994
|
return {
|
|
791
|
-
type: '
|
|
792
|
-
items: buildJsonSchemaInternal(elementType, checker, defs, visited, counts, circularHashes)
|
|
793
|
-
uniqueItems: true
|
|
995
|
+
'x-typescript-type': 'Set',
|
|
996
|
+
items: buildJsonSchemaInternal(elementType, checker, defs, visited, counts, circularHashes)
|
|
794
997
|
};
|
|
795
998
|
}
|
|
796
999
|
if (type.getSymbol()?.name === 'Map') {
|
|
1000
|
+
const keyType = type.typeArguments?.[0] || checker.getAnyType();
|
|
797
1001
|
const valueType = type.typeArguments?.[1] || checker.getAnyType();
|
|
798
1002
|
return {
|
|
799
|
-
type: '
|
|
800
|
-
|
|
1003
|
+
'x-typescript-type': 'Map',
|
|
1004
|
+
key: buildJsonSchemaInternal(keyType, checker, defs, visited, counts, circularHashes),
|
|
1005
|
+
value: buildJsonSchemaInternal(valueType, checker, defs, visited, counts, circularHashes)
|
|
801
1006
|
};
|
|
802
1007
|
}
|
|
803
1008
|
if (flags & ts.TypeFlags.Null) {
|
|
804
1009
|
return { type: 'null' };
|
|
805
1010
|
}
|
|
806
1011
|
if (flags & ts.TypeFlags.Undefined || flags & ts.TypeFlags.Void) {
|
|
807
|
-
return { type
|
|
1012
|
+
return { 'x-typescript-type': 'undefined' };
|
|
808
1013
|
}
|
|
809
1014
|
if (flags & ts.TypeFlags.String) {
|
|
810
1015
|
return { type: 'string' };
|
|
@@ -813,7 +1018,7 @@ function buildJsonSchemaInternal(type, checker, defs, visited, counts, circularH
|
|
|
813
1018
|
return { type: 'number' };
|
|
814
1019
|
}
|
|
815
1020
|
if (flags & ts.TypeFlags.BigInt) {
|
|
816
|
-
return { type: '
|
|
1021
|
+
return { 'x-typescript-type': 'bigint' };
|
|
817
1022
|
}
|
|
818
1023
|
if (flags & ts.TypeFlags.Boolean || type.intrinsicName === 'boolean') {
|
|
819
1024
|
return { type: 'boolean' };
|
|
@@ -848,6 +1053,7 @@ function buildJsonSchemaInternal(type, checker, defs, visited, counts, circularH
|
|
|
848
1053
|
}
|
|
849
1054
|
// Object types
|
|
850
1055
|
if (flags & ts.TypeFlags.Object || type.isClassOrInterface()) {
|
|
1056
|
+
const stringIndexInfo = checker.getIndexInfoOfType(type, ts.IndexKind.String);
|
|
851
1057
|
const symbol = type.getSymbol() || type.aliasSymbol;
|
|
852
1058
|
const name = symbol ? symbol.getName() : 'Object';
|
|
853
1059
|
const typeHash = generateHash(type, checker);
|
|
@@ -875,9 +1081,14 @@ function buildJsonSchemaInternal(type, checker, defs, visited, counts, circularH
|
|
|
875
1081
|
}
|
|
876
1082
|
const schemaObj = {
|
|
877
1083
|
type: 'object',
|
|
878
|
-
properties
|
|
879
|
-
additionalProperties: false
|
|
1084
|
+
properties
|
|
880
1085
|
};
|
|
1086
|
+
if (stringIndexInfo) {
|
|
1087
|
+
schemaObj.additionalProperties = buildJsonSchemaInternal(stringIndexInfo.type, checker, defs, visited, counts, circularHashes);
|
|
1088
|
+
}
|
|
1089
|
+
else {
|
|
1090
|
+
schemaObj.additionalProperties = false;
|
|
1091
|
+
}
|
|
881
1092
|
if (required.length > 0) {
|
|
882
1093
|
schemaObj.required = required;
|
|
883
1094
|
}
|
|
@@ -173,7 +173,8 @@ export function evaluateStaticConstraints(constant, constraints) {
|
|
|
173
173
|
ok = BigInt(v) % BigInt(n) === 0n;
|
|
174
174
|
}
|
|
175
175
|
else {
|
|
176
|
-
|
|
176
|
+
const q = v / n;
|
|
177
|
+
ok = Math.abs(q - Math.round(q)) <= 1e-8 * Math.max(1, Math.abs(q));
|
|
177
178
|
}
|
|
178
179
|
if (!ok) {
|
|
179
180
|
errors.push(c.message || `Value ${String(v)} does not satisfy MultipleOf<${n}>`);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import { ResolveDefaults } from './runtime/tags.js';
|
|
2
|
+
import type { IValidationError } from './runtime/validators.js';
|
|
2
3
|
export interface IValidation<T> {
|
|
3
4
|
success: boolean;
|
|
4
5
|
data?: T;
|
|
5
|
-
errors?:
|
|
6
|
+
errors?: IValidationError[];
|
|
6
7
|
}
|
|
7
8
|
export type ValidationMode = 'strict' | 'relaxed' | 'strip';
|
|
8
9
|
export interface ValidationOptions {
|
|
9
10
|
mode?: ValidationMode;
|
|
10
|
-
|
|
11
|
+
from?: 'json' | 'query' | ((key: string, value: any, type: 'string' | 'number' | 'boolean' | 'bigint' | 'function' | 'symbol' | 'never' | 'Date' | 'RegExp' | 'Set' | 'Map' | 'Array' | 'Object' | 'instance' | 'null' | 'undefined' | 'tuple' | 'literal') => any);
|
|
11
12
|
wrapArrays?: boolean;
|
|
13
|
+
/** When true, write validated/coerced values onto the input. Default false: always return new containers. */
|
|
14
|
+
mutate?: boolean;
|
|
12
15
|
schema?: any;
|
|
13
16
|
errorFactory?: (errors: any[]) => Error;
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
export declare function
|
|
17
|
-
|
|
18
|
-
export declare function
|
|
18
|
+
/** Returns whether `input` already matches `T`. Does not coerce; `from` is ignored. */
|
|
19
|
+
export declare function is<T>(_input: unknown, _options?: ValidationMode | ValidationOptions): _input is ResolveDefaults<T>;
|
|
20
|
+
/** Validates and returns the (possibly coerced) value. Use `from` when conversion is needed. */
|
|
21
|
+
export declare function assert<T>(_input: unknown, _options?: ValidationMode | ValidationOptions): ResolveDefaults<T>;
|
|
22
|
+
/** Asserts `input` already matches `T`. Does not coerce; `from` is ignored. */
|
|
23
|
+
export declare function assertGuard<T>(_input: unknown, _options?: ValidationMode | ValidationOptions): asserts _input is ResolveDefaults<T>;
|
|
24
|
+
export declare function validate<T>(_input: unknown, _options?: ValidationMode | ValidationOptions): IValidation<ResolveDefaults<T>>;
|
|
19
25
|
export declare function jsonSchema<T>(): any;
|
|
20
26
|
export * from './runtime/validators.js';
|
|
21
27
|
export * from './runtime/tags.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
const TRANSFORMER_MISSING = 'Typechecker transformer was not applied. Register { "transform": "@webergency-utils/typechecker/transformer" } in tsconfig plugins (requires ts-patch).';
|
|
2
|
+
/** Returns whether `input` already matches `T`. Does not coerce; `from` is ignored. */
|
|
3
|
+
export function is(_input, _options) {
|
|
4
|
+
throw new Error(TRANSFORMER_MISSING);
|
|
5
|
+
}
|
|
6
|
+
/** Validates and returns the (possibly coerced) value. Use `from` when conversion is needed. */
|
|
7
|
+
export function assert(_input, _options) {
|
|
8
|
+
throw new Error(TRANSFORMER_MISSING);
|
|
9
|
+
}
|
|
10
|
+
/** Asserts `input` already matches `T`. Does not coerce; `from` is ignored. */
|
|
11
|
+
export function assertGuard(_input, _options) {
|
|
12
|
+
throw new Error(TRANSFORMER_MISSING);
|
|
13
|
+
}
|
|
14
|
+
export function validate(_input, _options) {
|
|
15
|
+
throw new Error(TRANSFORMER_MISSING);
|
|
16
|
+
}
|
|
17
|
+
export function jsonSchema() {
|
|
18
|
+
throw new Error(TRANSFORMER_MISSING);
|
|
19
|
+
}
|
|
1
20
|
export * from './runtime/validators.js';
|
|
2
21
|
export * from './runtime/tags.js';
|
|
3
22
|
export * from './runtime/casing.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Format } from './constraint.js';
|
|
2
2
|
export type Email = Format<'email'>;
|
|
3
|
+
export type IdnEmail = Format<'idn-email'>;
|
|
3
4
|
export type UUID = Format<'uuid'>;
|
|
4
5
|
export type URL = Format<'url'>;
|
|
5
6
|
export type IPv4 = Format<'ipv4'>;
|
|
@@ -10,6 +11,12 @@ export type Byte = Format<'byte'>;
|
|
|
10
11
|
export type Password = Format<'password'>;
|
|
11
12
|
export type Regex = Format<'regex'>;
|
|
12
13
|
export type Hostname = Format<'hostname'>;
|
|
14
|
+
export type IdnHostname = Format<'idn-hostname'>;
|
|
15
|
+
export type URI = Format<'uri'>;
|
|
16
|
+
export type UriReference = Format<'uri-reference'>;
|
|
17
|
+
export type IRI = Format<'iri'>;
|
|
18
|
+
export type IriReference = Format<'iri-reference'>;
|
|
19
|
+
export type UriTemplate = Format<'uri-template'>;
|
|
13
20
|
export type Time = Format<'time'>;
|
|
14
21
|
export type Duration = Format<'duration'>;
|
|
15
22
|
export type ObjectId = Format<'objectId'>;
|
package/dist/runtime/tags.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
|
47
47
|
* Recursively resolves object properties, removing the optional `?` modifier
|
|
48
48
|
* from any properties that have a `tag.Default` applied.
|
|
49
49
|
*/
|
|
50
|
-
export type ResolveDefaults<T> = T extends Date | symbol | string | number | boolean | bigint | null | undefined | Function ? T : T extends Array<infer U> ? Array<ResolveDefaults<U>> : T extends object ? {
|
|
50
|
+
export type ResolveDefaults<T> = T extends Date | RegExp | Promise<any> | Map<any, any> | Set<any> | ArrayBuffer | SharedArrayBuffer | DataView | Buffer | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array | symbol | string | number | boolean | bigint | null | undefined | Function ? T : T extends Array<infer U> ? Array<ResolveDefaults<U>> : T extends object ? {
|
|
51
51
|
[K in keyof T as IsAny<T[K]> extends true ? never : '__default' extends keyof NonNullable<T[K]> ? K : never]-?: ResolveDefaults<Exclude<T[K], undefined>>;
|
|
52
52
|
} & {
|
|
53
53
|
[K in keyof T as IsAny<T[K]> extends true ? K : '__default' extends keyof NonNullable<T[K]> ? never : K]: ResolveDefaults<T[K]>;
|