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

safe-navigation-proxy

v1.0.1

Published

Safe navigation using ES2015 Proxies

Downloads

5

Readme

safe-navigation-proxy

npm npm bundle size (minified + gzip) GitHub

Safe navigation using ES2015 Proxies

Installation

npm

$ npm install safe-navigation-proxy
# or
$ yarn add safe-navigation-proxy
# or
$ pnpm install safe-navigation-proxy

Browser

<script src="https://unpkg.com/safe-navigation-proxy/dist/index.js"></script>

Manual build

To manually build from source, clone this Github repo.

Then, install all dependencies. Dependencies are originally managed using pnpm, though npm and yarn should work fine.

$ npm install
# or
$ yarn isntall
# or
$ pnpm install

which should also run the build. If not, run

$ npm run build

The build output is in the dist directory. dist/index.js is in the revealing module pattern. I.e. it is an IIFE whose return value is assigned to a global variable, which makes it suitable for use in a browser environment. In this case, a function is assigned to safeNav (documented below as $).

Files for other module loader styles (ES2015, CommonJS, UMD) are also available in the respective directories.

Caveats

Support

safe-navigation-proxy only supports environments that supports both ES2015 Proxies and Symbol.toPrimitive.

Performance

While safe-navigation-proxy is written with performance (time and memory) in mind, the overhead of using Proxies is unavoidable. For performance-critical code. Use plain conditionals to check for undefined and null intermediate values.

Example

import $ from 'safe-navigation-proxy'

const obj = {n: {e: {s: {t: {e: {d: {
	one: 1,
	func: () => 1
}}}}}}}
const proxy = $(obj)

// Get
console.log(proxy.n.e.s.t.e.d.one.$()) // 1
console.log(proxy.non.existent.property.$()) // undefined
console.log(proxy.non.existent.property.$(2)) // 2

// Set
proxy.x.y.z = 2
console.log(JSON.stringify(obj.x)) // {"y":{"z":2}}

// Apply
console.log(proxy.n.e.s.t.e.d.func().$()) // 1
console.log(proxy.non.existent.func().$()) // undefined

Documentation

Safe navigation proxies

Safe navigation proxies are, as you may have guessed, the basis of safe-navigation-proxy. As shown in the examples above, they allow access and other operations on nested properties of objects without throwing TypeErrors for undefined or null intermediate values (a.k.a. safe navigation).

There are two kinds of safe navigation proxies. A valued proxy contains a value, and operations on them are, in some sense, forwarded to the contained value. They are denoted $V{value} or $V in this documentation.

On the other hand, a nil reference represents a non-existent value. Nil references are usually created by attempting to access a non-existent or null property via a valued proxy. They are denoted $N{ref} or $N in this documentation.

Notice that they are called nil references. While JavaScript does not have abitrary references and aliases, there are limited cases where the effect can be achieved. Using those, a nil reference $N{ref} can change the value being referred to by ref.

Some operations create "detached" nil references, denoted as $N{}. Operations on them cannot mutate any visible objects.

Construction

The default export of safe-navigation-proxy is a function that constructs a safe navigation proxy. This function is documented as $ below. But note that the revealing module (dist/index.js) and the UMD module (in revealing module mode) distributables assign this function to the global variable safeNav instead of $ to prevent conflict with other libraries.

$(value) returns a detached nil if value is nullish (by default, undefined and null are nullish), and a valued proxy containing that value otherwise.

That is

  • $(value) returns $N{} if value is nullish
  • $(value) returns $V{value} otherwise

Unwrapping

To retrieve values from safe navigation proxies, they have an unwrap method. It is accessible as a property of safe navigation proxies whose key is $ itself (i.e. $(...)[$]). By default, it is also accessible as the $ property of safe navigation proxies (i.e. $(...).$).

The unwrap method of a valued proxy returns the contained value. By default, the unwrap method of a nil reference returns the first argument it receives. This allows one to pass a "default value" argument, which is returned if the proxy is nil and ignored otherwise. Note that the argument is undefined if none is explicitly passed.

