vee-type-safe v4.2.0
vee-type-safe
This is a simple TypeScript type checking utility library.
Requires Typescript version >= 3.2.
View detailed API documentation generated by TypeDoc
Quick API glance
mismatch(suspect: unknown, typeDescr: TypeDescription)
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 clientWhat is TypeDescription?
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.
- 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<TypeDescription>, returnstrueif suspect conforms to at least one of the given TDs inSet. - If it is an
Array<TypeDescription>and it consists of one item, returnstrueifsuspectisArrayand each of its items conforms to the given TD attypeDescr[0]. - If it is an
Array<TypeDescription>and it consists of more than one item, returnstrueif suspect isArrayandsuspect.length === typeDescr.lengthand each correspondingsuspect[i]conforms totypeDescr[i]type description. - If it is an empty
Array, returnstrueifsuspectisArrayof any type. - If it is an object, returns
trueifsuspectis also an object and eachtypeDescr[key]is a TD forsuspect[key]. Excess properties insuspectdo not matter forconforms()function, but matter forexactlyConforms()andmismatch()functions. - If it is a
TypePredicate(i.e.(suspect: unknown) => boolean), then returnstypeDescr(suspect).
Predefined TypeDescriptions
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)
}); // falseisIntegerWithinRange(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 absentSelf explanatory functions
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
}); // trueisOneOf<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>Convenient type definitions
interface BasicObject<T>
A shorthand for { [key: string]: T; } type.
type PrimitiveType
A union of all primitive types (null is treated as a primitive type).
type BasicTypeName
A union type of string literals which are in typeof operator domain definition ('string' | 'boolean' | 'object' ...).
vee-type-safe/express (BETA)
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 totypeDescr, based on the givenreqargument.typeDescr- type description that the value returned bygetRequestProperty(req)will be checked to match tomakeError?: (failInfo: MismatchInfo) => unknown- it is an optional function which makes a custom error to forward tonext(), by default this function retunsBadTypeStatusError
BadTypeStatusError 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
/* ... */
}
);7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago