Dec 1, 2023 (5 months ago)
3 minutes
35 views
An article about "type-guarder", a runtime type checker I built for TypeScript.
TypeScript is great, but it's not perfect. Given enough time, you'll eventually run into a situation where half of your code is type checking. It's not fun, it's annoying, and it's error prone.
I wanted a package that would allow me to write type checking code in a more declarative manner. Many such packages already exist, but I believe you never truly learn something unless you build it youself.
Thus, typr
was born.
typr
is a recursive runtime type checker. You define a type
and provide a value, and it will ensure that the type you provide, is the type you get.
There are three main functions that you can use to check types: conforms
, verify
, and force
.
conforms(value: any): boolean
This will check if the value passed in conforms to the type. It returns a boolean for whether the value conforms to the type.
verify(value: any): T | undefined
This function will check if the value conforms and if it does, it will return the value with the correct generic types in the type system by parsing out the raw type from the type provided. If it isn't the write type, it will return undefined.
force(value: any): T
This function is almost identical to verify(...)
however it will return T instead of T | undefined. If the value does not conform to the type, an error will be thrown.
This can be simple:
import { T } from "@elijahjcobb/typr";
T.string().verify("Hello World!"); // "Hello World!"
T.string().verify(123); // undefined
T.string().verify(false); // undefined
Or with more complex types:
import { T } from "@elijahjcobb/typr";
T.object({
name: T.string(),
isAdmin: T.boolean(),
}).verify({ name: "Elijah", isAdmin: true }); // {name: "Elijah", isAdmin: true}
T.object({
name: T.string(),
isAdmin: T.boolean(),
}).verify(3); // undefined
Or with even more complex types:
import { T } from "@elijahjcobb/typr";
const checker = T.object({
name: T.union(
T.string(),
T.object({
first: T.string(),
last: T.string(),
})
),
isAdmin: T.boolean(),
});
checker.verify({
name: "Elijah",
isAdmin: true,
}); // {name: "Elijah", isAdmin: true}
checker.verify({
name: { first: "Elijah", last: "Cobb" },
isAdmin: true,
}); // {name: {first: "Elijah", last: "Cobb"}, isAdmin: true}
checker.verify({
name: "Elijah",
isAdmin: 9,
}); // undefined
typr is set up to be extensible. A base class exists, and all other types extend that base class. The base class has a few helper functions, and requires that all children classes implement a conforms
function.
TType
ClassAll special types extend the TType
class. This class has the implementation for the few helper functions force
and verify
. It requires that any children classes implement the conforms
function.
export abstract class TType<T> {
protected constructor() {}
public abstract conforms(value: any): boolean;
public verify(value: any): T | undefined {
if (!this.conforms(value)) return undefined;
return value as unknown as T;
}
public force(value: any): T {
if (!this.conforms(value))
throw new Error("typr found a type that does not conform");
return value as unknown as T;
}
}