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

@interaktiv/types

v1.1.0

Published

Types and related tools for Javascript at DIA

Downloads

15

Readme

@interaktiv/types

Types and related tools for Javascript at DIA

Commitizen friendly Conventional Commits Semantic Release Code of Conduct MIT License

npm latest version npm next version

The Problem

Using type guards in your code improves its runtime type safety characteristics, makes it more readable, and provides richer typing information for IDEs. Type guards are implemented as conditional statements, however, and can quickly become noisy and make what was once terse JavaScript code expand into several lines of type checking.

This library aimed to simplify the experience of reducing the amount of type guards needed to process e.g. a typed-JSON data structure by providing several convenience functions that help extract well-typed data from such JSON structures.

This Solution

This is a simple library developed for use in DIA Javascript libraries, applications, and plugins consisting of "two" parts:

  1. A collection of type-narrowing convenience functions for writing concise type-guards.
  2. Maybe in the future a collection of commonly desired types for TypeScript.

Table of Contents

Installation

This module can be installed via npm which is bundled with Node.js and should be installed as one of your project's dependencies:

npm install --save @interaktiv/types

Usage

For example, look at the following typical untyped JSON processing in JavaScript:

// concise, but not at all null-safe or type-safe
// often made to be at least null-safe using lodash functions
JSON.parse(response.body).results.forEach(item => db.save(item.id, item));

Then a safe version in bare TypeScript using type guards:

const json = JSON.parse(response.body);
// type of json -> `any`, but will not be undefined or JSON.parse would throw
if (json === null && typeof json !== 'object')
  throw new Error('Unexpected json data type');
let results = json.results;

// type of results -> `any`
if (!Array.isArray(results)) results = [];

// type of results -> `any[]`
results.forEach(item => {
  // type of item -> `any`
  const id = item.id;

  // type of id -> `any`
  if (typeof id !== 'string') throw new Error('Unexpected item id data type');

  // type of id -> `string`
  db.save(id, item);
});

While that's pretty safe, it's also a mess to read and write. That's why this library is here to help!

const json = ensureJsonMap(JSON.parse(response.body));

// type of json -> `JsonMap` or raises an error
const results = asJsonArray(json.results, []);

// type of results -> `JsonArray` or uses the default of `[]`
results.forEach(item => {
  // type of item -> `AnyJson`
  record = ensureJsonMap(record);
  db.save(ensureString(record.id), record);
});

Removing the comments, we can create something short with robust type and null checking implemented:

asJsonArray(ensureJsonMap(JSON.parse(response.body)).results, []).forEach(
  item => {
    const record = ensureJsonMap(item);
    db.save(ensureString(record.id), record);
  },
);

The ensure* functions are used in this example since they will raise an error when the value being checked either does not exist or does not match the expected type. Of course, you don't always want to raise an error when these conditions are not met, so alternative forms exist for each of the JSON data types that allow the types to be tested and narrowed -- see the is* and as* variants for testing and narrowing capabilities without additionally raising errors.

Narrowing functions

This library provides several categories of functions to help with safely narrowing variables of broadly typed variables, like unknown or object, to more specific types.

is*

The is* suite of functions accept a variable of a broad type such as unknown or object and returns a boolean type-predicate useful for narrowing the type in conditional scopes.

// type of value -> string | boolean
if (isString(value)) {
  // type of value -> string
}
// type of value -> boolean

as*

The as* suite of functions accept a variable of a broad type such as unknown or object and optionally returns a narrowed type after validating it with a runtime test. If the test is negative or if the value was not defined (i.e. undefined or null), undefined is returned instead.

// some function that takes a string or undefined
function upperFirst(s) {
  return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
}
// type of value -> unknown
const name = upperFirst(asString(value));
// type of name -> Optional<string>

ensure*

The ensure* suite of functions narrow values' types to a definite value of the designated type, or raises an error if the value is undefined or of an incompatible type.

// type of value -> unknown
try {
  const s = ensureString(value);
  // type of s -> string
} catch (err) {
  // s was undefined, null, or not of type string
}

has*

The has* suite of functions both tests for the existence and type-compatibility of a given value and, if the runtime value check succeeds, narrows the type to a view of the original value's type intersected with the tested property (e.g. T & { [_ in K]: V } where K is the test property key and V is the test property value type).

// type of value -> unknown
if (hasString(value, 'name')) {
  // type of value -> { name: string }
  // value can be further narrowed with additional checks
  if (hasArray(value, 'results')) {
    // type of value -> { name: string } & { results: unknown[] }
  } else if (hasInstance(value, 'error', Error)) {
    // type of value -> { name: string } & { error: Error }
  }
}

get*

The get* suite of functions search an unknown target value for a given path. Search paths follow the same syntax as lodash's get, set, at, etc. These functions are more strictly typed, however, increasingly the likelihood that well-typed code stays well-typed as a function's control flow advances.

// imagine response json retrieved from a remote query
const response = {
  start: 0,
  length: 2,
  results: [{ name: 'first' }, { name: 'second' }],
};
const nameOfFirst = getString(response, 'results[0].name');
// type of nameOfFirst = string

coerce*

The coerce suite of functions accept values of general types and narrow their types to JSON-specific values. They are named with the coerce prefix to indicate that they do not perform an exhaustive runtime check of the entire data structure -- only shallow type checks are performed. As a result, only use these functions when you are confident that the broadly typed subject being coerced was derived from a JSON-compatible value.

const response = coerceJsonMap(
  JSON.parse(await http.get('http://example.com/data.json').body),
);
// type of response -> JsonMap

Object Utilities

This suite of functions are used to iterate the keys, entries, and values of objects with some typing conveniences applied that are not present in their built-in counterparts (i.e. Object.keys, Object.entries, and Object.values), but come with some caveats noted in their documentation. Typical uses include iterating over the properties of an object with more useful keyof typings applied during the iterator bodies, and/or filtering out undefined or null values before invoking the iterator functions.

const pets = {
  fido: 'dog',
  bill: 'cat',
  fred: undefined,
};

// note that the array is typed as [string, string] rather than [string, string | undefined]
function logPet([name, type]: [string, string]) {
  console.log('%s is a %s', name, type);
}

definiteEntriesOf(pets).forEach(logPet);
// fido is a dog
// bill is a cat

References

This library is using custom error types from @interaktiv/errors.

Other Use Cases

If you lack some use cases, you are welcome to open a pull request and add it. We'll come back to you and see how we can support your use case and present it to all devs.

Please consult the contribution guides before contributing.

Acknowledgements

This library is heavily inspired by @salesforce/ts-types. Thank you 💙

License

MIT Copyright © 2019-present die.interaktiven GmbH & Co. KG