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

io-ts-extra

v0.11.6

Published

Adds pattern matching, optional properties, and several other helpers and types, to io-ts.

Downloads

48,614

Readme

io-ts-extra

Adds pattern matching, optional properties, and several other helpers and types, to io-ts.

Node CI codecov npm version

Features

  • Pattern matching
  • Optional properties
  • Advanced refinement types
  • Regex types
  • Parser helpers

Contents

Motivation

Comparison with io-ts

The maintainers of io-ts are (rightly) strict about keeping the API surface small and manageable, and the implementation clean. As a result, io-ts is a powerful but somewhat low-level framework.

This library implements some higher-level concepts for use in real-life applications with complex requirements - combinators, utilities, parsers, reporters etc.

Comparison with io-ts-types

io-ts-types exists for similar reasons. This library will aim to be orthogonal to io-ts-types, and avoid re-inventing the wheel by exposing types that already exist there.

io-ts-extra will also aim to provide more high-level utilities and combinators than pre-defined codecs.

Philosophically, this library will skew slightly more towards pragmatism at the expense of type soundness - for example the stance on t.refinement vs t.brand.

This package is also less mature. It's currently in v0, so will have a different release cadence than io-ts-types.

Documentation

Pattern matching

match

Match an object against a number of cases. Loosely based on Scala's pattern matching.

Example
// get a value which could be a string or a number:
const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10
const stringified = match(value)
 .case(String, s => `the message is ${s}`)
 .case(7, () => 'exactly seven')
 .case(Number, n => `the number is ${n}`)
 .get()

Under the hood, io-ts is used for validation. The first argument can be a "shorthand" for a type, but you can also pass in io-ts codecs directly for more complex types:

Example
// get a value which could be a string or a number:
const value = Math.random() < 0.5 ? 'foo' : 123
const stringified = match(value)
 .case(t.number, n => `the number is ${n}`)
 .case(t.string, s => `the message is ${s}`)
 .get()

you can use a predicate function or t.refinement for the equivalent of scala's case x: Int if x > 2:

Example
// value which could be a string, or a real number in [0, 10):
const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10
const stringified = match(value)
 .case(Number, n => n > 2, n => `big number: ${n}`)
 .case(Number, n => `small number: ${n}`)
 .default(x => `not a number: ${x}`)
 .get()
Example
// value which could be a string, or a real number in [0, 10):
const value = Math.random() < 0.5 ? 'foo' : Math.random() * 10
const stringified = match(value)
 .case(t.refinement(t.number, n => n > 2), n => `big number: ${n}`)
 .case(t.number, n => `small number: ${n}`)
 .default(x => `not a number: ${x}`)
 .get()

note: when using predicates or t.refinement, the type being refined is not considered exhaustively matched, so you'll usually need to add a non-refined option, or you can also use .default as a fallback case (the equivalent of .case(t.any, ...))

Params

|name|description | |----|--------------------------------| |obj |the object to be pattern-matched|

matcher

Like @see match but no object is passed in when constructing the case statements. Instead .get is a function into which a value should be passed.

Example
const Email = t.type({sender: t.string, subject: t.string, body: t.string})
const SMS = t.type({from: t.string, content: t.string})
const Message = t.union([Email, SMS])
type Message = typeof Message._A

const content = matcher<MessageType>()
  .case(SMS, s => s.content)
  .case(Email, e => e.subject + '\n\n' + e.body)
  .get({from: '123', content: 'hello'})

expect(content).toEqual('hello')

The function returned by .get is stateless and has no this context, you can store it in a variable and pass it around:

Example
const getContent = matcher<Message>()
  .case(SMS, s => s.content)
  .case(Email, e => e.subject + '\n\n' + e.body)
  .get

const allMessages: Message[] = getAllMessages();
const contents = allMessages.map(getContent);

Shorthand

The "shorthand" format for type specifications maps to io-ts types as follows:

codecFromShorthand

Gets an io-ts codec from a shorthand input:

|shorthand|io-ts type| |-|-| |String, Number, Boolean|t.string, t.number, t.boolean| |Literal raw strings, numbers and booleans e.g. 7 or 'foo'|t.literal(7), t.literal('foo') etc.| |Regexes e.g. /^foo/|see regexp| |null and undefined|t.null and t.undefined| |No input (not the same as explicitly passing undefined)|t.unknown| |Objects e.g. { foo: String, bar: { baz: Number } }|t.type(...) e.g. t.type({foo: t.string, bar: t.type({ baz: t.number }) }) |Array|t.unknownArray| |Object|t.object| |One-element arrays e.g. [String]|t.array(...) e.g. t.array(t.string)| |Tuples with explicit length e.g. [2, [String, Number]]|t.tuple e.g. t.tuple([t.string, t.number])| |io-ts codecs|unchanged| |Unions, intersections, partials, tuples with more than 3 elements, and other complex types|not supported, except by passing in an io-ts codec|

