npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

to-typed

v0.5.2

Published

Type-guards, casts and converts unknowns into typed values

Downloads

384

Readme

to-typed

Type-guards, casts and converts unknown values into typed values.

Installation

npm install to-typed

Introduction

This package provides 3 interrelated classes: Cast, Guard and Convert.

Cast

The base class of Guard and Convert. It is a wrap around a cast function that takes an unknown value and returns a Maybe:

cast: (value: unknown) => Maybe<T>

If the cast succeeds, the function returns just the casted value, otherwise it returns nothing.

Cast and its derived classes are designed to make operations chainable in a functional/declarative way:

console.log(Convert
    .toArrayWhere(Cast
        .asString
        .if(str => str.length)
    )
    .map(arr => arr.join(' '))
    .convert([1, null, 'hello', '', 'world', {}, true])
) // "1 hello world true"

Cast factory methods start with the as prefix, such as asNumber or asUnknown.

Guard

A wrap around a guard function that takes an unknown value and returns a boolean indicating whether the input value has the expected type:

guard: (input: unknown) => input is T

It implements the cast method by returning just the input value if it has the expected type, or nothing otherwise:

value => guard(value) ? Maybe.just(value) : Maybe.nothing()

Guard factory methods start with the is prefix, such as isEnum or isBoolean.

Convert

A wrap around a convert function that takes an unknown value and returns a typed value:

convert: (value: unknown) => T

It implements the cast method by always returning just the converted value:

value => Maybe.just(convert(value))

Convert factory methods start with the to prefix, such as toFinite or toString.

Remarks

Note that Guard and Convert are complementary subclasses of Cast in the sense that Guard cannot provide an alternative to the input value, while Convert must provide one. The base class Cast lies in the middle by including both possibilities.

A Guard can produce a Cast by calling some value mapping method:

const guard = Guard.is({ value: Guard.isUnknown }); // Guard<{ value: unknown }>
const cast = guard.map(obj => obj.value).asInteger; // Cast<number>

And a Cast can produce a Convert by providing a default value:

const convert = cast.if(x => x > 0).else(1); // Convert<number>
console.log(convert.convert({ value: '33.3'})); // 33

Quick Start

 

import { Guard, Cast, Convert } from "to-typed"

// ---------------- Type guarding ----------------

// Create a `Guard` based on an object, which may include other guards
const guard = Guard.is({
    integer: Guard.isInteger,
    number: 0,
    boolean: false,
    tuple: [20, 'default', false] as const,
    arrayOfNumbers: Guard.isArrayOf(Guard.isFinite),
    even: Guard.isInteger.if(n => n % 2 === 0),
    object: {
        union: Guard.some(
            Guard.isConst(null),
            Guard.isString,
            Guard.isNumber
        ),
        intersection: Guard.every(
            Guard.is({ int: 0 }),
            Guard.is({ str: "" })
        )
    }
})

const valid: unknown = {
    integer: 123,
    number: 3.14159,
    boolean: true,
    tuple: [10, 'hello', true],
    arrayOfNumbers: [-1, 1, 2.5, Number.MAX_VALUE],
    even: 16,
    object: {
        union: null,
        intersection: { int: 100, str: 'good bye' }
    }
}

if (guard.guard(valid)) {
    // `valid` is now fully typed
    console.log(valid.object.intersection.int); // 100
}

// Alternatively, the base class' `cast` method can be used. Since this is
// just a `Guard`, no casting or cloning will actually occur.
const maybe = guard.cast(valid);

if (maybe.hasValue) {
    // In this context, `maybe.value` is available and fully typed, and it
    // points to the same instance as `valid`.
    console.log(maybe.value.object.intersection.int); // 100
}

// Or equivalently...
maybe.read(value => console.log(value.object.intersection.int)); // 100

// ---------------- Type casting / converting ----------------

// Create a `Convert` based on a sample value, from which the default
// values will also be taken if any cast fails.
const converter = Convert.to({
    integer: Convert.toInteger(1),
    number: 0,
    string: '',
    boolean: false,
    trueIfTruthyInput: Convert.toTruthy(),
    tuple: [0, 'default', false] as const,
    arrayOfInts: Convert.toArrayOf(Convert.to(0)),
    percentage: Convert.toFinite(.5).map(x => Math.round(x * 100) + '%'),
    enum: Convert.toEnum('zero', 'one', 'two', 'three'),
    object: {
        originalAndConverted: Convert.all({
            original: Convert.id,
            converted: Convert.to('')
        }),
        strictNumberOrString: Guard.isNumber.or(Convert.to('')),
        relaxedNumberOrString: Cast.asNumber.or(Convert.to(''))
    }
})

console.log(converter.convert({ excluded: 'exclude-me' }))
// {
//     integer: 1,
//     number: 0,
//     string: '',
//     boolean: false,
//     trueIfTruthyInput: false,
//     tuple: [ 0, 'default', false ],
//     arrayOfInts: [],
//     percentage: '50%',
//     enum: 'zero',
//     object: {
//         originalAndConverted: { original: undefined, converted: '' },
//         strictNumberOrString: '',
//         relaxedNumberOrString: ''
//     }
// }

console.log(converter.convert({
    integer: 2.99,
    number: '3.14',
    string: 'hello',
    boolean: 'true',
    trueIfTruthyInput: [],
    tuple: ['10', 3.14159, 1, 'exclude-me'],
    arrayOfInts: ['10', 20, '30', false, true],
    percentage: ['0.33333'],
    enum: 'two',
    object: {
        originalAndConverted: 12345,
        strictNumberOrString: '-Infinity',
        relaxedNumberOrString: '-Infinity'
    }
}))
// {
//     integer: 3,
//     number: 3.14,
//     string: 'hello',
//     boolean: true,
//     trueIfTruthyInput: true,
//     tuple: [ 10, '3.14159', true ],
//     arrayOfInts: [ 10, 20, 30, 0, 1 ],
//     percentage: '33%',
//     enum: 'two',
//     object: {
//         originalAndConverted: { original: 12345, converted: '12345' },
//         strictNumberOrString: '-Infinity',
//         relaxedNumberOrString: -Infinity
//     }
// }