cast-as-trophy

Build the type guards you want for the supported types you can

Usage no npm install needed!

<script type="module">
  import castAsTrophy from 'https://cdn.skypack.dev/cast-as-trophy';
</script>

README

Cast as Trophy"> Cast as Trophy

Gitlab pipeline status Gitlab code coverage Libraries.io dependency status for latest release NPM npm type definitions npm

Build the type guards you want for the supported types you can! And enjoy your compile time errors when you forget to keeping something up to date! ;)

The rules of the supported types:

No. Rule Example
1. Objects with string, number or boolean properties { s: string }
2. Supported objects can be embedded { i: { s: string } }
3. Arrays can be used for the supported types and objects { ia: Array<{ s: string }> }
4. The supported types and objects can be optional with null and / or undefined { iau: Array<{ s: string }> \| undefined }

The library is not optimized for performance. And it is built for TypeScript (with strictNullChecks option turned on).

How to start?

You can install the library with NPM:

npm i --save cast-as-trophy

And create a caster for a not too complex object type:

import { createCaster } from "cast-as-trophy";

interface Example {
    s: string; // You need to call `addString("s")`
    n: number; // You need to call `addNumber("n")`
    b: boolean; // You need to call `addBoolean("b")`
}

const exampleCaster = createCaster<Example>()
    .addString("s") // Needed because of `s: string`
    .addNumber("n") // Needed because of `n: number`
    .addBoolean("b") // Needed because of `b: boolean`
    .build();

And check some values:

const test: unknown = {
    s: "test",
    n: 42,
    b: true,
};

if (exampleCaster.isType(test)) {
    // The `test` is type guarded! It has the proper type here.
    console.log(`Value of s: ${test.s}`);
}

How to use a caster?

1. isType<TYPE>(value: unknown) => value is TYPE

A simple old fashion type guard.

Input Value to check
Result Whether the value has the proper type (boolean, but the type guard way)
Exception No

Example:

import { createCaster } from "cast-as-trophy";

interface Example {
    s: string;
}

const caster = createCaster<Example>().addString("s").build();

const myLittleObject: unknown = { s: "test" };

if (caster.isType(myLittleObject)) {
    // The type of the `myLittleObject` is now known.
    console.log(myLittleObject.s);
}

2. castTo<TYPE>(value: unknown) => TYPE

Returns the same object but with the narrowed type. (Throws error when the type of the input is not okay.)

Input Value to convert
Result Same value but with the narrowed type
Exception When the type of the value is wrong

Example:

import { createCaster } from "cast-as-trophy";

interface Example {
    s: string;
}

const caster = createCaster<Example>().addString("s").build();

try {
    const value: Example = caster.castTo({ s: "test", n: 42 });
    // You can use `s` in the result, but there is no `n`
    console.log(`Value of s: ${value.s}`);
} catch {
    // Things can go wrong.
}

Warning:

  • The returned object can contain additional values

3. copyTo<TYPE>(value: unknown) => TYPE

Returns a copy of checked part of the input with the narrowed type. (Throws error when the type of the input is not okay.)

Input Value to convert
Result Copy of input
Exception When the type of the value is wrong

Example:

import { createCaster } from "cast-as-trophy";

interface Example {
    s: string;
}

const caster = createCaster<Example>().addString("s").build();

try {
    const value: Example = caster.copyTo({ s: "test", n: 42 });
    // You can use `s` in the result, but there is no `n`
    console.log(`Value of s: ${value.s}`);
} catch {
    // Things can go wrong.
}

Warning:

  • The returned copy only contains the checked props (and nothing else)
  • Creating a copy costs extra memory usage and CPU time (can cause possible performance issues)

4. asyncCastTo<TYPE>(value: unknown) => Promise<TYPE>

The asynchronous version of the castTo(...) method. It does the same but with promises. Can be useful if you work with large object and don't want to block your app.

Input Value to convert
Result Copy of the value with the checked type (a promise)
Exception When the type of the value is wrong (in a promise like way)

Example:

import { createCaster } from "cast-as-trophy";

interface Example {
    s: string;
}

const caster = createCaster<Example>().addString("s").build();

function doAsyncStuffs(): Promise<void> {
    try {
        const value = await caster.asyncCastTo({ s: "test" });
        console.log(`Value of s: ${value.s}`);
    } catch {
        // Don't forget: error can be thrown...
    }
}

Warning:

  • This method returns the same value but with the narrowed type, just like the original castTo(...)
  • There can be a lot of async calls in the background which can cause performance issues too

5. asyncCopyTo<TYPE>(value: unknown) => Promise<TYPE>

The asynchronous version of the copyTo(...) method. It does the same but with promises. Can be useful if you work with large object and don't want to block your app.

Input Value to convert
Result Copy of the checked part of the input value with the checked type (a promise)
Exception When the type of the value is wrong (in a promise like way)

Example:

import { createCaster } from "cast-as-trophy";

interface Example {
    s: string;
}

const caster = createCaster<Example>().addString("s").build();

function doAsyncStuffs(): Promise<void> {
    try {
        const value = await caster.asyncCopyTo({ s: "test" });
        console.log(`Value of s: ${value.s}`);
    } catch {
        // Don't forget: error can be thrown...
    }
}

Warning:

  • This method creates a copy of the checked part of the input, just like the original copyTo(...)
  • There can be a lot of async calls in the background which can cause performance issues too

Missing features on the casters

Async type guards and async assertions are not supported in TypeScript. (At least not yet. As far as I know.) So implementing a features like asyncIsType is unfortunatelly not an option.

How to build a caster?