That is,

  • $N.$(def) and $N[$](def) returns def
  • $V{value}.$(def) and $V{value}[$](def) returns value

Note that this allows safe navigation proxies to be used for nullish-coalescing

console.log($(undefined).$(2)) // 2
console.log($(null).$(2)) // 2
console.log($(1).$(2)) // 1

Get

Property access is the primary use case of safe navigation proxies.

Getting a property of a nil reference returns another nil, referencing the corresponding property of the former nil reference.

The results of getting a property of a valued proxy depends on the value of the corresponding property of the contained value. If that is nullish, a nil reference to that property is returned. If that is not nullish, a valued proxy containing the value of that property is returned.

That is

  • $N{ref}.prop returns $N{$N{ref}.prop}
  • $V{value}.prop returns $N{value.prop} if value.prop is nullish
  • $V{value}.prop returns $V{value.prop} if value.prop is not nullish

Since undefined is nullish by default, accessing an undefined property via a safe navigation proxy returns a nil reference.

Set

Like accessing deeply nested properties, creating deeply nested properties is also troublesome. In order to do so, one often has to create a stack of empty objects. safe-navigation-proxy simplifies this by supporting "propagation".

When propagation on assignment is enabled (which is the default), assigning a value to a property of a nil reference sets the referent to (by default) an object with only one own enumerable property -- the key-value pair being assigned. Then

Assigning a value to a property of a valued proxy delegates to a normal assignment to the contained value.

That is

  • Setting $N{ref}.prop = v sets ref = {prop: v}
  • Setting $V{value}.prop = v sets value.prop = v

Note that this means assignment propagates from deeply nested properties to shallow properties. Assuming obj.a is nullish, $(obj).a.b.c.d = 1 is $N{$N{$N{obj.a}.b}.c}.d = 1. That resolves as:

  1. N{$N{obj.a}.b}.c = {d: 1}
  2. $N{obj.a}.b = {c: {d: 1}}
  3. obj.a = {b: {c: {d: 1}}}

This distinction is important when configuration comes into the mix.

Apply

In JavaScript, functions are first class objects and can be assigned to object properties. These methods can be accessed using safe navigation proxies, but working with them only using the features above is cumbersome. One have to unwrap with a default implementation, call, then rewrap.

To facilitate safely navigating to and through methods, safe navigation proxies can be called as functions to effectively perform the process outlined above. In particular, calling a valued proxy calls the contained value as a function and wraps the return value in a safe navigation proxy; and calling a nil reference returns a detached nil by default.

That is,

  • $N(...args) returns $N{}
  • $V{value}(...args) returns $(value(...args))

Note that if a valued proxy containing a non-function value is called, the value will be called as a function, resulting in TypeError being thrown.

Configuration

While the sections above have detailed the default behavior of safe navigation proxies, their true power lies in their configurability.

$.config(options)

The $.config function creates a configured instance of $

const $conf = $.config(options)

// Then $conf can be used in place of $
const value = $conf(obj).n.e.s.t.e.d.$()

Any safe navigation proxy created from operations on a configured proxy also inherits the configuration. So, proxies in a get/apply chain exhibits the same behavior.

Available options are

{
	isNullish?: Array | function | any,
	noConflict?: true | string | symbol | Array,
	nil?: {
		unwrap?: function | any
		apply?: function | any
	},
	propagate?: {
		on: 'set' | 'get',
		value: function
	} | {
		on: false
	} | 'onSet' | 'onGet' | 'ignore' | false
}

A configured instance can be further configured, the options will be merged.

const $conf = $.config(options1).config(options2)

The following sections details each option.

options.isNullish

The default $ treats undefined and null as nullish. The isNullish configuration changes what value(s) is/are considered nullish.

Type/Value | Meaning -----------|----------------- Array | A value is considered nullish if and only if it is contained within isNullish, determined with Array.prototype.includes. function | A value is considered nullish if and only if isNullish(value) returns a truthy value. Note that if isNullish throws, $conf(value) also throws with the same error. Other | A value is considered nullish if and only if it is the same as isNullish, determined with Object.is.

