@sealcode/ts-predicates
v0.6.2
Published
Useful type assertions for TS that happen both in typechecking and in runtime
Downloads
118
Readme
ts-predicates
A set of functions that ensure type safety both during typechecking AND during runtime.
Examples
lang=ts
const a = {} as Record<string, unknown>;
const b = { number: "2" } as Record<string, unknown>;
const c = { number: 2 } as Record<string, unknown>;
if (hasFieldOfType("number", predicates.number, a)) {
a.number + 1;
console.log("A has a number prop");
}else{
a.number +1; // typescript error
}
if (hasFieldOfType("number", predicates.number, b)) {
b.number + 1;
console.log("B has a number prop");
}
if (hasFieldOfType("number", predicates.number, c)) {
c.number + 1;
console.log("C has a number prop");
}
console.log("utils finished");
You can test multiple fields at once:
lang=ts
const a = {};
if (
!hasShape(
<const>{
string: predicates.string,
undefined: predicates.undefined,
},
a
)
) {
throw new Error("wrong shape!");
}
You can infer object type from its shape:
lang=ts
const shape = {
text: predicates.string,
number: predicates.number,
} as const;
type the_shape = ShapeToType<typeof shape>
Dealing with nested/recursive types
In order to use recursive shapes/types, a little bit of syntax overhead is necessary.
Let's assume an example where we have a list of json objects. Some of them are images, and some of them are containers. Containers can contain other containers and images. For runtime type safety this would be enough:
lang=ts
const ImagePrimitiveShape = {
type: predicates.const(<const>"image"),
src: predicates.string,
};
type ImagePrimitiveType = ShapeToType<typeof ImagePrimitiveShape>;
type BlockPrimitiveType = {
type: "block";
content: Primitive[];
};
const BlockPrimitiveShape = <const>{
type: predicates.const(<const>"block"),
content: predicates.lazy(() =>
predicates.array<Primitive>(
predicates.or(
predicates.shape(ImagePrimitiveShape),
predicates.shape(BlockPrimitiveShape)
)
)
),
};
type Primitive = ImagePrimitiveType | BlockPrimitiveType;
But, this would throw a compile error:
typescript [7022]: 'BlockPrimitiveShape' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
In order to fix the compile error, we need to make the BlockPrimitiveShape
const a little bit more verbose by manually specifying the type of the shape:
lang=ts
const BlockPrimitiveShape: {
readonly type: ConstPredicate<"block">;
readonly content: LazyPredicate<
(BlockPrimitiveType | ImagePrimitiveType)[]
>;
} = <const>{
type: predicates.const(<const>"block"),
content: predicates.lazy(() =>
predicates.array<Primitive>(
predicates.or(
predicates.shape(ImagePrimitiveShape),
predicates.shape(BlockPrimitiveShape)
)
)
),
};
This results in a shapes+types combo than can check recursive types both at compile- and runtime:
lang=ts
if (hasShape(BlockPrimitiveShape, input)) {
input.type; // "block"
const element = input.content[0];
element.type; // "image" | "block"
if (element.type === "block") {
element.content; // Primitive[]
const nested_element = element.content[0];
nested_element.type; // "block" | "image"
}
}