You can do this with 3 easy steps:

  1. Call the createCaster<...>(...) function with the proper generic type
  2. Call the addXYZ(...) methods on the returned value.
  3. Call the build() at the end of the process.

Example:

import { createCaster } from "cast-as-trophy";

interface Example {
    s: string;
    n: number;
    b: boolean;
}

const exampleCaster = createCaster<Example>().addString("s").addNumber("n").addBoolean("b").build();

Notes:

  • The best way to build a caster is this kind of call chain you can see in the example
  • The order of the addXYZ(...) methods is not important. Just be sure you called all of them!
  • You can only call the build() method after you did all the necessary calls to add your props to the caster builder. Before that the build() will simply not be available. (If there is a property s: string and you haven't added yet then you need to add an addString("s") before you can call the build().)
  • You will get compile errors related to never for the keys when you try to use the wrong addXYZ(...) method for a property.

Basic types

Methods to add a basic types (the input parameter is the key of the given property):

Method Supported type
addString(KEY) string
addNumber(KEY) number
addBoolean(KEY) boolean

Example:

import { createCaster } from "cast-as-trophy";

interface Example {
    s: string;
    n: number;
    b: boolean;
}

const result = createCaster<Example>().addString("s").addNumber("n").addBoolean("b").build();

Embedded

You can use the addCustom(..., ...) method to add a previously created caster for a given property. This means that you can embed the objects and the casters) into each other. The second parameter of the function is the caster you already have. (The first one is the key, as usual.)

Method Supported type
addCustom(KEY, CASTER) The embedded object type with a builded caster

Example:

import { createCaster } from "cast-as-trophy";

interface Inner {
    s: string;
}

interface Outer {
    i: Inner;
}

const innerCaster = createCaster<Inner>().addString("s").build();

const outerCaster = createCaster<Outer>().addCustom("i", innerCaster).build();

Arrays

You can use arrays of string, number, boolean and embedded objects of these. (Or object of object of these. And so on.)

Method Supported type
addStringArray(KEY) Array<string>
addNumberArray(KEY) Array<number>
addBooleanArray(KEY) Array<boolean>
addCustomArray(KEY, CASTER) Array of the embedded object type with a builded caster

Example:

import { createCaster } from "cast-as-trophy";

interface Inner {
    sa: string[];
    na: number[];
    ba: boolean[];
}

interface Outer {
    ia: Inner[];
}

const innerCaster = createCaster<Inner>().addStringArray("sa").addNumberArray("na").addBooleanArray("ba").build();

const outerCaster = createCaster<Outer>().addCustomArray("ia", innerCaster).build();

Optional (null and undefined)

At last but not least you can make the previous types optional with null and / or undefined

Method Supported type
addStringOrUndefined(KEY) string \| undefined
addNumberOrUndefined(KEY) number \| undefined
addBooleanOrUndefined(KEY) boolean \| undefined
addCustomOrUndefined(KEY) EmbeddedType \| undefined
addStringArrayOrUndefined(KEY) Array<string> \| undefined
addNumberArrayOrUndefined(KEY) Array<number> \| undefined
addBooleanArrayOrUndefined(KEY) Array<boolean> \| undefined
addCustomArrayOrUndefined(KEY, CASTER) Array<EmbeddedType> \| undefined
addStringOrNull(KEY) string \| null
addNumberOrNull(KEY) number \| null
addBooleanOrNull(KEY) boolean \| null
addCustomOrNull(KEY) EmbeddedType \| null
addStringArrayOrNull(KEY) Array<string> \| null
addNumberArrayOrNull(KEY) Array<number> \| null
addBooleanArrayOrNull(KEY) Array<boolean> \| null
addCustomArrayOrNull(KEY, CASTER) Array<EmbeddedType> \| null
addStringOrNullOrUndefined(KEY) string \| null \| undefined
addNumberOrNullOrUndefined(KEY) number \| null \| undefined
addBooleanOrNullOrUndefined(KEY) boolean \| null \| undefined
addCustomOrNullOrUndefined(KEY) EmbeddedType \| null \| undefined
addStringArrayOrNullOrUndefined(KEY) Array<string> \| null \| undefined
addNumberArrayOrNullOrUndefined(KEY) Array<number> \| null \| undefined
addBooleanArrayOrNullOrUndefined(KEY) Array<boolean> \| null \| undefined
addCustomArrayOrNullOrUndefined(KEY, CASTER) Array<EmbeddedType> \| null \| undefined

Example:

import { createCaster } from "cast-as-trophy";

interface Example {
    su: string | undefined;
    nn: number | null;
    bnu: boolean | null | undefined;
}

const result = createCaster<Example>()
    .addStringOrUndefined("su")
    .addNumberOrNull("nn")
    .addBooleanOrNullOrUndefined("bnu")
    .build();

Not supported features for the caster builders

Union types are not supported

Except null and undefined, you can make other types optional with them.

You can not use something like this: string | number | boolean.

Array of arrays is not supported

Only the first level of arrays is available.

Array of unions is not supported

You can not use unions of types in the arrays. So Array<string | number> is not supported. (The Array<string | undefined> is not supported too.)

Tuples are not supported

You can't use tuples like [string] or [number, number]. Only arrays are supported, like this: string[].

Pure undefined and null are not supported

You can not use pure undefined or null for a property. And you can not use undefined[] and null[] array types either.

Index types are not supported

You can not use index types.

Using other exotic types like functions, symbols, etc. are not supported

You can not build caster for object with exotic types in it. (Including unknown and never.)

Using custom type guards are not supported

You can not embed your custom type guards.

Alternatives

If this library is not flexible enough for you then a good alternative can be the https://www.npmjs.com/package/ts-looks-like package.