Note that if options.isNullish explicitly set to or declared as undefined, then null is not considered nullish since only undefined is. To trigger the default behavior, make sure options does not have isNullish as an own property or use the array [undefined, null].

options.noConflict

By default, one can access the unwrap method of a safe navigation proxy as the properties whose keys are $ and '$' (i.e.$(...)[$] and $(...).$). However, the latter may clash with the underlying value if it has a $ property. In this case, the unwrap method "shadows" that property.

$({ $: 1 }).$ // Unwrap method, not proxy with 1 as value

The noConflict configuration can be used to avoid this. Note that regardless of this configuration, the the unwrap method can always be accessed with $ itself.

Type/Value | Meaning -----------|----------------- true | The unwrap method can only be accessed with $. string or symbol | The unwrap method can be access using the specified string or symbol as key, in addition to $. Array | The unwrap method can be access using any string or symbol in the array, in addition to $.

const sym = Symbol('unwrap')

let $conf = $.config({noConflict: true})
// Unwrap method can be accessed as:
$conf()[$]

$conf = $.config({noConflict: 'unwrap'})
// Unwrap method can be accessed as:
$conf().unwrap
$conf()[$]

$conf = $.config({noConflict: sym})
// Unwrap method can be accessed as:
$conf()[sym]
$conf()[$]


$conf = $.config({noConflict: ['unwarp', sym]})
// Unwrap method can be accessed as:
$conf().unwrap
$conf()[sym]
$conf()[$]

options.nil

The nil configuration modifies a number of behaviors of nil references.

options.nil.unwrap

The default unwrap method of nil references simply returns the first argument it is passed. The nil.unwrap configuration replaces the implementation.

Type/Value | Meaning -----------|----------------- function | The unwrap method of nil is nil.unwrap. Other | The unwrap method of nil takes no argument and returns nil.unwrap.

let $conf = $.config({nil: {unwrap: arg => {
	console.log(`Unwrapping nil with ${arg}`)
}})

$conf().$(42) // Logs: "Unwrapping nil with 42"

$conf = $.config({nil: {unwrap: 42}})

console.log($conf().$()) // 42

options.nil.apply

By default, calling a nil reference as a function simply returns a detached nil. The nil.apply configuration replaces that implementation.

Type/Value | Meaning -----------|----------------- function | Calling a nil reference as a function calls nil.apply. Other | Calling a nil reference as a function returns nil.apply.

let $conf = $.config({nil: {apply: arg => {
	console.log(`Applying nil with ${arg}`)
}})

$conf()(42) // Logs: "Applying nil with 42"

$conf = $.config({nil: {apply: 42}})

console.log($conf()()) // 42

options.propagate

The propagate configuration changes the behavior of "propagation" documented above.

Propagate on assignment

If propagate.on is 'set', then setting a property of a nil reference sets the referent to the return value of calling the propagate.value function with the property key and value as arguments and with this bound to the nil.

That is, $N{ref}[prop] = value sets ref = propagate.value.call($N{ref}, prop, value)

Recall that assignment propagates from deeply nested properties up. If obj.n is nullish, $(obj).n.e.s.t = 1 calls propagate.value with args ('t', 1) first.

Setting propagate configuration to 'onSet' is a shorthand for {on: 'set', value: (k,v) => ({[k]: v})}, which is the same as the default behavior.

Propagate on access

If propagate.on is 'get', then accessing a nullish property of a valued proxy sets the corresponding property of the contained value to the return value of calling the propagate.value function with the property key as argument and with this bound to the contained value.

That is, assuming value[prop] is nullish, accessing $V{value}[prop] sets value[prop] = propagate.value.call(value, prop) and returns $(value)[prop].

Note that if propagate.value never returns a nullish value, then $conf is incapable of creating nil references. If propagate.value return a nullish values, then the resulting nils does not have propagation

Setting propagate configuration to 'onGet' is a shorthand for {on: 'get', value: () => ({})}.

No propagation

If propagate.on is false, then setting a property of a nil reference does nothing.

Setting propagate configuration to either 'ignore' or false is a shorthand for {on: false}.