Simple TypeScript type checking utility library.
npm install vee-type-safe
This is a simple TypeScript type checking utility library.
Requires Typescript version >= 3.2.
null or a MismatchInfo object that stores informationTypeDescription, e.g. why and where suspect's invalid property is.suspect to have properties not listed in typeDescr which differentiates it from duckMismatch() (see bellow).~~~typescript
import * as Vts from 'vee-type-safe';
import { Model } from '@models/model';
const untrustedJson: unknown = / ... /;
const ExpectedJsonTD: Vts.TypeDescription = / this is actually a generic type (advanced topic) /;
const dbDocument: Model = / Some object /;
const mismatchInfo = Vts.mismatch(untrustedJson, ExpectedJsonTD);
if (mismatchInfo != null) {
console.log(
mismatchInfo.path,
mismatchInfo.actualValue,
mismatchInfo.expectedTd
);
// logs human readable path to invalid property
console.log(mismatchInfo.pathString());
// mismatchInfo.toErrorString() generates human readable error message
throw new Vts.TypeMismatchError(mismatchInfo);
}
// now you may safely assign untrustedJson to dbDocument:
Object.assign(dbDocument, untrustedJson);
~~~
mismatch(suspect, typeDescr) but allows suspect object with excess properties to pass the match.~~~typescript
import * as Vts from 'vee-type-safe';
Vts.duckMismatch(
{ name: 'Ihor', somePropertyIDontCareAbout: 42 },
{ name: 'string' }
); // returns null as suspect is allowed to have excess properties
const untrustedJson = {
client: 'John Doe',
walletNumber: null,
};
const ExpectedJsonTD = Vts.td({ // this noop call is needed to preserve unit types
client: 'string',
walletNumber: /\d{16}/ // implies a string of the given format
});
// Here we map the given type description shape to the type that it describes statically
type ExpectedJson = Vts.TypeDescriptionTarget
/*
ExpectedJson === {
client: string;
walletNumber: string;
}
*/
const mismatchInfo = Vts.duckMismatch(untrustedJson, ExpectedJsonTD);
if (mismatchInfo != null) {
throw new Vts.TypeMismatchError(mismatchInfo);
}
// ^~~~ Vts.ensureDuckMatch() does the same
const trustedJson = untrustedJson as ExpectedJson;
// process client
~~~
TypeDescription type or basic typename string ('string', 'number', 'function'...) or Set or TypeDescription[] or RegExp or yourTypePredicate function. TypeDescription is actually a conditional (dependent on type argument) union type of all of these.Here is an example of how you may describe your type.
~~~typescript
import * as Vts from 'vee-type-safe';
Vts.conforms(
{
prop: 'lala',
tel: '8800-555-35-35'
prop2: true,
obj: {
obj: [23, false]
},
someIDontCareProperty: null // excess properties are ok for confroms()
},
{
prop: 'string',
tel: /\d{4}-\d{3}-\d{2}-\d{2}/, // claims a string of given format
prop2: 'boolean',
obj: {
obj: ['number', 'boolean'] // claims a fixed length tuple
}
}); // true
Vts.conforms(
{
arr: ['array', null, 'of any type', 8888 ],
strArr: ['Pinkie', 'Promise', 'some', 'strings'],
oneOf: 2,
custom: 43
},
{
arr: [], // claims an array of any type
strArr: ['string'], // claims an array of any length
oneOf: new Set(['boolean', 'number']),// claims to be one of these types
custom: isOddNumber // custom type predicate function
}); // true
function isOddNumber(suspect: unknown): suspect is number {
return typeof suspect === 'number' && suspect % 2;
}
const HumanTD = Vts.td({ // noop function that preserves unit types
name: 'string',
id: 'number'
});
// generate static TypeScript type:
type Human = Vts.TypeDescriptionTarget
// type Human === {
// name: string;
// id: number;
// }
function tryUseHuman(maybeHuman: unknown) {
if (conforms(maybeHuman, HumanTD)) {
// maybeHuman is of type that is assignable to Human here
// it is inferred to be Vts.TypeDescriptionTarget
maybeHuman.name;
maybeHuman.id;
}
}
~~~
Here is an actual algorithm how conforms() function interprets TypeDescription.
* If it is a basic JavaScript typename string (should satisfy typeof operator
domain definition), then function returns typeof suspect === typeDescr.
* If it is a RegExp, then returnstypeof suspect === 'string' && typeDescr.test(suspect).
* If it is a Set, returns true if suspect conforms to at
least one of the given TDs in Set.
* If it is an Array and it consists of one item,
returns true if suspect is Array and each of its items conforms to the given
TD at typeDescr[0].
* If it is an Array and it consists of more than one item,
returns true if suspect is Array and suspect.length === typeDescr.length
and each corresponding suspect[i] conforms to typeDescr[i] type description.
* If it is an empty Array, returns true if suspect is Array of any type.
* If it is an object, returns true if suspect is also an object and
each typeDescr[key] is a TD for suspect[key]. Excess properties in suspect
do not matter for conforms() function, but matter for exactlyConforms() and mismatch() functions.
* If it is a TypePredicate (i.e. (suspect: unknown) => boolean), then returns typeDescr(suspect).
TypeDescriptions (those are often TypePredicates) or already defined TypePredicates, that you should use as type descriptions when calling mismatch/duckMismatch/conforms/exactlyConforms(suspect, typeDescr).TypePredicate is a function of type:(suspect: unknown) => boolean
If you specify a generic argument TTarget it becomes a true TypeScript type predicate, so that you will be able to get described type from it when using Vts.TypeDescriptionTarget:
(suspect: unknown) => suspect is TTarget
min, max] or \[max, min] if min > max.isNumberWithinRange(min, max), but its returned predicate returns false if forwarded argument is not an integer.Set(['undefined', typeDescr]))},{
prop: Vts.optional(Vts.isNegativeInteger)
});
// returns true because property 'prop' may be absent
~~~
unknown type argument and return suspect is number, which is useful as a type guard or when using as a type description.* isInteger(suspect)
* isPositiveInteger(suspect)
* isNegativeInteger(suspect)
* isPositiveNumber(suspect)
* isNegativeNumber(suspect)
* isZeroOrPositiveInteger(suspect)
* isZeroOrNegativeInteger(suspect)
* isZeroOrPositiveNumber(suspect)
* isZeroOrNegativeNumber(suspect)
* ...
~~~typescript
import * as Vts from 'vee-type-safe';
Vts.conforms(
{
id: 2,
volume: 22.5
},
{
id: Vts.isPositiveInteger,
money: Vts.isZeroOrPositiveNumber
}); // true
~~~
any type and matches it topossibleValues.includes(suspect). Don't confuse it with new Set(possibleValues) when forwarding as a type description to conforms() function, because possibleValues are not TDs, but values to match with.Vts.conforms(2, Vts.isOneOf([0, 1, 2, 3])); // true
Vts.conforms(2, new Set([0, 1, 2, 3])); // compile error
// Set
~~~
{ [key: string]: T; } type.null is treated as a primitive type).typeof operator domain definition ('string' | 'boolean' | 'object' ...).Returns express.Handler that exactly matches the value returned by getRequestProperty(req) to typeDescr and if it fails, calls next(makeError(failedTypeInfo)).
Thus you can be sure that the property of express.Request object was type checked before using it in your middleware.
Does type matching via core library mismatch() function.
* getRequestProperty: (req: express.Request) => unknown - this function must return a suspect to match to typeDescr, based on the given req argument.
* typeDescr - type description that the value returned by getRequestProperty(req) will be checked to match to
* makeError?: (failInfo: MismatchInfo) => unknown - it is an optional function which makes a custom error to forward to next(), by default this function retuns BadTypeStatusError
BadTypeStatusError is an instance of TypeMismatchError that has a status: number property, which is http BAD_REQUEST by default.
~~~typescript
import * as express from 'express';
import * as VtsEx from 'vee-type-safe/express'
import * as Vts from 'vee-type-safe';
const router = express.Router();
interface MessagesPostRequest {
filters: string[];
limit: number;
}
router.post('api/v1/messages',
VtsEx.matchType(
VtsEx.ReqBody, // or req => req.body (your custom obtaining logic here)
{
filters: ['string'],
limit: Vts.isPositiveInteger
},
mmInfo => new MyCustomError(mmInfo.path, mmInfo.actualValue)
),
// replaces standard express.Request.body type with MessagesPostRequest
(req: VtsEx.ReqBody
/ your middleware, where you can trust to req.body /
// req.body has MessagesPostRequest type here
const filters = req.body.filters.join();
// ...
}
);
~~~
There is a list of handy functions to specify as getRequestProperty argument:
* ReqBody(req) => req.body
* ReqParams(req) => req.params
* ReqQuery(req) => req.query
* ReqCookies(req) => req.cookies
* ReqHeaders(req) => req.headers
~~~typescript
import * as VtsEx from 'vee-type-safe/express';
/ ... /
router.get('api/v1/users/',
VtsEx.matchType(VtsEx.ReqQuery, { title: 'string' }),
(req: VtsEx.ReqQuery<{title: string}>, res, next) => {
const title: string = req.query.title; // now you are sure
/ ... /
}
);
~~~