@sealcode/ts-predicates
TypeScript icon, indicating that this package has built-in type declarations

0.6.2 • Public • Published

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"
	}
}

Readme

Keywords

none

Package Sidebar

Install

npm i @sealcode/ts-predicates

Weekly Downloads

17

Version

0.6.2

License

ISC

Unpacked Size

113 kB

Total Files

76

Last publish

Collaborators

  • kuba-orlik