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

tucson-decode

v0.2.1

Published

Runtime validator that will not leave you in the desert

Downloads

3

Readme

tucson

Type-safe runtime schema validator that won't leave you in the desert.

Getting started

Simply install as npm install tucson and start validating like so:

import * as tucson from "tucson-decode";

tucson.object({
  name: tucson.string,
  age: tucson.number,
})({ name: "Paul", age: "thirty-five" }); // logs { type: "error", value: { path: [ "age" ], error: "expected number", received: "thirty-five" } }

Anything you create/combine with tucson will become a function that you can call on your data, returning a result object indicating success or a specific error you can reconcile with your server.

Motivation

We wanted a clean, simple, non-obtrusive solution that supports complicated data structures while not compromising type-safety and ease of use.

The API is inspired by Elm's json decoders, making sure it suits TypeScript well and giving it a lodash-style twist. Hello safe and familiar.

API guide

Primitive decoders

Primitive decoders decode primitive types that translate into primitive types in TypeScript. tucson.string(2) returns an error because it was given a number, whereas tucson.boolean(true) and tucson.number(5) will come back with a success. You get the deal.

Combining decoders

Non-primitive types are built up from primitive ones using helpers like tucson.object in the example above. The combine methods are as follows:

optional

Any decoder can be made optional so it succeeds with undefined if the value isn't there.

object

A decoder for a statically defined object structure can be simply built up from an object of the same shape, just with equivalent decoders as field values:

interface Person {
  name: string;
  age: number;
}

const personDecoder: tucson.Decoder<Person> = tucson.object({
  name: tucson.string,
  age: tucson.number,
});

This is completely type-safe, courtesy of the TypeScript compiler.

dictionary

Dictionaries are the dynamic cousins of objects so they can have any number of keys with the restriction that values are of the same type. They correspond most closely to maps in JavaScript and the Record type in TypeScript. We are refraining from those names to avoid confusion on minor nuances.

You can define a dictionary decoder by simply passing it the decoder of the value:

tucson.dictionary(tucson.string)({ one: "two", three: "four" }); // success

array

By calling tucson.array(someDecoder as tucson.Decoder<Some>) an array of a Some's is decoded.

Transforming decoders

Any realistic application will run into the following needs:

  • transforming the result of a successful decode (date string to date object for instance)
  • decoding algebraic data types
  • performing fine-grained validation such as integers only or last names present

This is where tucson gets very unopinionated and mathemtical, allowing you to do all this in pretty much two methods:

map

map simply transforms a successful decode result, while obviously leaving unsuccessful ones alone with their original error message.

tucson.map(tucson.string, Number)("2"); // { type: "success", value: 2 }

Any transformation can be made at this point, maintaining type-safety through function signatures.

flatMap

The limitation of map is that if the original decoder succeeds, the mapped one succeeds also. But what if I want to reject a value like 358.37 coming in for a field like conference attendees?

When using flatMap, the mapping function doesn't return a value, but instead a decoder, which is 'flattened' under the hood to get a final value:

const attendeesDecoder = tucson.flatMap(tucson.number, count => {
  if (count === Math.floor(count)) {
    return tucson.succeed(count);
  }
  return tucson.fail("expected an integer");
});

succeed and fail are decoders that immediately resolve in a constant or success value, similar to Promise.resolve or Promise.reject. They seem trivial, but come in super handy in situations like this.

Custom decoders

Why did we call them decoders? They're basically a function that takes an any and returns { type: "success", value: T } | { type: "error", value: "should be pleasing to the eye" }, so you can quickly come up with domain-specific decoders and not be tied up with an opinionated library. tucson takes care of composition so you can easily set up the building blocks that are right for you.

With custom decoders, however, you are responsible that they don't thrown runtime errors, a guarantee that tucson's primitives will keep for you.

Errors

When decoders fail, they provide to-the-point error messages that help pin down errors easily. They can be referenced instantly to open up a discussion around frontend-backend contracts and find bugs in both places.

A typical error message looks like this:

{
  type: "error",
  value: [
    // There can be multiple error messages
    {
      path: [ "address", "street" ],
      error: "expected string",
      received: 2
    }
  ]
}

Alternatives

tucson is inspired by and an alternative to the following projects:

Contributing

Feel free to just open an issue and start a discussion. This will be more formal when the library gets more exposure.


tucson is born and raised at Contiamo in Berlin. Our Arizona ties are scarce