{"id":17909617,"url":"https://github.com/clemyan/safe-navigation-proxy","last_synced_at":"2025-04-03T07:43:50.375Z","repository":{"id":65492330,"uuid":"142887282","full_name":"clemyan/safe-navigation-proxy","owner":"clemyan","description":"Safe navigation using ES2015 Proxies","archived":false,"fork":false,"pushed_at":"2018-10-20T13:07:35.000Z","size":151,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-09T20:15:19.692Z","etag":null,"topics":["javascript","null","optional-chaining","safe-navigation","undefined"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clemyan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-30T14:30:38.000Z","updated_at":"2018-10-20T12:49:27.000Z","dependencies_parsed_at":"2023-01-25T19:35:12.730Z","dependency_job_id":null,"html_url":"https://github.com/clemyan/safe-navigation-proxy","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clemyan%2Fsafe-navigation-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clemyan%2Fsafe-navigation-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clemyan%2Fsafe-navigation-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clemyan%2Fsafe-navigation-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clemyan","download_url":"https://codeload.github.com/clemyan/safe-navigation-proxy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246961898,"owners_count":20861177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","null","optional-chaining","safe-navigation","undefined"],"created_at":"2024-10-28T19:26:29.516Z","updated_at":"2025-04-03T07:43:50.333Z","avatar_url":"https://github.com/clemyan.png","language":"JavaScript","readme":"# safe-navigation-proxy\n\n[![npm](https://img.shields.io/npm/v/safe-navigation-proxy.svg?longCache=true\u0026style=flat-square)](https://www.npmjs.com/package/safe-navigation-proxy)\n[![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/safe-navigation-proxy.svg?longCache=true\u0026style=flat-square\u0026label=min%2Bgzip)](https://bundlephobia.com/result?p=safe-navigation-proxy)\n[![GitHub](https://img.shields.io/github/license/clemyan/safe-navigation-proxy.svg?longCache=true\u0026style=flat-square)](https://github.com/clemyan/safe-navigation-proxy/blob/master/LICENSE)\n\nSafe navigation using ES2015 Proxies\n\n## Installation\n\n### npm\n```bash\n$ npm install safe-navigation-proxy\n# or\n$ yarn add safe-navigation-proxy\n# or\n$ pnpm install safe-navigation-proxy\n```\n\n### Browser\n```html\n\u003cscript src=\"https://unpkg.com/safe-navigation-proxy/dist/index.js\"\u003e\u003c/script\u003e\n```\n\n### Manual build\n\nTo manually build from source, clone this Github repo.\n\nThen, install all dependencies. Dependencies are originally managed using [`pnpm`](https://pnpm.js.org/), though `npm` and `yarn` should work fine.\n```bash\n$ npm install\n# or\n$ yarn isntall\n# or\n$ pnpm install\n```\nwhich should also run the build. If not, run\n```bash\n$ npm run build\n```\n\nThe 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 `$`).\n\nFiles for other module loader styles (ES2015, CommonJS, UMD) are also available in the respective directories.\n\n## Caveats\n\n### Support\n\n`safe-navigation-proxy` only supports environments that supports both [ES2015 Proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#Browser_compatibility) and [Symbol.toPrimitive](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive#Browser_compatibility).\n\n### Performance\n\nWhile `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.\n\n## Example\n\n```JavaScript\nimport $ from 'safe-navigation-proxy'\n\nconst obj = {n: {e: {s: {t: {e: {d: {\n\tone: 1,\n\tfunc: () =\u003e 1\n}}}}}}}\nconst proxy = $(obj)\n\n// Get\nconsole.log(proxy.n.e.s.t.e.d.one.$()) // 1\nconsole.log(proxy.non.existent.property.$()) // undefined\nconsole.log(proxy.non.existent.property.$(2)) // 2\n\n// Set\nproxy.x.y.z = 2\nconsole.log(JSON.stringify(obj.x)) // {\"y\":{\"z\":2}}\n\n// Apply\nconsole.log(proxy.n.e.s.t.e.d.func().$()) // 1\nconsole.log(proxy.non.existent.func().$()) // undefined\n```\n\n## Documentation\n\n### Safe navigation proxies\n\nSafe 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 `TypeError`s for `undefined` or `null` intermediate values (a.k.a. safe navigation).\n\nThere 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.\n\nOn 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.\n\nNotice 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`.\n\nSome operations create \"detached\" nil references, denoted as `$N{}`. Operations on them cannot mutate any visible objects.\n\n### Construction\n\nThe 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.\n\n`$(value)` returns a detached nil if `value` is nullish (by default, `undefined` and `null` are nullish), and a valued proxy containing that value otherwise.\n\nThat is\n\n- `$(value)` returns `$N{}` if `value` is nullish\n- `$(value)` returns `$V{value}` otherwise\n\n### Unwrapping\n\nTo 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. `$(...).$`).\n\nThe 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.\n\nThat is,\n\n- `$N.$(def)` and `$N[$](def)` returns `def`\n- `$V{value}.$(def)` and `$V{value}[$](def)` returns `value`\n\nNote that this allows safe navigation proxies to be used for nullish-coalescing\n\n```JavaScript\nconsole.log($(undefined).$(2)) // 2\nconsole.log($(null).$(2)) // 2\nconsole.log($(1).$(2)) // 1\n```\n\n### Get\n\nProperty access is the primary use case of safe navigation proxies.\n\nGetting a property of a nil reference returns another nil, referencing the corresponding property _of the former nil reference_.\n\nThe 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.\n\nThat is\n\n- `$N{ref}.prop` returns `$N{$N{ref}.prop}`\n- `$V{value}.prop` returns `$N{value.prop}` if `value.prop` is nullish\n- `$V{value}.prop` returns `$V{value.prop}` if `value.prop` is not nullish\n\nSince `undefined` is nullish by default, accessing an undefined property via a safe navigation proxy returns a nil reference.\n\n### Set\n\nLike 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\".\n\nWhen 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\n\nAssigning a value to a property of a valued proxy delegates to a normal assignment to the contained value.\n\nThat is\n\n- Setting `$N{ref}.prop = v` sets `ref = {prop: v}`\n- Setting `$V{value}.prop = v` sets `value.prop = v`\n\nNote 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:\n\n1. `N{$N{obj.a}.b}.c = {d: 1}`\n2. `$N{obj.a}.b = {c: {d: 1}}`\n3. `obj.a = {b: {c: {d: 1}}}`\n\nThis distinction is important when configuration comes into the mix.\n\n### Apply\n\nIn 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.\n\nTo 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.\n\nThat is,\n\n- `$N(...args)` returns `$N{}`\n- `$V{value}(...args)` returns `$(value(...args))`\n\nNote 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.\n\n### Configuration\n\nWhile the sections above have detailed the default behavior of safe navigation proxies, their true power lies in their configurability.\n\n#### `$.config(options)`\n\nThe `$.config` function creates a configured instance of `$`\n\n```JavaScript\nconst $conf = $.config(options)\n\n// Then $conf can be used in place of $\nconst value = $conf(obj).n.e.s.t.e.d.$()\n```\n\nAny 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.\n\nAvailable options are\n\n```\n{\n\tisNullish?: Array | function | any,\n\tnoConflict?: true | string | symbol | Array,\n\tnil?: {\n\t\tunwrap?: function | any\n\t\tapply?: function | any\n\t},\n\tpropagate?: {\n\t\ton: 'set' | 'get',\n\t\tvalue: function\n\t} | {\n\t\ton: false\n\t} | 'onSet' | 'onGet' | 'ignore' | false\n}\n```\n\nA configured instance can be further configured, the options will be merged.\n\n```JavaScript\nconst $conf = $.config(options1).config(options2)\n```\n\nThe following sections details each option.\n\n#### `options.isNullish`\n\nThe default `$` treats `undefined` and `null` as nullish. The `isNullish` configuration changes what value(s) is/are considered nullish.\n\nType/Value | Meaning\n-----------|-----------------\n`Array` | A value is considered nullish if and only if it is contained within `isNullish`, determined with [`Array.prototype.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes).\n`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.\nOther | A value is considered nullish if and only if it is the same as `isNullish`, determined with [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).\n\nNote 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]`.\n\n#### `options.noConflict`\n\nBy 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.\n\n```JavaScript\n$({ $: 1 }).$ // Unwrap method, not proxy with 1 as value\n```\n\nThe `noConflict` configuration can be used to avoid this. Note that regardless of this configuration, the the unwrap method can always be accessed with `$` itself.\n\nType/Value | Meaning\n-----------|-----------------\n`true` | The unwrap method can only be accessed with `$`.\n`string` or `symbol` | The unwrap method can be access using the specified string or symbol as key, in addition to `$`.\n`Array` | The unwrap method can be access using any string or symbol in the array, in addition to `$`.\n\n```JavaScript\nconst sym = Symbol('unwrap')\n\nlet $conf = $.config({noConflict: true})\n// Unwrap method can be accessed as:\n$conf()[$]\n\n$conf = $.config({noConflict: 'unwrap'})\n// Unwrap method can be accessed as:\n$conf().unwrap\n$conf()[$]\n\n$conf = $.config({noConflict: sym})\n// Unwrap method can be accessed as:\n$conf()[sym]\n$conf()[$]\n\n\n$conf = $.config({noConflict: ['unwarp', sym]})\n// Unwrap method can be accessed as:\n$conf().unwrap\n$conf()[sym]\n$conf()[$]\n```\n\n#### `options.nil`\n\nThe `nil` configuration modifies a number of behaviors of nil references.\n\n#### `options.nil.unwrap`\n\nThe default unwrap method of nil references simply returns the first argument it is passed. The `nil.unwrap` configuration replaces the implementation.\n\nType/Value | Meaning\n-----------|-----------------\n`function` | The unwrap method of nil is `nil.unwrap`.\nOther | The unwrap method of nil takes no argument and returns `nil.unwrap`.\n\n```JavaScript\nlet $conf = $.config({nil: {unwrap: arg =\u003e {\n\tconsole.log(`Unwrapping nil with ${arg}`)\n}})\n\n$conf().$(42) // Logs: \"Unwrapping nil with 42\"\n\n$conf = $.config({nil: {unwrap: 42}})\n\nconsole.log($conf().$()) // 42\n```\n\n#### `options.nil.apply`\n\nBy default, calling a nil reference as a function simply returns a detached nil. The `nil.apply` configuration replaces that implementation.\n\nType/Value | Meaning\n-----------|-----------------\n`function` | Calling a nil reference as a function calls `nil.apply`.\nOther | Calling a nil reference as a function returns `nil.apply`.\n\n```JavaScript\nlet $conf = $.config({nil: {apply: arg =\u003e {\n\tconsole.log(`Applying nil with ${arg}`)\n}})\n\n$conf()(42) // Logs: \"Applying nil with 42\"\n\n$conf = $.config({nil: {apply: 42}})\n\nconsole.log($conf()()) // 42\n```\n\n#### `options.propagate`\n\nThe `propagate` configuration changes the behavior of \"propagation\" documented above.\n\n#### Propagate on assignment\n\nIf `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.\n\nThat is, `$N{ref}[prop] = value` sets `ref = propagate.value.call($N{ref}, prop, value)`\n\nRecall 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.\n\nSetting `propagate` configuration to `'onSet'` is a shorthand for `{on: 'set', value: (k,v) =\u003e ({[k]: v})}`, which is the same as the default behavior.\n\n#### Propagate on access\n\nIf `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.\n\nThat is, assuming `value[prop]` is nullish, accessing `$V{value}[prop]` sets `value[prop] = propagate.value.call(value, prop)` and returns `$(value)[prop]`.\n\nNote 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\n\nSetting `propagate` configuration to `'onGet'` is a shorthand for `{on: 'get', value: () =\u003e ({})}`.\n\n#### No propagation\n\nIf `propagate.on` is `false`, then setting a property of a nil reference does nothing.\n\nSetting `propagate` configuration to either `'ignore'` or `false` is a shorthand for `{on: false}`.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclemyan%2Fsafe-navigation-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclemyan%2Fsafe-navigation-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclemyan%2Fsafe-navigation-proxy/lists"}