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

existential-proxy

v0.0.5

Published

Type safe existential property access using ES6 proxies

Downloads

39

Readme

existential-proxy

CircleCI

Type safe existential property access using ES6 proxies

About

This library gives you the ability to get, set, or update (with an update function) values nested within an object who's keys may be nullable (undefined or null), without mutating the original input, or having to worry about checking for values' existence.

Optional chaining in JavaScript / TypeScript is not yet finalized, and so we need a way to safely access nested values that may or may not exist.

This library was created for use with TypeScript to give sensible types when accessing nullable nested values without having to specify the types yourself.

Although designed with TypeScript in mind, existential-proxy works perfectly well with JavaScript.

Unlike destructuring with default values existential-proxy allows access through values that may be undefined or null without specifying default values for each level, and allows specifying defaults even when the value may be null.

Additionally, unlike (some) alternatives that allow access to nested properties this library also allows setting or updating values inside a nested object in an immutable way (returning a copy of the input with updated values as necessary).

Future plans

I intend to add a function to create a selector, much like reselect, to allow transforming values only when changed, but using proxies removing the need to null check nested values.

Installation

Install existential-proxy with NPM (-S will automatically add this to your package.json):

npm i existential-proxy -S

Usage

I'd recommend importing * as so that you can easily tell where the functions are coming from, and avoid naming conflicts (as many libraries will use similarly named functions).

import * as ep from 'existential-proxy';

Almost of the examples below will use the following types / object.

// Here's an example object type
interface ABC {
  a: {
    b?: {
      c: string;
    };
  } | null;
}

// Here's our example object
const abc: ABC = {
  a: {} // Note that the b key does not exist
};

Get

The get function takes 3 arguments:

  1. The object from which you wish to retrieve a value
  2. A callback that will be passed a proxy to this object
  3. An optional default value
// Access without defaults
ep.get(abc, (proxy) => proxy); // ABC
ep.get(abc, (proxy) => proxy.a); // { b?: { c: string; } } | null
ep.get(abc, (proxy) => proxy.a.b); // { c: string; } | undefined
ep.get(abc, (proxy) => proxy.a.b.c); // string | undefined

// Access with defaults
ep.get(abc, (proxy) => proxy, 'whatever'); // ABC
ep.get(abc, (proxy) => proxy.a, { b: { c: 'hello' } }); // { b?: { c: string; } } | { b: { c: string; } }
ep.get(abc, (proxy) => proxy.a.b, { c: 'hello' }); // { c: string; }
ep.get(abc, (proxy) => proxy.a.b.c, 'hello'); // string

Set

The set function takes 3 arguments:

  1. The object from which you wish to retrieve a value
  2. A callback that will be passed a proxy to this object
  3. The new value to be set at the returned proxy key
// Will return the provided value (essentially replacing the input object)
ep.set(abc, (proxy) => proxy, { a: { b: { c: 'hello' } } }); // { a: { b: { c: 'hello' } } }: ABC
// Will return a copy of the `abc` object with a new `a` value
ep.set(abc, (proxy) => proxy.a, { b: { c: 'hello' } }); // { a: { b: { c: 'hello' } } }: ABC
// Will return a copy of the `abc` object with a new `b` value
ep.set(abc, (proxy) => proxy.a.b, { c: 'hello' }); // { a: { b: { c: 'hello' } } }: ABC

Update

The update function takes 3 arguments:

  1. The object from which you wish to retrieve a value
  2. A callback that will be passed a proxy to this object
  3. An updater function that will be passed the existing value at the returned proxy key, and returns a new value
// Will return the returned value (essentially replacing the input object)
ep.update(abc, (proxy) => proxy, () => ({ a: { b: { c: 'hello' } } })); // { a: { b: { c: 'hello' } } }: ABC
// Will return a copy of the `abc` object with a new `a` value if it is not defined
ep.update(abc, (proxy) => proxy.a, (a) => {
  if (!a) {
    return { b: { c: 'hello' } };
  }

  return a;
}); // { a: { b: { c: 'hello' } } }: ABC
// Will return a copy of the `abc` object with a new `b` value if it is not defined
ep.update(abc, (proxy) => proxy.a.b, (b) => {
  if (!b) {
    return { c: 'hello' };
  }

  return b;
}); // { a: { b: { c: 'hello' } } }: ABC

Important notes

When using set and update you should note that:

  1. The return type will always match the input type - if keys are nullable, they will still be nullable even if set by one of these functions
  2. Keys that when cast to a number are a valid integer (including negative values) will produce arrays if the parent object is undefined or null. This is because there is no way to detect if trying to access values from an array or object if the target is not already an array or object (all keys; .a, [0], are only available as strings when using a proxy).

Example of potentially unintended array creation

This library's set and update functions may not give you the output you'd expect if you are using integers as keys in objects.

interface NumKey {
  a?: {
    0: string;
  };
}

const numKey: NumKey = {};

// Will create an array when trying to access the `0` key
ep.set(numKey, (proxy) => proxy.a[0], 'hello'); // { a: ['hello'] }: NumKey
// Will still create an array when trying to access the `0` key
ep.set(numKey, (proxy) => proxy.a['0'], 'hello'); // { a: ['hello'] }: NumKey