existential-proxy
Advanced tools
Comparing version 0.0.1 to 0.0.2
@@ -6,4 +6,9 @@ "use strict"; | ||
}); | ||
const MAGIC_PROXY_SYMBOL = Symbol('Magic proxy symbol'); | ||
exports.MAGIC_PROXY_SYMBOL = Symbol('Magic proxy symbol'); | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
function get(input, callback, defaultValue) { | ||
@@ -46,2 +51,55 @@ let currentValue = input; | ||
var set_1 = require("./set"); | ||
exports.set = set_1.set; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
function setIn(input, keys, newValue) { | ||
if (!keys.length) { | ||
return newValue; | ||
} | ||
const [firstKey, ...restKeys] = keys; | ||
const key = Number.isFinite(parseFloat(firstKey)) ? parseFloat(firstKey) : firstKey; | ||
let copy; | ||
if (input === null || typeof input === 'undefined') { | ||
copy = typeof key === 'number' ? [] : {}; | ||
} else { | ||
copy = Array.isArray(input) ? [...input] : Object.assign({}, input); | ||
} | ||
copy[key] = setIn(copy[key], restKeys, newValue); | ||
return copy; | ||
} | ||
exports.setIn = setIn; | ||
function set(input, callback, newValue) { | ||
const keys = []; | ||
const handlers = { | ||
get(_value, key) { | ||
keys.push(key); | ||
return new Proxy({}, handlers); | ||
} | ||
}; | ||
const proxy = new Proxy({}, handlers); | ||
callback(proxy); | ||
return setIn(input, keys, newValue); | ||
} | ||
exports.set = set; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
const constants_1 = require("./constants"); | ||
//# sourceMappingURL=index.js.map |
@@ -20,1 +20,14 @@ // tslint:disable:no-console | ||
console.log(b); | ||
interface NumKey { | ||
a?: { | ||
0: string; | ||
}; | ||
} | ||
const numKey: NumKey = {}; | ||
// Will create an array when trying to access the `0` key | ||
const numKeyResult = ep.set(numKey, proxy => proxy.a[0], 'hello'); // { a: ['hello'] }: | ||
console.log(numKeyResult); |
{ | ||
"name": "existential-proxy", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "Type safe existential property access using ES6 proxies", | ||
@@ -18,3 +18,4 @@ "main": "dist/index.js", | ||
"tests": "jest", | ||
"test": "npm run type-check && npm run lint && npm run tests -- --runInBand --coverage" | ||
"test": "npm run type-check && npm run lint && npm run tests -- --runInBand --coverage", | ||
"examples": "ts-node examples/index.ts" | ||
}, | ||
@@ -58,2 +59,3 @@ "repository": { | ||
"ts-jest": "^23.10.4", | ||
"ts-node": "^7.0.1", | ||
"tslint": "^5.11.0", | ||
@@ -68,2 +70,5 @@ "tslint-config-prettier": "^1.15.0", | ||
], | ||
"coveragePathIgnorePatterns": [ | ||
"types.ts" | ||
], | ||
"setupFiles": [ | ||
@@ -70,0 +75,0 @@ "<rootDir>/tests/helpers/setup.ts" |
@@ -17,6 +17,10 @@ # existential-proxy | ||
Additionally, unlike (some) alternatives that allow access to nested properties this library also allows setting values inside a nested object in an immutable way (returning a copy of the input with updated values as necessary). | ||
### Future plans | ||
Although this library currently only offers the ability to `get` values from a nested object, I intend to add the ability to `set` or `update` (with a function) nested values in an immutable way. This is something that optional chaining would not necessarily provide. | ||
Although this library currently only offers the ability to `get` values from a nested object, and `set` values inside a nested object, I intend to add the ability to `update` (with a function) nested values in an immutable way. This is something that optional chaining would not necessarily provide. | ||
Additionally, I may add a function to create a selector, much like [reselect](https://github.com/reduxjs/reselect), to allow transforming values only when changed, but without the need to null check nested values. | ||
## Installation | ||
@@ -32,21 +36,17 @@ | ||
### Get | ||
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). | ||
The `get` function takes 3 arguments: | ||
```typescript | ||
import * as ep from 'existential-proxy'; | ||
``` | ||
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 | ||
Almost of the examples below will use the following types / object. | ||
Simply import the library and begin accessing the keys that you want via the proxy. | ||
```typescript | ||
import * as ep from 'existential-proxy'; | ||
// Here's our example type | ||
// Here's an example object type | ||
interface ABC { | ||
a: { | ||
b?: { | ||
c: string | ||
} | ||
c: string; | ||
}; | ||
} | null; | ||
@@ -59,3 +59,13 @@ } | ||
}; | ||
``` | ||
### 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 | ||
```typescript | ||
// Access without defaults | ||
@@ -73,1 +83,40 @@ ep.get(abc, (proxy) => proxy); // ABC | ||
``` | ||
### 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 | ||
Some important things to note: | ||
1. The return type will always match the input type - if keys are nullable, they will still be nullable even if set by this function | ||
2. Keys that when cast to a number are finite numbers 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 undefined or null (all keys; `.a`, `[0]`, are only available as strings when using a proxy). | ||
```typescript | ||
// 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 | ||
``` | ||
This library's `set` function may not give you the output you'd expect if you are using numbers as keys in objects. | ||
```typescript | ||
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 | ||
``` |
@@ -1,21 +0,3 @@ | ||
const MAGIC_PROXY_SYMBOL = Symbol('Magic proxy symbol'); | ||
import { WithProxy } from './types'; | ||
export interface AccessProxy<T> { | ||
[MAGIC_PROXY_SYMBOL]: T; | ||
} | ||
export interface WithProxyArray<T, U> extends ReadonlyArray<WithProxy<T | U>> {} | ||
export type WithProxyObject<T, U> = { [P in keyof T]-?: WithProxy<T[P] | U> }; | ||
export type WithProxy< | ||
T, | ||
U = T extends undefined | null ? undefined : never, | ||
S = Exclude<T, undefined | null> | ||
> = S extends object | ||
? WithProxyObject<T, U> & AccessProxy<T> | ||
: S extends ReadonlyArray<infer V> | ||
? WithProxyArray<V, U> & AccessProxy<T> | ||
: U | T & AccessProxy<T>; | ||
export function get<T extends object, R, D extends undefined | never | void>( | ||
@@ -39,3 +21,3 @@ input: T, | ||
const handlers = { | ||
get(value: T, key: keyof T): object { | ||
get<S>(value: S, key: keyof S): object { | ||
currentValue = value[key]; | ||
@@ -42,0 +24,0 @@ |
export { get } from './get'; | ||
export { set } from './set'; | ||
export * from './types'; |
@@ -1,2 +0,2 @@ | ||
import { get } from '../src'; | ||
import * as ep from '../src'; | ||
@@ -29,6 +29,6 @@ describe('get', () => { | ||
it('should return the requested value when it exists', () => { | ||
const root = get(obj1, proxy => proxy); | ||
const foo = get(obj1, proxy => proxy.foo); | ||
const bar = get(obj1, proxy => proxy.foo.bar); | ||
const baz = get(obj1, proxy => proxy.foo.bar.baz); | ||
const root = ep.get(obj1, proxy => proxy); | ||
const foo = ep.get(obj1, proxy => proxy.foo); | ||
const bar = ep.get(obj1, proxy => proxy.foo.bar); | ||
const baz = ep.get(obj1, proxy => proxy.foo.bar.baz); | ||
@@ -42,6 +42,6 @@ expect(root).toEqual({ foo: { bar: { baz: 'baz' } } }); | ||
it('should return undefined when the value does not exist', () => { | ||
const root = get(obj2, proxy => proxy); | ||
const foo = get(obj2, proxy => proxy.foo); | ||
const bar = get(obj2, proxy => proxy.foo.bar); | ||
const baz = get(obj2, proxy => proxy.foo.bar.baz); | ||
const root = ep.get(obj2, proxy => proxy); | ||
const foo = ep.get(obj2, proxy => proxy.foo); | ||
const bar = ep.get(obj2, proxy => proxy.foo.bar); | ||
const baz = ep.get(obj2, proxy => proxy.foo.bar.baz); | ||
@@ -55,6 +55,6 @@ expect(root).toEqual({}); | ||
it('should return the default value when the value does not exist', () => { | ||
const root = get(obj2, proxy => proxy, { foo: null }); | ||
const foo = get(obj2, proxy => proxy.foo, { bar: null }); | ||
const bar = get(obj2, proxy => proxy.foo.bar, { baz: 'def' }); | ||
const baz = get(obj2, proxy => proxy.foo.bar.baz, 'def'); | ||
const root = ep.get(obj2, proxy => proxy, { foo: null }); | ||
const foo = ep.get(obj2, proxy => proxy.foo, { bar: null }); | ||
const bar = ep.get(obj2, proxy => proxy.foo.bar, { baz: 'def' }); | ||
const baz = ep.get(obj2, proxy => proxy.foo.bar.baz, 'def'); | ||
@@ -68,6 +68,6 @@ expect(root).toEqual({}); | ||
it('should return the default value when the value is null', () => { | ||
const root = get(obj3, proxy => proxy, { foo: null }); | ||
const foo = get(obj3, proxy => proxy.foo, { bar: { baz: 'def' } }); | ||
const bar = get(obj3, proxy => proxy.foo.bar, { baz: 'def' }); | ||
const baz = get(obj3, proxy => proxy.foo.bar.baz, 'def'); | ||
const root = ep.get(obj3, proxy => proxy, { foo: null }); | ||
const foo = ep.get(obj3, proxy => proxy.foo, { bar: { baz: 'def' } }); | ||
const bar = ep.get(obj3, proxy => proxy.foo.bar, { baz: 'def' }); | ||
const baz = ep.get(obj3, proxy => proxy.foo.bar.baz, 'def'); | ||
@@ -74,0 +74,0 @@ expect(root).toEqual({ foo: { bar: null } }); |
@@ -9,3 +9,3 @@ { | ||
"rules": { | ||
"space-before-function-paren": [2, "always"], | ||
"variable-name": [true, "allow-leading-underscore", "ban-keywords"], | ||
"strict-type-predicates": true, | ||
@@ -12,0 +12,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23214
23
450
119
14