💂
tguard Declarative type guarding system for TypeScript.
Installation
npm install tguard
or
yarn add tguard
Example Usage
import {
TArray,
TInteger,
TObject,
TString,
TStringUUID,
GuardedType,
} from "tguard";
// Let's define a User type as a Guard.
const TPost = TObject({
id: TStringUUID,
title: TString,
body: TString,
});
const TUser = TObject({
id: TStringUUID,
name: TString,
age: TInteger,
posts: TArray(TPost),
});
// Note: If you don't want to define these types twice
// (once as a TypeScript type, once as a guard)
// you can infer it's guarded types with the `GuardedType` utility type:
type User = GuardedType<typeof TUser>;
type Post = GuardedType<typeof TPost>;
// We can use guards to validate if a given value is a valid 'User' type or not:
if (TUser.isValid(unknownValue)) {
// TypeScript will know that `unknownValue` is 'User' in this block.
}
// Or try to cast a value to the User type:
try {
const user = TUser.cast({ posts: ["Who am I?", "I am a user."] });
// Type of `user` === {
// id: string,
// name: string,
// age: number,
// posts: Array<{id: string, title: string, body: string}>
// }
} catch (error) {
// error.message === 'Validation failed: Missing value at "id", expected type: string(UUID)'
}
❌
Motivation, Guarding Types Manually TypeScript does a static analysis to infer types, but won't provide any guarantees for runtime type safety. These checks should be done by the developer manually.
Here is an example for that with using type predicates:
tguard
:
interface User {
name: string;
posts: string[];
}
function isUser(fetchedUser: any): fetchedUser is User {
const user = fetchedUser as User;
return typeof user.name === "string" && isStringArray(user.posts);
}
function isStringArray(array: any): array is string[] {
if (!Array.isArray(array)) return false;
for (const item of array) {
if (typeof item !== "string") return false;
}
return true;
}
tguard
import { TObject, TString, TArray } from "tguard";
const TUser = TObject({
name: TString,
posts: TArray(TString),
});
Guards
By convention, every guard's name starts with an upper-case T
.
These are instances of the Guard abstract class with a name
field, isValid
method, and a cast
method.
Built-in type guards:
Guards
Primitive - TAny
- TAnyObject
- TBigInt
- TBoolean
- TFunction
- TInteger
- TIntegerAsString
- TNull
- TNumber
- TNumberAsString
- TString
- TStringEmail
- TStringISODate
- TStringJWT
- TStringMIMEType
- TStringPhoneNumber
- TStringSemVer
- TStringURL
- TStringUUID
- TUndefined
Guard
Functions, returning a - TAnd
- TArray
- TConstant
- TNot
- TObject
- TObjectOfShape
- TOr
- TStringBase64
- TStringMatch
- TStringWithLength
- TValidate
Defining Custom guards
You can define any custom Guard with TValidate
.
Defining a guard that validates if a number is bigger than 10:
const TNumberBiggerThan10 = TValidate<number>(
"number(bigger than 10)",
(value) => typeof value === "number" && value > 10
);
Exported utility tpyes
Tree shaking
All guards can be imported as a single module, which enables tree-shaking:
import TString from "tguard/lib/guards/TString";
import Guard, { GuardedType } from "tguard/lib/Guard";