Codecs/Combinators

sparseType

Can be used much like t.type from io-ts, but any property types wrapped with optional from this package need not be supplied. Roughly equivalent to using t.intersection with t.type and t.partial.

Example
const Person = sparseType({
  name: t.string,
  age: optional(t.number),
})

// no error - `age` is optional
const bob: typeof Person._A = { name: 'bob' }
Params

|name |description | |-----|----------------------------------------------| |props|equivalent to the props passed into t.type|

Returns

a type with props field, so the result can be introspected similarly to a type built with t.type or t.partial - which isn't the case if you manually use t.intersection([t.type({...}), t.partial({...})])

optional

unions the passed-in type with null and undefined.

mapper

A helper for building "parser-decoder" types - that is, types that validate an input, transform it into another type, and then validate the target type.

Example
const StringsFromMixedArray = mapper(
  t.array(t.any),
  t.array(t.string),
  mixedArray => mixedArray.filter(value => typeof value === 'string')
)
StringsFromMixedArray.decode(['a', 1, 'b', 2]) // right(['a', 'b'])
StringsFromMixedArray.decode('not an array')   // left(...)
Params

|name |description | |-----|-----------------------------------------------| |from |the expected type of input value | |to |the expected type of the decoded value | |map |transform (decode) a from type to a to type| |unmap|transfrom a to type back to a from type |

parser

A helper for parsing strings into other types. A wrapper around mapper where the from type is t.string.

Example
const IntFromString = parser(t.Int, parseFloat)
IntFromString.decode('123')          // right(123)
IntFromString.decode('123.4')        // left(...)
IntFromString.decode('not a number') // left(...)
IntFromString.decode(123)            // left(...)
Params

|name |description | |------|--------------------------------------------| |type |the target type | |decode|transform a string into the target type | |encode|transform the target type back into a string|

strict

Like t.type, but fails when any properties not specified in props are defined.

Example
const Person = strict({name: t.string, age: t.number})

expectRight(Person.decode({name: 'Alice', age: 30}))
expectLeft(Person.decode({name: 'Bob', age: 30, unexpectedProp: 'abc'}))
expectRight(Person.decode({name: 'Bob', age: 30, unexpectedProp: undefined}))
Params

|name |description | |-----|-------------------------------------------------------| |props|dictionary of properties, same as the input to t.type| |name |optional type name |

note:

  • additional properties explicitly set to undefined are permitted.
  • internally, sparseType is used, so optional properties are supported.

narrow

Like io-ts's refinement type but:

  1. Not deprecated (see https://github.com/gcanti/io-ts/issues/373)
  2. Passes in Context to the predicate argument, so you can check parent key names etc.
  3. Optionally allows returning another io-ts codec instead of a boolean for better error messages.
Example
const CloudResources = narrow(
  t.type({
    database: t.type({username: t.string, password: t.string}),
    service: t.type({dbConnectionString: t.string}),
  }),
  ({database}) => t.type({
    service: t.type({dbConnectionString: t.literal(`${database.username}:${database.password}`)}),
  })
)

const valid = CloudResources.decode({
  database: {username: 'user', password: 'pass'},
  service: {dbConnectionString: 'user:pass'},
})
// returns a `Right`

const invalid = CloudResources.decode({
  database: {username: 'user', password: 'pass'},
  service: {dbConnectionString: 'user:wrongpassword'},
})
// returns a `Left` - service.dbConnectionString expected "user:pass", but got "user:wrongpassword"

validationErrors

Similar to io-ts's PathReporter, but gives slightly less verbose output.

Params

|name |description | |----------|------------------------------------------------------------------------------------------------------------------------------------| |validation|Usually the result of calling .decode with an io-ts codec. | |typeAlias |io-ts type names can be verbose. If the type you're using doesn't have a name,you can use this to keep error messages shorter.|

regexp

A type which validates its input as a string, then decodes with String.prototype.match, succeeding with the RegExpMatchArray result if a match is found, and failing if no match is found.

Example
const AllCaps = regexp(/\b([A-Z]+)\b/)
AllCaps.decode('HELLO')  // right([ 'HELLO', index: 0, input: 'HELLO' ])
AllCaps.decode('hello')  // left(...)
AllCaps.decode(123)      // left(...)

instanceOf

Validates that a value is an instance of a class using the instanceof operator

Example
const DateType = instanceOf(Date)
DateType.is(new Date())  // right(Date(...))
DateType.is('abc')       // left(...)