lifeboat
TypeScript icon, indicating that this package has built-in type declarations

1.3.3 • Public • Published

Lifeboat

Lifeboat is a simple library for generating typescript type guards. Its purpose is to let you handle data from outside typescript, like from a network request or a database query, in a type-safe way with the least overhead possible. It is explicitly NOT intended for complex use cases like form validation (use zod or joi for those).

Example

Here's how you might define a simple schema in lifeboat:

import ty, { ValidatedBy } from "lifeboat";

const userSchema = ty.object({
   name: ty.string(),
   age: ty.number(),
   friendList: ty.array(ty.string())
});

// You can get access to the corresponding type like this:
type User = ValidatedBy<typeof userSchema>;

With userSchema you can now use the two functions checkType and assertType:

import { checkType, assertType } from "lifeboat";

// Returns true when `data` is of type User, and false otherwise.
// Functions as a type guard.
const isUser = checkType(userSchema, data);

// Throws an error when `data` is not of type User.
// Functions as a type assertion.
assertType(userSchema, data);

When using checkType, you can get a message for why a value was rejected by using the reason property of the schema, like so:

if (checkType(userSchema, data)) {
   // data is a User, do something with it
} else {
   console.error(`Not a valid user: ${userSchema.reason}`);
}

Reference

Primitive Types

Lifeboat exposes simple validators for primitive types:

Validator Type
ty.undefined() undefined
ty.boolean() boolean
ty.number() number
ty.bigint() bigint
ty.string() string
ty.symbol() symbol

Objects

By structure

You can build an object validator by using ty.object({ ... }). The function takes an object as a paramter that specifies the object's structure, like this:

const exampleSchema = ty.object({
   id: ty.number(),
   name: ty.string()
});

By default, all properties are required and non-nullable. If you want optional or nullable values, specify them explicitly using ty.optional, ty.nullable, or ty.allowNullish:

const exampleSchema = ty.object({
   optionalVal: ty.optional(ty.string()), // string | undefined
   nullableVal: ty.nullable(ty.string()), // string | null
   valOrNullish: ty.allowNullish(ty.string()) // string | null | undefined
});

By class

You can also require that a value be an instance of a particular class, like ArrayBuffer, by using ty.instanceof.

const arrayBufferSchema = ty.instanceof(ArrayBuffer);

Like with the instanceof keyword, make sure that you know that the values you expect are actual instances of the class, not just POJOs with the class' structure; especially when your data is deserialized from a source like a network packet.

Arrays

You can validate arrays by using ty.array(...). This function takes another validator that will be used to validate the array's items as a parameter, like this:

const stringArraySchema = ty.array(ty.string()); // string[]

Unknown types

Sometimes it can be useful to allow any type, for example when trying to validate that something is an array, without caring about the specific types. This can be done using ty.unknown():

const unknownArray = ty.array(ty.unknown()); // unknown[]

Enums

There are two kinds of enums that are supported; string unions (like "apple" | "tomato" | "pear"), and const enums (i.e. number unions). You can validate them by using ty.enum.

const strEnum = ty.enum("apple", "tomato", "pear"); // "apple" | "tomato" | "pear"

// Yes, tomatoes are fruit, fight me
const enum Fruit {
   APPLE,
   TOMATO,
   PEAR
}

const constEnum = ty.enum(Fruit.APPLE, Fruit.TOMATO, Fruit.PEAR); // Fruit.APPLE | Fruit.TOMATO | Fruit.PEAR

General Unions and Intersections

Although not recommended, as it makes for less readable error messages, general type unions and intersections are also supported:

// Unions
const stringOrNumber = ty.union(ty.string(), ty.number()); // string | number

// Intersections
const obj1Schema = ty.object(...); // Obj1
const obj2Schema = ty.object(...); // Obj2
const obj1And2 = ty.intersection(obj1Schema, obj2Schema); // Obj1 & Obj2

Note that both ty.union and ty.intersection currently only take two arguments, as opposed to ty.stringUnion. This is because in this general case, type inference doesn't seem to work as well.

Exact Values

Sometimes it is useful to only allow precise values, particularly when working with unions. This can be done using ty.equals(...):

const emptyStringSchema = ty.equals("" as const); // ""

const aCoolNumberSchema = ty.equals(69 as const); // 69

Note that as const is required here to make sure the type inference works correctly; without it, typescript would widen the types of values like "" and 69 to string and number respectively.

Package Sidebar

Install

npm i lifeboat

Weekly Downloads

0

Version

1.3.3

License

MIT

Unpacked Size

59.5 kB

Total Files

7

Last publish

Collaborators

  • nicholas-roether