This is a simple TypeScript type checking utility library.
Requires Typescript version >= 3.2.
Returns null or a MismatchInfo object that stores information
about type incompatability with the given
TypeDescription, e.g. why and where suspect's invalid property is.
This is a powerful tool to generate useful error messages while validating value shape type.
Note: this function doesn't allow suspect to have properties not listed in typeDescr which differentiates it from duckMismatch() (see bellow).
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);
duckMismatch(suspect, typeDescr)Works the same way as mismatch(suspect, typeDescr) but allows suspect object with excess properties to pass the match.
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<typeof ExpectedJsonTD>;
/*
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
Type description is a simple JavaScript object with values of TypeDescription type or basic typename string ('string',
'number', 'function'...) or Set<TypeDescription> or TypeDescription[] or RegExp or your
custom TypePredicate 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.
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<typeof HumanTD>;
// 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<typeof HumanTD> exactly
maybeHuman.name;
maybeHuman.id;
}
}
Here is an actual algorithm how conforms() function interprets TypeDescription.
typeof suspect === typeDescr.RegExp, then returns
typeof suspect === 'string' && typeDescr.test(suspect).Set<TypeDescription>, returns true if suspect conforms to at
least one of the given TDs in Set.Array<TypeDescription> 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].Array<TypeDescription> 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.Array, returns true if suspect is Array of any type.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.TypePredicate (i.e. (suspect: unknown) => boolean), then returns typeDescr(suspect).There are factory functions that return 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
isNumberWithinRange(min, max) Returns a predicate that returns true if its argument is a number within the range [min, max] or [max, min] if min > max.
import * as Vts from 'vee-type-safe';
Vts.conforms(
{
num: 32
},
{
num: Vts.isNumberWithinRange(0, 5)
}); // false
isIntegerWithinRange(min, max) The same as isNumberWithinRange(min, max), but its returned predicate returns false if forwarded argument is not an integer.
optional(typeDescr: TypeDescription)Retuns Set(['undefined', typeDescr]))
import * as Vts from 'vee-type-safe';
Vts.conforms(
{
prop: 'str'
},{
prop: Vts.optional('number')
})
// return false because the property is not undefined,
// but doesn't conform to 'number' type
Vts.conforms(
{
prop: -23
},{
prop: Vts.optional(Vts.isNegativeInteger)
});
// returns true because the property is not undefined
// and conforms to isNegativeInteger restriction
Vts.conforms(
{
},{
prop: Vts.optional(Vts.isNegativeInteger)
});
// returns true because property 'prop' may be absent
All these functions take 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)import * as Vts from 'vee-type-safe';
Vts.conforms(
{
id: 2,
volume: 22.5
},
{
id: Vts.isPositiveInteger,
money: Vts.isZeroOrPositiveNumber
}); // true
isOneOf<T>(possibleValues: T[]) Returns a predicate that accepts a suspect of any type and matches it to
one of the provided possible values by
possibleValues.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.
import * as Vts from 'vee-type-safe';
Vts.conforms(2, Vts.isOneOf([0, 1, 2, 3])); // true
Vts.conforms(2, new Set([0, 1, 2, 3])); // compile error
// Set<numbers> is not a Set<TypeDescritpion>
interface BasicObject<T>A shorthand for { [key: string]: T; } type.
type PrimitiveTypeA union of all primitive types (null is treated as a primitive type).
type BasicTypeNameA union type of string literals which are in typeof operator domain definition ('string' | 'boolean' | 'object' ...).
This is a library for ExpressJS routing middleware functions.
ensureTypeMatch(getRequestProperty, typeDescr, makeError?)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 tomakeError?: (failInfo: MismatchInfo) => unknown - it is an optional function which makes a custom error to forward to next(), by default this function retuns BadTypeStatusErrorBadTypeStatusError is an instance of TypeMismatchError that has a status: number property, which is http BAD_REQUEST by default.
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<MessagesPostRequest>, res, next) => {
/* 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.bodyReqParams(req) => req.paramsReqQuery(req) => req.queryReqCookies(req) => req.cookiesReqHeaders(req) => req.headers 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
/* ... */
}
);
Generated using TypeDoc