Declarative type guarding system for TypeScript.
npm install tguard
or
yarn add tguard
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)'
}
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:
❌ Without 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;
}
✅ With tguard
import { TObject, TString, TArray } from "tguard";
const TUser = TObject({
name: TString,
posts: TArray(TString),
});
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.
- TAny
- TAnyObject
- TBigInt
- TBoolean
- TFunction
- TInteger
- TIntegerAsString
- TNull
- TNumber
- TNumberAsString
- TString
- TStringEmail
- TStringISODate
- TStringJWT
- TStringMIMEType
- TStringPhoneNumber
- TStringSemVer
- TStringURL
- TStringUUID
- TUndefined
- TAnd
- TArray
- TConstant
- TNot
- TObject
- TObjectOfShape
- TOr
- TStringBase64
- TStringMatch
- TStringWithLength
- TValidate
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
);
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";