From cb8933df0aea7c37f90dd9550c92e7b7669b3cef Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Fri, 3 Aug 2018 15:31:26 +0200 Subject: [PATCH 01/13] Fix bug where array is merged with empty object --- src/tests/default-test.ts | 20 ++++++++++++++ src/tests/object-pathRev-test.ts | 46 ++++++++++++++++++++++++++++++++ src/utils/createObjectMapper.ts | 9 +++++-- src/utils/pathSetter-test.ts | 21 +++++++++++++++ src/utils/pathSetter.ts | 8 +++--- 5 files changed, 99 insertions(+), 5 deletions(-) diff --git a/src/tests/default-test.ts b/src/tests/default-test.ts index 23eebb2..aebda7b 100644 --- a/src/tests/default-test.ts +++ b/src/tests/default-test.ts @@ -118,6 +118,26 @@ test('should not use default values', (t) => { t.deepEqual(ret, expected) }) +test('should not set missing prop to undefined', (t) => { + const def = { + mapping: { + title: 'content.heading' + } + } + const data = [ + { content: {} }, + { content: { heading: 'From data' } } + ] + const expected = [ + {}, + { title: 'From data' } + ] + + const ret = mapTransform(def).noDefaults(data) + + t.deepEqual(ret, expected) +}) + test('should not use default values on rev', (t) => { const def = { mapping: { diff --git a/src/tests/object-pathRev-test.ts b/src/tests/object-pathRev-test.ts index db101fc..4c8e3ef 100644 --- a/src/tests/object-pathRev-test.ts +++ b/src/tests/object-pathRev-test.ts @@ -27,6 +27,52 @@ test('should reverse map with object path', (t) => { t.deepEqual(ret, expected) }) +test('should map with object array path', (t) => { + const def = { + mapping: { + title: { path: 'content.heading' } + }, + path: 'content.articles[]' + } + const data = [ + { title: 'Heading 1' }, + { title: 'Heading 2' } + ] + const expected = { + content: { + articles: [ + { content: { heading: 'Heading 1' } }, + { content: { heading: 'Heading 2' } } + ] + } + } + + const ret = mapTransform(def).rev(data) + + t.deepEqual(ret, expected) +}) + +test('should map with root array path', (t) => { + const def = { + mapping: { + title: { path: 'content.heading' } + }, + path: '[]' + } + const data = [ + { title: 'Heading 1' }, + { title: 'Heading 2' } + ] + const expected = [ + { content: { heading: 'Heading 1' } }, + { content: { heading: 'Heading 2' } } + ] + + const ret = mapTransform(def).rev(data) + + t.deepEqual(ret, expected) +}) + test('should reverse map data as is when no mapping', (t) => { const def = { path: 'content' diff --git a/src/utils/createObjectMapper.ts b/src/utils/createObjectMapper.ts index ed5ee86..22ea12d 100644 --- a/src/utils/createObjectMapper.ts +++ b/src/utils/createObjectMapper.ts @@ -13,8 +13,13 @@ const filterObj = (filterFn: FilterFunction, x: any) => const filterAny = (filterFn: FilterFunction) => (x: any) => (x && typeof x.filter === 'function') ? x.filter(filterFn) : filterObj(filterFn, x) -// Lens -> (a -> a) -const setAtObjectPath = (lens: R.Lens): MapperFunction => R.set(lens, _, {}) as MapperFunction +// Lens -> (a -> b) +// Note: Setting a path on null, will return the result from the set operation +// without setting it on any object. This is useful as we don't know whether +// we'll get an object or an array. It does not comply with the Ramda spec, +// however, hence the `as any` casting. Maybe it would be better to bypass the +// Ramda lens altogether, and use the getter and setter methods directly. +const setAtObjectPath = (lens: R.Lens): MapperFunction => R.set(lens, _, null) as any // [(a -> a -> a)] -> g a const reduceMapperFns = (mapperFns: FieldMapperFunction[]): MapperFunction => diff --git a/src/utils/pathSetter-test.ts b/src/utils/pathSetter-test.ts index 9894933..627d47a 100644 --- a/src/utils/pathSetter-test.ts +++ b/src/utils/pathSetter-test.ts @@ -107,6 +107,27 @@ test('should set value at path with array', (t) => { t.deepEqual(ret, expected) }) +test('should set array at path with array', (t) => { + const path = 'meta.authors[]' + const object = {} + const expected = { + meta: { + authors: ['johnf', 'maryk'] + } + } + const ret = pathSetter(path)(['johnf', 'maryk'], object) + + t.deepEqual(ret, expected) +}) + +test('should set array at path with only array brackets', (t) => { + const path = '[]' + const expected = ['johnf', 'maryk'] + const ret = pathSetter(path)(['johnf', 'maryk'], null) + + t.deepEqual(ret, expected) +}) + test('should set value array at path with indexed array', (t) => { const path = 'meta.authors[0].id' const object = {} diff --git a/src/utils/pathSetter.ts b/src/utils/pathSetter.ts index d0b0e12..e535443 100644 --- a/src/utils/pathSetter.ts +++ b/src/utils/pathSetter.ts @@ -59,7 +59,7 @@ const getSetters = R.compose( split ) -type SetFunction = (value: Data, object: Data) => Data +type SetFunction = (value: Data, object: Data | null) => Data /** * Set `value` at `path` in `object`. Note that a new object is returned, and @@ -74,6 +74,8 @@ type SetFunction = (value: Data, object: Data) => Data export default function pathSetter (path: PathString): SetFunction { const setters = getSetters(path) - return (value: Data, object: Data) => - R.mergeDeepRight(object, setters(value)) + return (value, object) => { + const data = setters(value) + return (object) ? R.mergeDeepRight(object, data) : data + } } From ff409e94588ade2d411e7d2aacba936ce117d372 Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Tue, 7 Aug 2018 17:27:39 +0200 Subject: [PATCH 02/13] Move basic features from definition props to the pipeline approach --- README.md | 44 +++- package-lock.json | 7 +- package.json | 3 +- src/funcs/alt-test.ts | 78 ++++++ src/funcs/alt.ts | 14 ++ src/funcs/get-test.ts | 75 ++++++ src/funcs/get.ts | 11 + src/funcs/mutate-test.ts | 115 +++++++++ src/funcs/mutate.ts | 23 ++ src/funcs/pipe-test.ts | 50 ++++ src/funcs/pipe.ts | 12 + src/funcs/set-test.ts | 70 ++++++ src/funcs/set.ts | 28 +++ src/funcs/value-test.ts | 20 ++ src/funcs/value.ts | 6 + src/index.ts | 85 ++----- src/tests/default-test.ts | 29 +-- src/tests/field-path-test.ts | 113 --------- src/tests/field-pathRev-test.ts | 6 +- src/tests/field-transform-test.ts | 14 +- src/tests/filter-test.ts | 26 +- src/tests/object-pathRev-test.ts | 22 +- src/tests/object-transform-test.ts | 26 +- .../{object-path-test.ts => path-test.ts} | 234 ++++++++++++------ src/tests/shape-test.ts | 34 --- src/types.ts | 31 +++ src/utils/createFieldMapper.ts | 36 --- src/utils/createMapper.ts | 93 ------- src/utils/createObjectMapper.ts | 76 ------ src/utils/definitionHelpers.ts | 16 ++ src/utils/lensPath.ts | 21 -- src/utils/normalizeMapping-test.ts | 124 ---------- src/utils/normalizeMapping.ts | 48 ---- src/utils/pathGetter-test.ts | 4 +- src/utils/pathGetter.ts | 15 +- src/utils/pathSetter.ts | 9 +- src/utils/stateHelpers.ts | 24 ++ 37 files changed, 847 insertions(+), 795 deletions(-) create mode 100644 src/funcs/alt-test.ts create mode 100644 src/funcs/alt.ts create mode 100644 src/funcs/get-test.ts create mode 100644 src/funcs/get.ts create mode 100644 src/funcs/mutate-test.ts create mode 100644 src/funcs/mutate.ts create mode 100644 src/funcs/pipe-test.ts create mode 100644 src/funcs/pipe.ts create mode 100644 src/funcs/set-test.ts create mode 100644 src/funcs/set.ts create mode 100644 src/funcs/value-test.ts create mode 100644 src/funcs/value.ts delete mode 100644 src/tests/field-path-test.ts rename src/tests/{object-path-test.ts => path-test.ts} (50%) delete mode 100644 src/tests/shape-test.ts create mode 100644 src/types.ts delete mode 100644 src/utils/createFieldMapper.ts delete mode 100644 src/utils/createMapper.ts delete mode 100644 src/utils/createObjectMapper.ts create mode 100644 src/utils/definitionHelpers.ts delete mode 100644 src/utils/lensPath.ts delete mode 100644 src/utils/normalizeMapping-test.ts delete mode 100644 src/utils/normalizeMapping.ts create mode 100644 src/utils/stateHelpers.ts diff --git a/README.md b/README.md index 38fcd7a..3ecdd9d 100644 --- a/README.md +++ b/README.md @@ -9,23 +9,27 @@ Map and transform objects with mapping definitions. [![Maintainability](https://api.codeclimate.com/v1/badges/fbb6638a32ee5c5f60b7/maintainability)](https://codeclimate.com/github/integreat-io/map-transform/maintainability) Behind this boring name hides a powerful object transformer. There are a lot of -these around, but MapTransform's main differentiator is the mapping definition -object, which is a simple JavaScript object that defines every part of a -transformation up front, and by defining a mapping from one object, you would -also define a mapping back to the original format (with some gotchas). +these around, but MapTransform has some differentating features: +- You pretty much define the transformation by creating the JavaScript object +you want as a result, setting paths and transformation functions, etc. where +they apply. +- There's a concept of a transform pipeline, that your data is passed through, +and you define pipelines anywhere you'd like on the target object. +- By defining a mapping from one object to another, you have at the same time +defined a mapping back to the original format (with some gotchas). Let's look at a simple example: ```javascript import mapTransform from 'map-transform' -const def = { - mapping: { - 'title': 'content.headline', - 'author': 'meta.writer.username' - }, - path: 'sections[0].articles' -} +const def = [ + 'sections[0].articles', + { + title: 'content.headline', + author: 'meta.writer.username' + } +] const mapper = mapTransform(def) @@ -142,6 +146,9 @@ npm install map-transform --save ### Mapping definition +**Note:** This description is obsolete. All the same features (and more) will be +available in version 0.2, but with different syntax. + ```javascript { filterFrom: , @@ -179,7 +186,8 @@ First of all, if there's the `pathFrom` property on the root object is set, with from the source data. When this `path` is not set, the source data is just used as is. -Then the `path` property of each object on `mapping`, is used to retrieve field values from the object(s) returned from the root `path`, using the value of +Then the `path` property of each object on `mapping`, is used to retrieve field +values from the object(s) returned from the root `path`, using the value of `default` when the path does not match a value in the source data. Next, the path string used as keys for the object in `mapping`, is used to set @@ -199,6 +207,15 @@ There is a shortcut when defining mappings without default values. The `mapping` object `{'title': 'content.heading'}` is exactly the same as `{'title': {path: 'content.heading'}}`. +The `path` properties below the `mapping` object will relate to the data +extracted and transformed (see below) by the definition properties on the root +object. If you need to get values from the original data object, prefix the path +with a `$`. E.g. when mapping items from `content.items[]`, a field may still +have a path like `$meta.author.id` to retrieve a value from the root of the +original data. Note that `$` paths will never be used to set values, as this +could lead to many items setting the same root property, which would be +unpredictable. + The `transformTo` and `transformToRev` props, with `transform` and `transformRev` as aliases, will transform a field or an object, and should be set to a transform function or an array of transform functions, also called a @@ -240,7 +257,8 @@ set to `null` to keep a filter pipeline from being applied on reverse mapping. The `mapping` object defines the shape of the target item(s) to map _to_, with options for how values _from_ the source object should be mapped to it. Another -way of specifying this shape, is simply supplying it as nested objects. In the following example, `def1` and `def2` are two ways of defining the exact same +way of specifying this shape, is simply supplying it as nested objects. In the +following example, `def1` and `def2` are two ways of defining the exact same mapping, it's simply a matter of taste: ``` diff --git a/package-lock.json b/package-lock.json index 7439fe4..0a16ae4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "map-transform", - "version": "0.1.6", + "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2738,6 +2738,11 @@ "capture-stack-trace": "^1.0.0" } }, + "crocks": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/crocks/-/crocks-0.10.1.tgz", + "integrity": "sha512-qYBDmdHmuBWOUqy99Hlg7BjFj1xg9J7F1BDWOAamVud4xRYFl5N5/WV0GUyGIqMRGys3Q9pr/dwwAoTCfrw9+A==" + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", diff --git a/package.json b/package.json index 2b7730c..2a0c14c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "map-transform", - "version": "0.1.6", + "version": "0.2.0", "description": "Map and transform objects with mapping definitions", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -58,6 +58,7 @@ }, "homepage": "https://github.com/integreat-io/map-transform#readme", "dependencies": { + "crocks": "^0.10.1", "map-any": "^0.1.1", "ramda": "^0.25.0" }, diff --git a/src/funcs/alt-test.ts b/src/funcs/alt-test.ts new file mode 100644 index 0000000..6a62f2a --- /dev/null +++ b/src/funcs/alt-test.ts @@ -0,0 +1,78 @@ +import test from 'ava' +import { State } from '../types' + +import alt from './alt' + +// Helpers + +const getUser = (state: State) => ({ ...state, value: (state.value as any).user }) + +// Tests + +test('should set alt value when value is undefined', (t) => { + const state = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: undefined + } + const expected = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: 'johnf' + } + + const ret = alt(getUser)(state) + + t.deepEqual(ret, expected) +}) + +test('should do nothing when value is set', (t) => { + const state = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: 'maryk' + } + const expected = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: 'maryk' + } + + const ret = alt(getUser)(state) + + t.deepEqual(ret, expected) +}) + +test('should treat string as path', (t) => { + const state = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: undefined + } + const expected = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: 'johnf' + } + + const ret = alt('user')(state) + + t.deepEqual(ret, expected) +}) + +test('should treat array as map pipe', (t) => { + const state = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: undefined + } + const expected = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: 'johnf' + } + + const ret = alt(['user'])(state) + + t.deepEqual(ret, expected) +}) diff --git a/src/funcs/alt.ts b/src/funcs/alt.ts new file mode 100644 index 0000000..0c35f1f --- /dev/null +++ b/src/funcs/alt.ts @@ -0,0 +1,14 @@ +import { State, MapFunction, MapDefinition } from '../types' +import { setValueFromState } from '../utils/stateHelpers' +import { mapFunctionFromDef } from '../utils/definitionHelpers' + +export default function alt (fn: MapDefinition): MapFunction { + const runAlt = mapFunctionFromDef(fn) + + return (state: State) => (typeof state.value === 'undefined') + ? setValueFromState( + state, + runAlt({ ...state, value: state.context }) + ) + : state +} diff --git a/src/funcs/get-test.ts b/src/funcs/get-test.ts new file mode 100644 index 0000000..ed634c2 --- /dev/null +++ b/src/funcs/get-test.ts @@ -0,0 +1,75 @@ +import test from 'ava' +import { compose } from 'ramda' + +import get from './get' + +// Helpers + +const object = { + data: { + items: [ + { id: 'item1', title: 'First item', tags: ['one', 'odd'], active: true }, + { id: 'item2', title: 'Second item', tags: ['two', 'even'], active: false }, + { id: 'item3', title: 'Third, but not last', tags: ['three', 'odd'] }, + { id: 'item4', title: 'Fourth and last', tags: ['four', 'even'], active: true } + ] + }, + meta: { + author: 'Someone', + tags: [] + }, + list: [{ id: 'no1' }, { id: 'no2' }, { id: 'no3' }] +} + +// Tests + +test('should get from path and set on value', (t) => { + const state = { + root: object, + context: object, + value: object + } + const expected = { + root: object, + context: object, + value: 'Someone' + } + + const ret = get('meta.author')(state) + + t.deepEqual(ret, expected) +}) + +test('should be composable', (t) => { + const state = { + root: object, + context: object, + value: object + } + const expected = { + root: object, + context: object, + value: 'Second item' + } + + const ret = compose(get('title'), get('data.items[1]'))(state) + + t.deepEqual(ret, expected) +}) + +test('should set value to undefined when path is missing on value', (t) => { + const state = { + root: object, + context: object, + value: object + } + const expected = { + root: object, + context: object, + value: undefined + } + + const ret = get('unknown.path')(state) + + t.deepEqual(ret, expected) +}) diff --git a/src/funcs/get.ts b/src/funcs/get.ts new file mode 100644 index 0000000..92980d8 --- /dev/null +++ b/src/funcs/get.ts @@ -0,0 +1,11 @@ +import { MapFunction, State, Path } from '../types' +import getter from '../utils/pathGetter' + +export default function get (path: Path | null): MapFunction { + const get = getter(path) + + return (state: State): State => ({ + ...state, + value: get(state.value) + }) +} diff --git a/src/funcs/mutate-test.ts b/src/funcs/mutate-test.ts new file mode 100644 index 0000000..9e01890 --- /dev/null +++ b/src/funcs/mutate-test.ts @@ -0,0 +1,115 @@ +import test from 'ava' +import value from './value' +import get from './get' + +import mutate from './mutate' + +test('should mutate object with map functions', (t) => { + const def = { + item: { + id: value('ent1'), + attributes: { + title: get('headline'), + text: value('The text') + }, + relationships: { + author: get('user') + } + } + } + const state = { + root: { data: { headline: 'The title' } }, + context: { headline: 'The title' }, + value: { headline: 'The title', user: 'johnf' } + } + const expected = { + root: { data: { headline: 'The title' } }, + context: { headline: 'The title' }, + value: { + item: { + id: 'ent1', + attributes: { + title: 'The title', + text: 'The text' + }, + relationships: { + author: 'johnf' + } + } + } + } + + const ret = mutate(def)(state) + + t.deepEqual(ret, expected) +}) + +test('should set value to undefined when no map functions', (t) => { + const def = {} + const state = { + root: { data: { headline: 'The title' } }, + context: { headline: 'The title' }, + value: { headline: 'The title', user: 'johnf' } + } + const expected = { + root: { data: { headline: 'The title' } }, + context: { headline: 'The title' }, + value: undefined + } + + const ret = mutate(def)(state) + + t.deepEqual(ret, expected) +}) + +test('should treat string as path', (t) => { + const def = { + item: { + attributes: { + title: 'headline' + } + } + } + const state = { + root: {}, + context: {}, + value: { headline: 'The title' } + } + const expectedValue = { + item: { + attributes: { + title: 'The title' + } + } + } + + const ret = mutate(def)(state) + + t.deepEqual(ret.value, expectedValue) +}) + +test('should treat array as map pipe', (t) => { + const def = { + item: { + attributes: { + title: ['data', 'headline'] + } + } + } + const state = { + root: {}, + context: {}, + value: { data: { headline: 'The title' } } + } + const expectedValue = { + item: { + attributes: { + title: 'The title' + } + } + } + + const ret = mutate(def)(state) + + t.deepEqual(ret.value, expectedValue) +}) diff --git a/src/funcs/mutate.ts b/src/funcs/mutate.ts new file mode 100644 index 0000000..eea85d4 --- /dev/null +++ b/src/funcs/mutate.ts @@ -0,0 +1,23 @@ +import { MapFunction, State, MapDefinition, Path } from '../types' +import set from './set' +import { setValueFromState, shiftState, pipeMapFns } from '../utils/stateHelpers' +import { mapFunctionFromDef, isMapObject } from '../utils/definitionHelpers' + +const appendToPath = (path: string[], fragment: string) => [...path, fragment] + +const extractSetFns = (def: MapDefinition, path: string[] = []): MapFunction[] => (isMapObject(def)) + ? Object.keys(def).reduce( + (sets: MapFunction[], key: string) => [ ...sets, ...extractSetFns(def[key], appendToPath(path, key)) ], + []) + : (def !== null) + ? [set(path.join('.'), mapFunctionFromDef(def as Path | MapFunction))] + : [] + +export default function mutate (def: MapDefinition): MapFunction { + const runMutation = pipeMapFns(extractSetFns(def)) + + return (state: State): State => setValueFromState( + state, + runMutation(shiftState(state)) + ) +} diff --git a/src/funcs/pipe-test.ts b/src/funcs/pipe-test.ts new file mode 100644 index 0000000..733d9e4 --- /dev/null +++ b/src/funcs/pipe-test.ts @@ -0,0 +1,50 @@ +import test from 'ava' +import get from './get' + +import pipe from './pipe' + +test('should run map pipe', (t) => { + const def = [get('data'), get('name')] + const state = { + root: { data: { name: 'John F.' } }, + context: { data: { name: 'John F.' } }, + value: { data: { name: 'John F.' } } + } + const expected = { + root: { data: { name: 'John F.' } }, + context: { data: { name: 'John F.' } }, + value: 'John F.' + } + + const ret = pipe(def)(state) + + t.deepEqual(ret, expected) +}) + +test('should treat string as path', (t) => { + const def = ['data', get('name')] + const state = { + root: {}, + context: {}, + value: { data: { name: 'John F.' } } + } + const expectedValue = 'John F.' + + const ret = pipe(def)(state) + + t.deepEqual(ret.value, expectedValue) +}) + +test('should treat object as map object', (t) => { + const def = ['data', { fullName: 'name' }] + const state = { + root: {}, + context: {}, + value: { data: { name: 'John F.' } } + } + const expectedValue = { fullName: 'John F.' } + + const ret = pipe(def)(state) + + t.deepEqual(ret.value, expectedValue) +}) diff --git a/src/funcs/pipe.ts b/src/funcs/pipe.ts new file mode 100644 index 0000000..b298283 --- /dev/null +++ b/src/funcs/pipe.ts @@ -0,0 +1,12 @@ +import { MapPipe, MapFunction, State } from '../types' +import { setValueFromState, pipeMapFns } from '../utils/stateHelpers' +import { mapFunctionFromDef } from '../utils/definitionHelpers' + +export default function pipe (def: MapPipe): MapFunction { + const runPipe = pipeMapFns(def.map(mapFunctionFromDef)) + + return (state: State) => setValueFromState( + state, + runPipe(state) + ) +} diff --git a/src/funcs/set-test.ts b/src/funcs/set-test.ts new file mode 100644 index 0000000..5ce8a55 --- /dev/null +++ b/src/funcs/set-test.ts @@ -0,0 +1,70 @@ +import test from 'ava' +import get from './get' + +import set from './set' + +// Tests + +test('should get value from context and set on path', (t) => { + const state = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: undefined + } + const expected = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: { meta: { author: 'johnf' } } + } + const ret = set('meta.author', get('user'))(state) + + t.deepEqual(ret, expected) +}) + +test('should set on existing value', (t) => { + const state = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: { meta: { sections: ['news'] } } + } + const expected = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: { meta: { author: 'johnf', sections: ['news'] } } + } + const ret = set('meta.author', get('user'))(state) + + t.deepEqual(ret, expected) +}) + +test('should get value from array and set on path', (t) => { + const state = { + root: [{ user: 'johnf' }, { user: 'maryk' }], + context: [{ user: 'johnf' }, { user: 'maryk' }], + value: undefined + } + const expectedValue = [ + { meta: { author: 'johnf' } }, + { meta: { author: 'maryk' } } + ] + + const ret = set('meta.author', get('user'))(state) + + t.deepEqual(ret.value, expectedValue) +}) + +test('should set undefined', (t) => { + const state = { + root: {}, + context: {}, + value: undefined + } + const expected = { + root: {}, + context: {}, + value: { meta: { author: undefined } } + } + const ret = set('meta.author', get('user'))(state) + + t.deepEqual(ret, expected) +}) diff --git a/src/funcs/set.ts b/src/funcs/set.ts new file mode 100644 index 0000000..9512f3e --- /dev/null +++ b/src/funcs/set.ts @@ -0,0 +1,28 @@ +import { compose, identity } from 'ramda' +import { State, MapFunction, Path, Data } from '../types' +import setter from '../utils/pathSetter' +import { getValue, setValue } from '../utils/stateHelpers' + +export default function set (path: Path, mapFn: MapFunction): MapFunction { + if (mapFn === null) { + return identity + } + + const isArray = path.endsWith('[]') + const set = setter(path) + const runMapping = compose( + getValue, + mapFn, + setValue + ) + + return (state: State) => { + const getAndSet = (from: Data, to?: Data) => set(runMapping(state, from), to) + + const value = (!isArray && Array.isArray(state.context)) + ? state.context.map((val) => getAndSet(val)) + : getAndSet(state.context, state.value) + + return { ...state, value } + } +} diff --git a/src/funcs/value-test.ts b/src/funcs/value-test.ts new file mode 100644 index 0000000..b9f38fc --- /dev/null +++ b/src/funcs/value-test.ts @@ -0,0 +1,20 @@ +import test from 'ava' + +import value from './value' + +test('should set on value', (t) => { + const state = { + root: {}, + context: {}, + value: undefined + } + const expected = { + root: {}, + context: {}, + value: 'Splendid!' + } + + const ret = value('Splendid!')(state) + + t.deepEqual(ret, expected) +}) diff --git a/src/funcs/value.ts b/src/funcs/value.ts new file mode 100644 index 0000000..967dd81 --- /dev/null +++ b/src/funcs/value.ts @@ -0,0 +1,6 @@ +import { set, lensProp } from 'ramda' +import { Data, MapFunction } from '../types' + +export default function value (val: Data): MapFunction { + return set(lensProp('value'), val) +} diff --git a/src/index.ts b/src/index.ts index 48f12b5..4ec04d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,72 +1,15 @@ -import * as R from 'ramda' -import { - createMapper, - MapperFunctionWithRev -} from './utils/createMapper' -import { PathString } from './utils/lensPath' -import { MappingDef } from './utils/normalizeMapping' -import { TransformPipeline } from './utils/transformPipeline' -import { FilterPipeline } from './utils/filterPipeline' - -namespace mapTransform { - export interface Shape { - [key: string]: PathString | MappingDef | Shape | null - } - - export interface DefinitionNormalized { - pathFrom?: PathString | null, - pathFromRev?: PathString | null, - filterFrom?: FilterPipeline, - filterFromRev?: FilterPipeline, - transformFrom?: TransformPipeline, - transformFromRev?: TransformPipeline, - mapping?: Shape, - transformTo?: TransformPipeline, - transformToRev?: TransformPipeline, - filterTo?: FilterPipeline, - filterToRev?: FilterPipeline, - pathTo?: PathString | null, - pathToRev?: PathString | null - } - - export interface Definition extends DefinitionNormalized { - path?: PathString | null, - pathRev?: PathString | null, - transform?: TransformPipeline, - transformRev?: TransformPipeline, - filter?: FilterPipeline, - filterRev?: FilterPipeline, - } - - type DataProperty = string | number | boolean | object - - export interface DataWithProps { - [key: string]: DataProperty | DataProperty[] - } - - export type Data = DataWithProps | DataWithProps[] | DataProperty | DataProperty[] +import { compose } from 'ramda' +import { MapDefinition, DataMapper } from './types' +import { mapFunctionFromDef } from './utils/definitionHelpers' +import { populateState, getValue } from './utils/stateHelpers' + +export { default as alt } from './funcs/alt' +export { default as value } from './funcs/value' + +export function mapTransform (def: MapDefinition): DataMapper { + return compose( + getValue, + mapFunctionFromDef(def), + populateState + ) } - -const identityMapper: MapperFunctionWithRev = Object.assign( - (data: mapTransform.Data | null) => data, - { - noDefaults: R.identity, - rev: Object.assign( - (data: mapTransform.Data | null) => data, - { noDefaults: R.identity } - ) - } -) - -/** - * Will return a function that executes the mapping defined in `mapping`. - * When no mapping is provided, an identity function is returned. - * - * @param {Object} definition - A mapping definition - * @returns {function} A mapper function - */ -function mapTransform (definition?: mapTransform.Definition | null): MapperFunctionWithRev { - return (definition) ? createMapper(definition) : identityMapper -} - -export = mapTransform diff --git a/src/tests/default-test.ts b/src/tests/default-test.ts index aebda7b..145a16a 100644 --- a/src/tests/default-test.ts +++ b/src/tests/default-test.ts @@ -1,16 +1,13 @@ import test from 'ava' -import mapTransform = require('..') +import { mapTransform, alt, value } from '..' test('should use default value', (t) => { const def = { - mapping: { - title: { - path: 'content.heading', - default: 'Default heading', - defaultRev: 'Wrong way' - } - } + title: [ + 'content.heading', + alt(value('Default heading')) + ] } const data = [ { content: {} }, @@ -28,11 +25,7 @@ test('should use default value', (t) => { test('should set missing values to undefined when no default', (t) => { const def = { - mapping: { - title: { - path: 'content.heading' - } - } + title: 'content.heading' } const data = [ { content: {} }, @@ -48,7 +41,7 @@ test('should set missing values to undefined when no default', (t) => { t.deepEqual(ret, expected) }) -test('should use defaultRev value', (t) => { +test.skip('should use defaultRev value', (t) => { const def = { mapping: { title: { @@ -72,7 +65,7 @@ test('should use defaultRev value', (t) => { t.deepEqual(ret, expected) }) -test('should set missing value to undefined when no defaultRev', (t) => { +test.skip('should set missing value to undefined when no defaultRev', (t) => { const def = { mapping: { title: { @@ -94,7 +87,7 @@ test('should set missing value to undefined when no defaultRev', (t) => { t.deepEqual(ret, expected) }) -test('should not use default values', (t) => { +test.skip('should not use default values', (t) => { const def = { mapping: { title: { @@ -118,7 +111,7 @@ test('should not use default values', (t) => { t.deepEqual(ret, expected) }) -test('should not set missing prop to undefined', (t) => { +test.skip('should not set missing prop to undefined', (t) => { const def = { mapping: { title: 'content.heading' @@ -138,7 +131,7 @@ test('should not set missing prop to undefined', (t) => { t.deepEqual(ret, expected) }) -test('should not use default values on rev', (t) => { +test.skip('should not use default values on rev', (t) => { const def = { mapping: { title: { diff --git a/src/tests/field-path-test.ts b/src/tests/field-path-test.ts deleted file mode 100644 index 1f8e0f5..0000000 --- a/src/tests/field-path-test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import test from 'ava' - -import * as mapTransform from '..' - -test('should map simple object', (t) => { - const def = { - mapping: { - title: 'content.heading', - author: 'meta.writer.username' - } - } - const data = { - content: { heading: 'The heading' }, - meta: { writer: { username: 'johnf' } } - } - const expected = { - title: 'The heading', - author: 'johnf' - } - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test('should map array of objects', (t) => { - const def = { - mapping: { - title: 'content.heading', - author: 'meta.writer.username' - } - } - const data = [ - { - content: { heading: 'The heading' }, - meta: { writer: { username: 'johnf' } } - }, - { - content: { heading: 'Second heading' }, - meta: { writer: { username: 'maryk' } } - } - ] - const expected = [ - { title: 'The heading', author: 'johnf' }, - { title: 'Second heading', author: 'maryk' } - ] - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test('should not map fields without paths', (t) => { - const def = { - mapping: { - title: null, - author: 'meta.writer.username' - } - } - const data = { - content: { heading: 'The heading' }, - meta: { writer: { username: 'johnf' } } - } - const expected = { - author: 'johnf' - } - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test('should map with array index path', (t) => { - const def = { - mapping: { - title: 'content.heading', - author: 'meta.writers[0].username' - } - } - const data = { - content: { heading: 'The heading' }, - meta: { writers: [ { username: 'johnf' }, { username: 'maryk' } ] } - } - const expected = { - title: 'The heading', - author: 'johnf' - } - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test('should map with array all path', (t) => { - const def = { - mapping: { - title: 'content.heading', - authors: 'meta.writers[].id' - } - } - const data = { - content: { heading: 'The heading' }, - meta: { writers: [ { id: 'johnf' }, { id: 'maryk' } ] } - } - const expected = { - title: 'The heading', - authors: ['johnf', 'maryk'] - } - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) diff --git a/src/tests/field-pathRev-test.ts b/src/tests/field-pathRev-test.ts index eb54b50..e9fe203 100644 --- a/src/tests/field-pathRev-test.ts +++ b/src/tests/field-pathRev-test.ts @@ -2,7 +2,7 @@ import test from 'ava' import * as mapTransform from '..' -test('should reverse map simple object', (t) => { +test.skip('should reverse map simple object', (t) => { const def = { mapping: { title: 'content.heading', @@ -23,7 +23,7 @@ test('should reverse map simple object', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map array of objects', (t) => { +test.skip('should reverse map array of objects', (t) => { const def = { mapping: { title: 'content.heading', @@ -50,7 +50,7 @@ test('should reverse map array of objects', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with array path', (t) => { +test.skip('should reverse map with array path', (t) => { const def = { mapping: { title: 'content.heading', diff --git a/src/tests/field-transform-test.ts b/src/tests/field-transform-test.ts index 9212acb..2be8efe 100644 --- a/src/tests/field-transform-test.ts +++ b/src/tests/field-transform-test.ts @@ -16,7 +16,7 @@ enclose.rev = (str: string) => (str.startsWith('(') && str.endsWith(')')) // Tests -test('should map field with one transform function', (t) => { +test.skip('should map field with one transform function', (t) => { const def = { mapping: { title: { @@ -37,7 +37,7 @@ test('should map field with one transform function', (t) => { t.deepEqual(ret, expected) }) -test('should map field with array of transform functions', (t) => { +test.skip('should map field with array of transform functions', (t) => { const def = { mapping: { title: { @@ -58,7 +58,7 @@ test('should map field with array of transform functions', (t) => { t.deepEqual(ret, expected) }) -test('should apply transform functions from left to right', (t) => { +test.skip('should apply transform functions from left to right', (t) => { const def = { mapping: { titleLength: { @@ -79,7 +79,7 @@ test('should apply transform functions from left to right', (t) => { t.deepEqual(ret, expected) }) -test('should not fail with empty transform array', (t) => { +test.skip('should not fail with empty transform array', (t) => { const def = { mapping: { title: { @@ -100,7 +100,7 @@ test('should not fail with empty transform array', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with transform functions from transformRev', (t) => { +test.skip('should reverse map with transform functions from transformRev', (t) => { const def = { mapping: { title: { @@ -122,7 +122,7 @@ test('should reverse map with transform functions from transformRev', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with transform function from rev props', (t) => { +test.skip('should reverse map with transform function from rev props', (t) => { const def = { mapping: { title: { @@ -143,7 +143,7 @@ test('should reverse map with transform function from rev props', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with several transform functions from rev props', (t) => { +test.skip('should reverse map with several transform functions from rev props', (t) => { const def = { mapping: { title: { diff --git a/src/tests/filter-test.ts b/src/tests/filter-test.ts index 304adb6..5ff68b5 100644 --- a/src/tests/filter-test.ts +++ b/src/tests/filter-test.ts @@ -13,7 +13,7 @@ const noAlso: FilterFunction = (item: {title: string}) => // Tests -test('should filter out item', (t) => { +test.skip('should filter out item', (t) => { const def = { mapping: { title: 'content.heading' @@ -30,7 +30,7 @@ test('should filter out item', (t) => { t.deepEqual(ret, expected) }) -test('should filter out items in array', (t) => { +test.skip('should filter out items in array', (t) => { const def = { mapping: { title: 'content.heading' @@ -51,7 +51,7 @@ test('should filter out items in array', (t) => { t.deepEqual(ret, expected) }) -test('should filter with array of filters', (t) => { +test.skip('should filter with array of filters', (t) => { const def = { mapping: { title: 'content.heading' @@ -73,7 +73,7 @@ test('should filter with array of filters', (t) => { t.deepEqual(ret, expected) }) -test('should keep all when filter is empty array', (t) => { +test.skip('should keep all when filter is empty array', (t) => { const def = { mapping: { title: 'content.heading' @@ -94,7 +94,7 @@ test('should keep all when filter is empty array', (t) => { t.deepEqual(ret, expected) }) -test('should set filtered items on pathTo', (t) => { +test.skip('should set filtered items on pathTo', (t) => { const def = { mapping: { title: 'content.heading' @@ -117,7 +117,7 @@ test('should set filtered items on pathTo', (t) => { t.deepEqual(ret, expected) }) -test('should filter items from pathTo for reverse mapping', (t) => { +test.skip('should filter items from pathTo for reverse mapping', (t) => { const def = { mapping: { title: 'content.heading' @@ -140,7 +140,7 @@ test('should filter items from pathTo for reverse mapping', (t) => { t.deepEqual(ret, expected) }) -test('should filter with filterTo', (t) => { +test.skip('should filter with filterTo', (t) => { const def = { mapping: { title: 'content.heading' @@ -157,7 +157,7 @@ test('should filter with filterTo', (t) => { t.deepEqual(ret, expected) }) -test('should filter on reverse mapping', (t) => { +test.skip('should filter on reverse mapping', (t) => { const def = { mapping: { title: 'content.heading' @@ -179,7 +179,7 @@ test('should filter on reverse mapping', (t) => { t.deepEqual(ret, expected) }) -test('should filter with filterRev on reverse mapping', (t) => { +test.skip('should filter with filterRev on reverse mapping', (t) => { const def = { mapping: { title: 'content.heading' @@ -203,7 +203,7 @@ test('should filter with filterRev on reverse mapping', (t) => { t.deepEqual(ret, expected) }) -test('should filter with filterToRev on reverse mapping', (t) => { +test.skip('should filter with filterToRev on reverse mapping', (t) => { const def = { mapping: { title: 'content.heading' @@ -227,7 +227,7 @@ test('should filter with filterToRev on reverse mapping', (t) => { t.deepEqual(ret, expected) }) -test('should filter with filterFrom', (t) => { +test.skip('should filter with filterFrom', (t) => { const def = { mapping: { heading: 'title' @@ -245,7 +245,7 @@ test('should filter with filterFrom', (t) => { t.deepEqual(ret, expected) }) -test('should filter with filterFrom on reverse mapping', (t) => { +test.skip('should filter with filterFrom on reverse mapping', (t) => { const def = { mapping: { heading: 'title' @@ -263,7 +263,7 @@ test('should filter with filterFrom on reverse mapping', (t) => { t.deepEqual(ret, expected) }) -test('should filter with filterFromRev on reverse mapping', (t) => { +test.skip('should filter with filterFromRev on reverse mapping', (t) => { const def = { mapping: { heading: 'title' diff --git a/src/tests/object-pathRev-test.ts b/src/tests/object-pathRev-test.ts index 4c8e3ef..588fe58 100644 --- a/src/tests/object-pathRev-test.ts +++ b/src/tests/object-pathRev-test.ts @@ -2,7 +2,7 @@ import test from 'ava' import * as mapTransform from '..' -test('should reverse map with object path', (t) => { +test.skip('should reverse map with object path', (t) => { const def = { mapping: { title: { path: 'content.heading' } @@ -27,7 +27,7 @@ test('should reverse map with object path', (t) => { t.deepEqual(ret, expected) }) -test('should map with object array path', (t) => { +test.skip('should map with object array path', (t) => { const def = { mapping: { title: { path: 'content.heading' } @@ -52,7 +52,7 @@ test('should map with object array path', (t) => { t.deepEqual(ret, expected) }) -test('should map with root array path', (t) => { +test.skip('should map with root array path', (t) => { const def = { mapping: { title: { path: 'content.heading' } @@ -73,7 +73,7 @@ test('should map with root array path', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map data as is when no mapping', (t) => { +test.skip('should reverse map data as is when no mapping', (t) => { const def = { path: 'content' } @@ -93,7 +93,7 @@ test('should reverse map data as is when no mapping', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with object pathFrom', (t) => { +test.skip('should reverse map with object pathFrom', (t) => { const def = { mapping: { title: { path: 'content.heading' } @@ -112,7 +112,7 @@ test('should reverse map with object pathFrom', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with object pathTo', (t) => { +test.skip('should reverse map with object pathTo', (t) => { const def = { mapping: { title: { path: 'content.heading' } @@ -137,7 +137,7 @@ test('should reverse map with object pathTo', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with object pathRev and pathToRev', (t) => { +test.skip('should reverse map with object pathRev and pathToRev', (t) => { const def = { mapping: { title: { path: 'content.heading' } @@ -167,7 +167,7 @@ test('should reverse map with object pathRev and pathToRev', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with object pathFromRev', (t) => { +test.skip('should reverse map with object pathFromRev', (t) => { const def = { mapping: { title: { path: 'content.heading' } @@ -191,7 +191,7 @@ test('should reverse map with object pathFromRev', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with empty pathRev and pathToRev', (t) => { +test.skip('should reverse map with empty pathRev and pathToRev', (t) => { const def = { mapping: { title: { path: 'content.heading' } @@ -215,7 +215,7 @@ test('should reverse map with empty pathRev and pathToRev', (t) => { t.deepEqual(ret, expected) }) -test('should return data when no mapping def', (t) => { +test.skip('should return data when no mapping def', (t) => { const def = null const data = [ { content: { heading: 'Heading 1' } }, @@ -228,7 +228,7 @@ test('should return data when no mapping def', (t) => { t.deepEqual(ret, expected) }) -test('should return data when mapping def is empty', (t) => { +test.skip('should return data when mapping def is empty', (t) => { const def = {} const data = [ { content: { heading: 'Heading 1' } }, diff --git a/src/tests/object-transform-test.ts b/src/tests/object-transform-test.ts index e715437..cc6e5c4 100644 --- a/src/tests/object-transform-test.ts +++ b/src/tests/object-transform-test.ts @@ -27,7 +27,7 @@ const setAuthorName: TransformFunction = (item: {author: string}) => ({ // Tests -test('should map simple object with one transform function', (t) => { +test.skip('should map simple object with one transform function', (t) => { const def = { mapping: { title: 'content.heading', @@ -49,7 +49,7 @@ test('should map simple object with one transform function', (t) => { t.deepEqual(ret, expected) }) -test('should map simple object with array of transform functions', (t) => { +test.skip('should map simple object with array of transform functions', (t) => { const def = { mapping: { title: 'content.heading', @@ -72,7 +72,7 @@ test('should map simple object with array of transform functions', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map simple object with transformRev', (t) => { +test.skip('should reverse map simple object with transformRev', (t) => { const def = { mapping: { title: 'content.heading', @@ -96,7 +96,7 @@ test('should reverse map simple object with transformRev', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map simple object with transform rev props', (t) => { +test.skip('should reverse map simple object with transform rev props', (t) => { const def = { mapping: { title: 'content.heading', @@ -118,7 +118,7 @@ test('should reverse map simple object with transform rev props', (t) => { t.deepEqual(ret, expected) }) -test('should transform after path and before pathTo', (t) => { +test.skip('should transform after path and before pathTo', (t) => { const def = { mapping: { title: 'content.heading', @@ -148,7 +148,7 @@ test('should transform after path and before pathTo', (t) => { t.deepEqual(ret, expected) }) -test('should reverse transform after pathTo and before path', (t) => { +test.skip('should reverse transform after pathTo and before path', (t) => { const def = { mapping: { title: 'content.heading', @@ -180,7 +180,7 @@ test('should reverse transform after pathTo and before path', (t) => { t.deepEqual(ret, expected) }) -test('should transform with array', (t) => { +test.skip('should transform with array', (t) => { const def = { mapping: { title: 'content.heading' @@ -201,7 +201,7 @@ test('should transform with array', (t) => { t.deepEqual(ret, expected) }) -test('should reverse transform with array', (t) => { +test.skip('should reverse transform with array', (t) => { const def = { mapping: { title: 'content.heading', @@ -223,7 +223,7 @@ test('should reverse transform with array', (t) => { t.deepEqual(ret, expected) }) -test('should map with transformTo function', (t) => { +test.skip('should map with transformTo function', (t) => { const def = { mapping: { title: 'content.heading', @@ -245,7 +245,7 @@ test('should map with transformTo function', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with transformToRev', (t) => { +test.skip('should reverse map with transformToRev', (t) => { const def = { mapping: { title: 'content.heading', @@ -269,7 +269,7 @@ test('should reverse map with transformToRev', (t) => { t.deepEqual(ret, expected) }) -test('should map with transformFrom function', (t) => { +test.skip('should map with transformFrom function', (t) => { const def = { mapping: { title: 'content.heading', @@ -290,7 +290,7 @@ test('should map with transformFrom function', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with transformFrom function', (t) => { +test.skip('should reverse map with transformFrom function', (t) => { const def = { mapping: { title: 'content.heading' @@ -312,7 +312,7 @@ test('should reverse map with transformFrom function', (t) => { t.deepEqual(ret, expected) }) -test('should reverse map with transformFromRev function', (t) => { +test.skip('should reverse map with transformFromRev function', (t) => { const def = { mapping: { title: 'content.heading' diff --git a/src/tests/object-path-test.ts b/src/tests/path-test.ts similarity index 50% rename from src/tests/object-path-test.ts rename to src/tests/path-test.ts index 53454b7..00db4e4 100644 --- a/src/tests/object-path-test.ts +++ b/src/tests/path-test.ts @@ -1,14 +1,64 @@ import test from 'ava' -import * as mapTransform from '..' +import { mapTransform } from '..' test('should map with object path', (t) => { + const def = [ + 'content.article', + { + title: 'content.heading' + } + ] + const data = { + content: { + article: { + content: { heading: 'Heading 1' } + } + } + } + const expected = { title: 'Heading 1' } + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + +test('should map with object shape', (t) => { const def = { - mapping: { - title: { path: 'content.heading' } + attributes: { + title: 'content.heading', + text: 'content.copy' + }, + relationships: { + author: 'meta.writer.username' + } + } + const data = { + content: { heading: 'The heading', copy: 'A long text' }, + meta: { writer: { username: 'johnf' } } + } + const expected = { + attributes: { + title: 'The heading', + text: 'A long text' }, - path: 'content.articles' + relationships: { + author: 'johnf' + } } + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + +test('should map array with object path', (t) => { + const def = [ + 'content.articles', + { + title: 'content.heading' + } + ] const data = { content: { articles: [ @@ -28,12 +78,12 @@ test('should map with object path', (t) => { }) test('should map with object array path', (t) => { - const def = { - mapping: { - title: { path: 'content.heading' } - }, - path: 'content.articles[]' - } + const def = [ + 'content.articles[]', + { + title: 'content.heading' + } + ] const data = { content: { articles: [ @@ -52,80 +102,93 @@ test('should map with object array path', (t) => { t.deepEqual(ret, expected) }) -test('should map with root array path', (t) => { - const def = { - mapping: { - title: { path: 'content.heading' } - }, - path: '[]' - } - const data = [ - { content: { heading: 'Heading 1' } }, - { content: { heading: 'Heading 2' } } - ] - const expected = [ - { title: 'Heading 1' }, - { title: 'Heading 2' } +test('should map with array index path', (t) => { + const def = [ + 'content.articles[1]', + { + title: 'content.heading' + } ] + const data = { + content: { + articles: [ + { content: { heading: 'Heading 1' } }, + { content: { heading: 'Heading 2' } } + ] + } + } + const expected = { title: 'Heading 2' } const ret = mapTransform(def)(data) t.deepEqual(ret, expected) }) -test('should map data as is when no mapping', (t) => { - const def = { - path: 'content' - } +test('should map with array index in middle of path', (t) => { + const def = [ + 'content.articles[0].content.heading' + ] const data = { - content: { heading: 'The heading' } - } - const expected = { - heading: 'The heading' + content: { + articles: [ + { content: { heading: 'Heading 1' } }, + { content: { heading: 'Heading 2' } } + ] + } } + const expected = 'Heading 1' const ret = mapTransform(def)(data) t.deepEqual(ret, expected) }) -test('should map with pathFrom', (t) => { +test('should not map fields without paths', (t) => { const def = { - mapping: { - title: { path: 'content.heading' } - }, - pathFrom: 'content.articles' + title: null, + author: 'meta.writer.username' } const data = { - content: { - articles: [{ content: { heading: 'Heading 1' } }] - } + content: { heading: 'The heading' }, + meta: { writer: { username: 'johnf' } } + } + const expected = { + author: 'johnf' } - const expected = [{ title: 'Heading 1' }] const ret = mapTransform(def)(data) t.deepEqual(ret, expected) }) -test('should map with object pathTo', (t) => { - const def = { - mapping: { - title: { path: 'content.heading' } - }, - pathTo: 'content.articles' - } +test('should map with root array path', (t) => { + const def = [ + '[]', + { + title: 'content.heading' + } + ] const data = [ { content: { heading: 'Heading 1' } }, { content: { heading: 'Heading 2' } } ] + const expected = [ + { title: 'Heading 1' }, + { title: 'Heading 2' } + ] + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + +test('should map data as is when no mapping', (t) => { + const def = ['content'] + const data = { + content: { heading: 'The heading' } + } const expected = { - content: { - articles: [ - { title: 'Heading 1' }, - { title: 'Heading 2' } - ] - } + heading: 'The heading' } const ret = mapTransform(def)(data) @@ -133,12 +196,17 @@ test('should map with object pathTo', (t) => { t.deepEqual(ret, expected) }) -test('should map with array pathTo', (t) => { +test.todo('should split mappings with array path in the middle') + +test('should map with nested mappings', (t) => { const def = { - mapping: { - title: { path: 'content.heading' } - }, - pathTo: 'content.articles[].item' + content: { + 'articles[]': [ + { + title: 'content.heading' + } + ] + } } const data = [ { content: { heading: 'Heading 1' } }, @@ -147,8 +215,8 @@ test('should map with array pathTo', (t) => { const expected = { content: { articles: [ - { item: { title: 'Heading 1' } }, - { item: { title: 'Heading 2' } } + { title: 'Heading 1' }, + { title: 'Heading 2' } ] } } @@ -171,55 +239,57 @@ test('should return data when no mapping def', (t) => { t.deepEqual(ret, expected) }) -test('should return data when mapping def is empty', (t) => { +test('should return undefined when mapping def is empty object', (t) => { const def = {} const data = [ { content: { heading: 'Heading 1' } }, { content: { heading: 'Heading 2' } } ] - const expected = data const ret = mapTransform(def)(data) - t.deepEqual(ret, expected) + t.is(ret, undefined) }) -test('should return null when no data is given', (t) => { +test('should try to map even when no data is given', (t) => { const def = { - mapping: { - title: 'content.heading' - } + title: 'content.heading' + } + const expected = { + title: undefined } const ret = mapTransform(def)(null) - t.is(ret, null) + t.deepEqual(ret, expected) }) -test('should return null when path points to a non-existing prop', (t) => { - const def = { - mapping: { - title: 'title' - }, - path: 'missing' - } +test('should try to map even when path points to a non-existing prop', (t) => { + const def = [ + 'missing', + { title: 'title' } + ] const data = { content: { title: 'Entry 1' } } + const expected = { + title: undefined + } const ret = mapTransform(def)(data) - t.is(ret, null) + t.deepEqual(ret, expected) }) -test('should set empty data array on pathTo', (t) => { +test('should set empty data array', (t) => { const def = { - mapping: { - title: 'heading' - }, - pathTo: 'items[]' + 'items[]': [ + { + title: 'heading' + } + ] } const data: any[] = [] const expected = { diff --git a/src/tests/shape-test.ts b/src/tests/shape-test.ts deleted file mode 100644 index f7839fa..0000000 --- a/src/tests/shape-test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import test from 'ava' - -import mapTransform = require('..') - -test('should map with object shape', (t) => { - const def = { - mapping: { - attributes: { - title: 'content.heading', - text: 'content.copy' - }, - relationships: { - author: 'meta.writer.username' - } - } - } - const data = { - content: { heading: 'The heading', copy: 'A long text' }, - meta: { writer: { username: 'johnf' } } - } - const expected = { - attributes: { - title: 'The heading', - text: 'A long text' - }, - relationships: { - author: 'johnf' - } - } - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..0387b66 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,31 @@ +export interface ObjectWithProps { + [key: string]: Data +} + +type Prop = string | number | boolean | object | null | undefined | ObjectWithProps + +export type Data = Prop | Prop[] + +export interface State { + root: Data, + context: Data, + value: Data +} + +export type Path = string + +export interface MapFunction { + (state: State): State +} + +export type MapPipe = (MapFunction | Path | MapObject)[] + +export interface MapObject { + [key: string]: MapDefinition +} + +export type MapDefinition = MapObject | MapFunction | MapPipe | Path | null + +export interface DataMapper { + (data: Data): Data +} diff --git a/src/utils/createFieldMapper.ts b/src/utils/createFieldMapper.ts deleted file mode 100644 index 6b0bf78..0000000 --- a/src/utils/createFieldMapper.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as R from 'ramda' -import { Data } from '..' -import { lensPath } from './lensPath' -import { pipeTransform, pipeTransformRev, TransformFunction } from './transformPipeline' -import { MappingDefNormalized } from './normalizeMapping' - -export interface FieldMapperFunction { - (target: Data, data: Data | null): Data -} - -// Lens -> Lens -> (a -> b) -> (c -> (d -> c | d)) => (e -> e -> e) -const setFieldValue = (fromLens: R.Lens, toLens: R.Lens, transformFn: TransformFunction): FieldMapperFunction => (target, data) => { - const value = R.view(fromLens, data) - return (typeof value === 'undefined') ? target : R.set(toLens, transformFn(value), target) -} - -const setDefaultValue = (toLens: R.Lens, defaultValue: any): FieldMapperFunction => R.over(toLens, R.defaultTo(defaultValue)) - -/** - * Create an object with field mapper functions. The default mapper – mapping - * _from_ the source _to_ the target is at the `map` property, and the reverse - * mapper is at `mapRev`. - */ -export const createFieldMapper = (def: MappingDefNormalized) => { - const fromLens = lensPath(def.path) - const toLens = lensPath(def.pathTo) - const transformFn = pipeTransform(def.transform) - const transformRevFn = pipeTransformRev(def.transformRev, def.transform) - - return { - map: setFieldValue(fromLens, toLens, transformFn), - mapRev: setFieldValue(toLens, fromLens, transformRevFn), - complete: setDefaultValue(toLens, def.default), - completeRev: setDefaultValue(fromLens, def.defaultRev) - } -} diff --git a/src/utils/createMapper.ts b/src/utils/createMapper.ts deleted file mode 100644 index 172b23d..0000000 --- a/src/utils/createMapper.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Definition, DefinitionNormalized, Data } from '..' -import { lensPath } from './lensPath' -import { createObjectMapper } from './createObjectMapper' -import { createFieldMapper } from './createFieldMapper' -import { pipeTransform, pipeTransformRev } from './transformPipeline' -import { pipeFilter } from './filterPipeline' -import { normalizeMapping } from './normalizeMapping' - -interface BaseMapperFunction { - (data: Data | null): Data | null -} - -export interface MapperFunction extends BaseMapperFunction { - rev?: MapperFunction, - noDefaults?: BaseMapperFunction -} - -interface MapperFunctionWithNoDefaults extends BaseMapperFunction { - noDefaults: MapperFunction -} - -export interface MapperFunctionWithRev extends MapperFunctionWithNoDefaults { - rev: MapperFunctionWithNoDefaults -} - -const resolveAliases = (def: Definition): DefinitionNormalized => ({ - pathFrom: def.path || def.pathFrom, - pathFromRev: (typeof def.pathRev !== 'undefined') ? def.pathRev : def.pathFromRev, - filterFrom: def.filterFrom, - filterFromRev: def.filterFromRev, - transformFrom: def.transformFrom, - transformFromRev: def.transformFromRev, - mapping: def.mapping, - transformTo: def.transform || def.transformTo, - transformToRev: def.transformRev || def.transformToRev, - filterTo: def.filter || def.filterTo, - filterToRev: (typeof def.filterRev !== 'undefined') ? def.filterRev : def.filterToRev, - pathTo: def.pathTo, - pathToRev: def.pathToRev -}) - -const setupNormAndRev = (fn: (arg: U) => T, norm: U, rev: U): [T, T] => { - const normRes = fn(norm) - const revRes = (typeof rev !== 'undefined') ? fn(rev) : normRes - return [normRes, revRes] -} - -export const createMapper = (def: Definition): MapperFunctionWithRev => { - const d = resolveAliases(def) - - const [pathFromLens, pathFromRevLens] = setupNormAndRev(lensPath, d.pathFrom, d.pathFromRev) - const [filterFromFn, filterFromRevFn] = setupNormAndRev(pipeFilter, d.filterFrom, d.filterFromRev) - - const transformFromFn = pipeTransform(d.transformFrom) - const transformFromRevFn = pipeTransformRev(d.transformFromRev, d.transformFrom) - - const fieldMappers = (d.mapping) ? normalizeMapping(d.mapping).map(createFieldMapper) : [] - - const transformToFn = pipeTransform(d.transformTo) - const transformToRevFn = pipeTransformRev(d.transformToRev, d.transformTo) - - const [filterToFn, filterToRevFn] = setupNormAndRev(pipeFilter, d.filterTo, d.filterToRev) - const [pathToLens, pathToRevLens] = setupNormAndRev(lensPath, d.pathTo, d.pathToRev) - - const createMapper = createObjectMapper( - pathFromLens, - filterFromFn, - transformFromFn, - fieldMappers, - transformToFn, - filterToFn, - pathToLens, - false // isRev - ) - - const createRevMapper = createObjectMapper( - pathToRevLens, - filterToRevFn, - transformFromRevFn, - fieldMappers, - transformToRevFn, - filterFromRevFn, - pathFromRevLens, - true // isRev - ) - - return Object.assign(createMapper(), { - noDefaults: createMapper(true), - rev: Object.assign(createRevMapper(), { - noDefaults: createRevMapper(true) - }) - }) -} diff --git a/src/utils/createObjectMapper.ts b/src/utils/createObjectMapper.ts deleted file mode 100644 index 22ea12d..0000000 --- a/src/utils/createObjectMapper.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as R from 'ramda' -import * as mapAny from 'map-any' -import { MapperFunction } from './createMapper' -import { TransformFunction } from './transformPipeline' -import { FieldMapperFunction } from './createFieldMapper' -import { FilterFunction } from './filterPipeline' - -const _ = (R as any).__ - -const filterObj = (filterFn: FilterFunction, x: any) => - (filterFn(x)) ? x : null - -const filterAny = (filterFn: FilterFunction) => (x: any) => - (x && typeof x.filter === 'function') ? x.filter(filterFn) : filterObj(filterFn, x) - -// Lens -> (a -> b) -// Note: Setting a path on null, will return the result from the set operation -// without setting it on any object. This is useful as we don't know whether -// we'll get an object or an array. It does not comply with the Ramda spec, -// however, hence the `as any` casting. Maybe it would be better to bypass the -// Ramda lens altogether, and use the getter and setter methods directly. -const setAtObjectPath = (lens: R.Lens): MapperFunction => R.set(lens, _, null) as any - -// [(a -> a -> a)] -> g a -const reduceMapperFns = (mapperFns: FieldMapperFunction[]): MapperFunction => - (data) => (R.isNil(data)) - ? null - : mapperFns.reduce((target, fn) => fn(target, data), {}) - -// Lens -> (a -> a) -const getFromObjectPath = (lens: R.Lens): MapperFunction => R.view(lens) - -const getMapProp = (rev: boolean) => (rev) ? 'mapRev' : 'map' -const getCompleteProp = (rev: boolean) => (rev) ? 'completeRev' : 'complete' - -const mapFieldsOrPassObject = (rev: boolean, noDefaults: boolean) => R.ifElse( - R.isEmpty, - R.always([R.nthArg(1)]), - R.map((mapper: any) => (noDefaults) - ? mapper[getMapProp(rev)] - : R.compose(mapper[getCompleteProp(rev)], mapper[getMapProp(rev)])) -) - -const mapObject = ( - rev: boolean, - noDefaults: boolean, - fieldMappers: object, - transformFromFn: TransformFunction, - transformToFn: TransformFunction -) => { - const createFieldMappers = R.compose( - reduceMapperFns, - mapFieldsOrPassObject(rev, noDefaults) - ) - - return (rev) - ? R.compose(transformFromFn, createFieldMappers(fieldMappers), transformToFn) - : R.compose(transformToFn, createFieldMappers(fieldMappers), transformFromFn) -} - -export const createObjectMapper = ( - pathFromLens: R.Lens, - filterFromFn: R.Pred, - transformFromFn: TransformFunction, - fieldMappers: object, - transformToFn: TransformFunction, - filterToFn: R.Pred, - pathToLens: R.Lens, - isRev: boolean -) => (noDefaults = false): MapperFunction => R.compose( - setAtObjectPath(pathToLens), - filterAny(filterToFn), - mapAny(mapObject(isRev, noDefaults, fieldMappers, transformFromFn, transformToFn)), - filterAny(filterFromFn), - getFromObjectPath(pathFromLens) - ) diff --git a/src/utils/definitionHelpers.ts b/src/utils/definitionHelpers.ts new file mode 100644 index 0000000..bc70f90 --- /dev/null +++ b/src/utils/definitionHelpers.ts @@ -0,0 +1,16 @@ +import { identity } from 'ramda' +import { MapFunction, MapDefinition, MapObject, Path, MapPipe } from '../types' +import get from '../funcs/get' +import mutate from '../funcs/mutate' +import pipe from '../funcs/pipe' + +export const isPath = (def: MapDefinition): def is Path => typeof def === 'string' +export const isMapObject = (def: MapDefinition): def is MapObject => def !== null && typeof def === 'object' && !isMapPipe(def) +export const isMapPipe = (def: MapDefinition): def is MapPipe => Array.isArray(def) + +export const mapFunctionFromDef = (def: MapDefinition): MapFunction => + (def === null) ? identity + : isPath(def) ? get(def) + : isMapObject(def) ? mutate(def) + : isMapPipe(def) ? pipe(def) + : def diff --git a/src/utils/lensPath.ts b/src/utils/lensPath.ts deleted file mode 100644 index 6adec57..0000000 --- a/src/utils/lensPath.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as R from 'ramda' -import pathGetter from './pathGetter' -import pathSetter from './pathSetter' - -export type PathString = string - -export const empty = R.lens(R.identity, R.identity) - -/** - * Take a path string and return a Ramda lens. If path is null or undefined, an - * empty lens (that always returns the value given to it) is returned. - * - * Example paths: - * - `'content.heading'` - * - `'data.items[0].id'` - * - * @param {string} path - A path string in dot notation - * @returns {Lens} A Ramda lens - */ -export const lensPath = (path?: PathString | null): R.Lens => - (path) ? R.lens(pathGetter(path), pathSetter(path)) : empty diff --git a/src/utils/normalizeMapping-test.ts b/src/utils/normalizeMapping-test.ts deleted file mode 100644 index 516d07f..0000000 --- a/src/utils/normalizeMapping-test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import test from 'ava' - -import { normalizeMapping } from './normalizeMapping' - -test('should normalize to mapping objects', (t) => { - const mapping = { - title: { path: 'content.heading' }, - author: { path: 'meta.writer.username' } - } - const expected = [ - { pathTo: 'title', path: 'content.heading' }, - { pathTo: 'author', path: 'meta.writer.username' } - ] - - const ret = normalizeMapping(mapping) - - t.deepEqual(ret, expected) -}) - -test('should normalize from path shortcut', (t) => { - const mapping = { - title: 'content.heading', - author: 'meta.writer.username' - } - const expected = [ - { pathTo: 'title', path: 'content.heading' }, - { pathTo: 'author', path: 'meta.writer.username' } - ] - - const ret = normalizeMapping(mapping) - - t.deepEqual(ret, expected) -}) - -test('should skip fields with no definition', (t) => { - const mapping = { - title: null - } - const expected: any[] = [] - - const ret = normalizeMapping(mapping) - - t.deepEqual(ret, expected) -}) - -test('should skip fields with no pathTo', (t) => { - const mapping = { - '': 'content.heading' - } - const expected: any[] = [] - - const ret = normalizeMapping(mapping) - - t.deepEqual(ret, expected) -}) - -test('should normalize from object shape', (t) => { - const mapping = { - attributes: { - title: 'content.heading', - text: 'content.copy', - deeper: { - 'with.path': 'id' - } - }, - relationships: { - author: 'meta.writer.username' - } - } - const expected = [ - { pathTo: 'attributes.title', path: 'content.heading' }, - { pathTo: 'attributes.text', path: 'content.copy' }, - { pathTo: 'attributes.deeper.with.path', path: 'id' }, - { pathTo: 'relationships.author', path: 'meta.writer.username' } - ] - - const ret = normalizeMapping(mapping) - - t.deepEqual(ret, expected) -}) - -test('should normalize array of mapping objects', (t) => { - const mapping = [ - { pathTo: 'title', path: 'content.heading' }, - { pathTo: 'author', path: 'meta.writer.username' } - ] - const expected = [ - { pathTo: 'title', path: 'content.heading' }, - { pathTo: 'author', path: 'meta.writer.username' } - ] - - const ret = normalizeMapping(mapping) - - t.deepEqual(ret, expected) -}) - -test('should normalize mapping object', (t) => { - const transform = (value: string) => value + ' norm' - const transformRev = (value: string) => value + ' rev' - const mapping = [ - { - pathTo: 'title', - path: 'heading', - transform, - transformRev, - default: 'Untitled', - defaultRev: 'Titled after all' - } - ] - const expected = [ - { - pathTo: 'title', - path: 'heading', - transform, - transformRev, - default: 'Untitled', - defaultRev: 'Titled after all' - } - ] - - const ret = normalizeMapping(mapping) - - t.deepEqual(ret, expected) -}) diff --git a/src/utils/normalizeMapping.ts b/src/utils/normalizeMapping.ts deleted file mode 100644 index e3b4f14..0000000 --- a/src/utils/normalizeMapping.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Shape } from '..' -import { PathString } from './lensPath' -import { TransformPipeline } from './transformPipeline' - -interface MappingDefBase { - path: PathString | null, - transform?: TransformPipeline, - transformRev?: TransformPipeline, - default?: any, - defaultRev?: any -} - -export interface MappingDefNormalized extends MappingDefBase { - pathTo: PathString | null, -} - -export interface MappingDef extends MappingDefBase { - pathTo?: PathString | null, -} - -const normalize = (def: MappingDef): MappingDefNormalized => - ({ ...def, path: def.path || null, pathTo: def.pathTo || null }) - -const normalizeDef = (pathArr: PathString[], def: MappingDef | PathString): - MappingDefNormalized => - (typeof def === 'string') - ? normalize({ path: def, pathTo: pathArr.join('.') }) - : normalize({ ...def, pathTo: pathArr.join('.') }) - -const isMappingDef = (obj: Shape | PathString | MappingDef | null) => - obj && typeof obj === 'object' && typeof (obj as MappingDef).path !== 'string' - -const normalizeShape = (mapping: Shape, pathTo: string[] = []): - MappingDefNormalized[] => - Object.keys(mapping).reduce((arr: MappingDefNormalized[], key: PathString) => - (isMappingDef(mapping[key])) - ? [ ...arr, ...normalizeShape(mapping[key] as Shape, [...pathTo, key]) ] - : [ ...arr, normalizeDef([...pathTo, key], mapping[key] as MappingDef)] - , [] -) - -export function normalizeMapping (mapping: Shape | MappingDef[]): - MappingDefNormalized[] { - const normalized = (Array.isArray(mapping)) - ? mapping.map(normalize) - : normalizeShape(mapping) - return normalized.filter((m) => m.path !== null && m.pathTo !== null) -} diff --git a/src/utils/pathGetter-test.ts b/src/utils/pathGetter-test.ts index 3bc149c..9776ddb 100644 --- a/src/utils/pathGetter-test.ts +++ b/src/utils/pathGetter-test.ts @@ -76,12 +76,12 @@ test('should return object when empty path', (t) => { t.deepEqual(ret, object) }) -test('should return null when object is null', (t) => { +test('should return undefined when object is null', (t) => { const path = 'meta.author' const ret = pathGetter(path)(null) - t.is(ret, null) + t.is(ret, undefined) }) test('should return undefined when path is not found', (t) => { diff --git a/src/utils/pathGetter.ts b/src/utils/pathGetter.ts index f8b1848..a9f03cb 100644 --- a/src/utils/pathGetter.ts +++ b/src/utils/pathGetter.ts @@ -1,17 +1,16 @@ import * as R from 'ramda' import * as mapAny from 'map-any' -import { Data, DataWithProps } from '..' -import { PathString } from './lensPath' +import { Data, ObjectWithProps, Path } from '../types' const numberOrString = (val: string): string | number => { const num = Number.parseInt(val, 10) return (Number.isNaN(num)) ? val : num } -const split = (str: PathString): (string | number)[] => +const split = (str: Path): (string | number)[] => str.split(/\[|]?\.|]/).filter((str) => str !== '').map(numberOrString) -const getProp = (prop: string) => (object?: DataWithProps) => +const getProp = (prop: string) => (object?: ObjectWithProps) => (object) ? object[prop] : undefined const getArrayIndex = (index: number) => (arr?: Data) => @@ -41,12 +40,8 @@ type GetFunction = (object?: Data | null) => Data * @param {string} path - The path to get * @returns {function} A function accepting an object to get the value from. */ -export default function pathGetter (path: PathString | null): GetFunction { +export default function pathGetter (path: Path | null): GetFunction { return (path) - ? R.ifElse( - R.isNil, - R.identity, - getGetters(path) - ) + ? getGetters(path) : R.identity } diff --git a/src/utils/pathSetter.ts b/src/utils/pathSetter.ts index e535443..ca2c9ed 100644 --- a/src/utils/pathSetter.ts +++ b/src/utils/pathSetter.ts @@ -1,11 +1,10 @@ import * as R from 'ramda' -import { Data } from '..' -import { PathString } from './lensPath' +import { Data, Path } from '../types' const preparePathPart = (part: string, isAfterOpenArray: boolean) => (isAfterOpenArray) ? `*${part}` : part -const pathSplitter = function* (path: PathString) { +const pathSplitter = function* (path: Path) { const regEx = /([^[\].]+|\[\w*])/g let match let isAfterOpenArray = false @@ -18,7 +17,7 @@ const pathSplitter = function* (path: PathString) { } while (match !== null) } -const split = (path: PathString): string[] => [...pathSplitter(path)] +const split = (path: Path): string[] => [...pathSplitter(path)] const setOnObject = (prop: string) => (value: Data): Data => ({ [prop]: value }) @@ -71,7 +70,7 @@ type SetFunction = (value: Data, object: Data | null) => Data * @param {string} path - The path to set the value at * @returns {function} A setter function accepting a value and a target object */ -export default function pathSetter (path: PathString): SetFunction { +export default function pathSetter (path: Path): SetFunction { const setters = getSetters(path) return (value, object) => { diff --git a/src/utils/stateHelpers.ts b/src/utils/stateHelpers.ts new file mode 100644 index 0000000..a9a73e3 --- /dev/null +++ b/src/utils/stateHelpers.ts @@ -0,0 +1,24 @@ +import { State, MapFunction, Data } from '../types' + +export const setValue = (state: State, value: Data): State => ({ ...state, value }) +export const getValue = (state: State): Data => state.value + +export const setValueFromState = (state: State, { value }: State) => ({ + ...state, + value +}) + +export const shiftState = (state: State) => ({ + ...state, + context: state.value, + value: undefined +}) + +export const pipeMapFns = (fns: MapFunction[]) => (state: State): State => + fns.reduce((state: State, fn: MapFunction) => fn(state), state) + +export const populateState = (data: Data): State => ({ + root: data, + context: data, + value: data +}) From 7342902b8741100ad67d01742da92f7ee0261c58 Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Tue, 7 Aug 2018 22:22:07 +0200 Subject: [PATCH 03/13] Implement transform and filter for the pipeline approach --- src/funcs/filter-test.ts | 67 +++++++ src/funcs/filter.ts | 27 +++ src/funcs/transform-test.ts | 38 ++++ src/funcs/transform.ts | 20 +++ src/index.ts | 3 + src/tests/field-transform-test.ts | 165 ----------------- src/tests/filter-test.ts | 138 +++++---------- ...ct-transform-test.ts => transform-test.ts} | 166 +++++++++--------- src/utils/filterPipeline.ts | 11 -- src/utils/transformPipeline.ts | 39 ---- 10 files changed, 275 insertions(+), 399 deletions(-) create mode 100644 src/funcs/filter-test.ts create mode 100644 src/funcs/filter.ts create mode 100644 src/funcs/transform-test.ts create mode 100644 src/funcs/transform.ts delete mode 100644 src/tests/field-transform-test.ts rename src/tests/{object-transform-test.ts => transform-test.ts} (69%) delete mode 100644 src/utils/filterPipeline.ts delete mode 100644 src/utils/transformPipeline.ts diff --git a/src/funcs/filter-test.ts b/src/funcs/filter-test.ts new file mode 100644 index 0000000..35d8084 --- /dev/null +++ b/src/funcs/filter-test.ts @@ -0,0 +1,67 @@ +import test from 'ava' + +import filter, { FilterFunction } from './filter' + +// Helpers + +const beginsWithA: FilterFunction = (str) => (typeof str === 'string') ? str.startsWith('A') : false + +// Tests + +test('should set value to undefined when filter returns false', (t) => { + const state = { + root: { title: 'Other entry' }, + context: { title: 'Other entry' }, + value: 'Other entry' + } + const expected = { + root: { title: 'Other entry' }, + context: { title: 'Other entry' }, + value: undefined + } + + const ret = filter(beginsWithA)(state) + + t.deepEqual(ret, expected) +}) + +test('should not touch value when filter returns true', (t) => { + const state = { + root: { title: 'An entry' }, + context: { title: 'An entry' }, + value: 'An entry' + } + + const ret = filter(beginsWithA)(state) + + t.deepEqual(ret, state) +}) + +test('should remove values in array when filter returns false', (t) => { + const state = { + root: { users: ['John F', 'Andy'] }, + context: { users: ['John F', 'Andy'] }, + value: ['John F', 'Andy'] + } + const expected = { + root: { users: ['John F', 'Andy'] }, + context: { users: ['John F', 'Andy'] }, + value: ['Andy'] + } + + const ret = filter(beginsWithA)(state) + + t.deepEqual(ret, expected) +}) + +test('should not touch value when filter is not a function', (t) => { + const state = { + root: { title: 'An entry' }, + context: { title: 'An entry' }, + value: 'An entry' + } + + const ret = filter('notallowed' as any)(state) + + t.deepEqual(ret, state) +}) diff --git a/src/funcs/filter.ts b/src/funcs/filter.ts new file mode 100644 index 0000000..2098701 --- /dev/null +++ b/src/funcs/filter.ts @@ -0,0 +1,27 @@ +import { compose, unless, always, ifElse, filter as filterR, identity } from 'ramda' +import { MapFunction, Data, State } from '../types' +import { getValue } from '../utils/stateHelpers' + +export interface FilterFunction { + (data: Data): boolean +} + +export default function filter (fn: FilterFunction): MapFunction { + if (typeof fn !== 'function') { + return identity + } + + const runFilter = compose( + ifElse( + Array.isArray, + filterR(fn), + unless(fn, always(undefined)) + ), + getValue + ) + + return (state: State) => ({ + ...state, + value: runFilter(state) + }) +} diff --git a/src/funcs/transform-test.ts b/src/funcs/transform-test.ts new file mode 100644 index 0000000..03ac72d --- /dev/null +++ b/src/funcs/transform-test.ts @@ -0,0 +1,38 @@ +import test from 'ava' + +import transform, { TransformFunction } from './transform' + +// Helpers + +const upper: TransformFunction = (str) => (typeof str === 'string') ? str.toUpperCase() : str + +// Tests + +test('should run transform function on value', (t) => { + const state = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'Entry 1' + } + const expected = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'ENTRY 1' + } + + const ret = transform(upper)(state) + + t.deepEqual(ret, expected) +}) + +test('should not touch value run with anything else than a function', (t) => { + const state = { + root: {}, + context: {}, + value: 'Entry 1' + } + + const ret = transform('wrong' as any)(state) + + t.deepEqual(ret, state) +}) diff --git a/src/funcs/transform.ts b/src/funcs/transform.ts new file mode 100644 index 0000000..c376014 --- /dev/null +++ b/src/funcs/transform.ts @@ -0,0 +1,20 @@ +import { compose, identity } from 'ramda' +import { Data, MapFunction, State } from '../types' +import { getValue } from '../utils/stateHelpers' + +export interface TransformFunction { + (data: Data): Data +} + +export default function transform (fn: TransformFunction): MapFunction { + if (typeof fn !== 'function') { + return identity + } + + const runTransform = compose(fn, getValue) + + return (state: State) => ({ + ...state, + value: runTransform(state) + }) +} diff --git a/src/index.ts b/src/index.ts index 4ec04d5..3dbcee8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,9 @@ import { populateState, getValue } from './utils/stateHelpers' export { default as alt } from './funcs/alt' export { default as value } from './funcs/value' +export { default as transform, TransformFunction } from './funcs/transform' +export { default as filter, FilterFunction } from './funcs/filter' +export { Data } from './types' export function mapTransform (def: MapDefinition): DataMapper { return compose( diff --git a/src/tests/field-transform-test.ts b/src/tests/field-transform-test.ts deleted file mode 100644 index 2be8efe..0000000 --- a/src/tests/field-transform-test.ts +++ /dev/null @@ -1,165 +0,0 @@ -import test from 'ava' -import { TransformFunction } from '../utils/transformPipeline' - -import * as mapTransform from '..' - -// Helpers - -const appendEllipsis: TransformFunction = (str: string) => str + ' ...' -appendEllipsis.rev = (str: string) => - (str.endsWith(' ...')) ? str.substr(0, str.length - 4) : str -const upperCase: TransformFunction = (str: string) => str.toUpperCase() -const getLength: TransformFunction = (str: string) => str.length -const enclose: TransformFunction = (str: string) => `(${str})` -enclose.rev = (str: string) => (str.startsWith('(') && str.endsWith(')')) - ? str.substr(1, str.length - 2) : str - -// Tests - -test.skip('should map field with one transform function', (t) => { - const def = { - mapping: { - title: { - path: 'content.heading', - transform: appendEllipsis - } - } - } - const data = { - content: { heading: 'The heading' } - } - const expected = { - title: 'The heading ...' - } - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should map field with array of transform functions', (t) => { - const def = { - mapping: { - title: { - path: 'content.heading', - transform: [ appendEllipsis, upperCase ] - } - } - } - const data = { - content: { heading: 'The heading' } - } - const expected = { - title: 'THE HEADING ...' - } - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should apply transform functions from left to right', (t) => { - const def = { - mapping: { - titleLength: { - path: 'content.heading', - transform: [ appendEllipsis, getLength ] - } - } - } - const data = { - content: { heading: 'The heading' } - } - const expected = { - titleLength: 15 - } - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should not fail with empty transform array', (t) => { - const def = { - mapping: { - title: { - path: 'content.heading', - transform: [] - } - } - } - const data = { - content: { heading: 'The heading' } - } - const expected = { - title: 'The heading' - } - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should reverse map with transform functions from transformRev', (t) => { - const def = { - mapping: { - title: { - path: 'content.heading', - transform: [ upperCase ], - transformRev: [ getLength, appendEllipsis ] - } - } - } - const data = { - title: 'The heading' - } - const expected = { - content: { heading: '11 ...' } - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should reverse map with transform function from rev props', (t) => { - const def = { - mapping: { - title: { - path: 'content.heading', - transform: appendEllipsis - } - } - } - const data = { - title: 'The heading ...' - } - const expected = { - content: { heading: 'The heading' } - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should reverse map with several transform functions from rev props', (t) => { - const def = { - mapping: { - title: { - path: 'content.heading', - transform: [ appendEllipsis, upperCase, enclose ] - } - } - } - const data = { - title: '(The heading ...)' - } - const expected = { - content: { heading: 'The heading' } - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) diff --git a/src/tests/filter-test.ts b/src/tests/filter-test.ts index 5ff68b5..392b483 100644 --- a/src/tests/filter-test.ts +++ b/src/tests/filter-test.ts @@ -1,42 +1,42 @@ import test from 'ava' -import { FilterFunction } from '../utils/filterPipeline' -import * as mapTransform from '..' +import { mapTransform, filter, FilterFunction, Data } from '..' // Helpers -const noHeading: FilterFunction = (item: {title: string}) => - !(/heading/gi).test(item.title) +const isObject = (item: Data): item is object => (!!item && typeof item === 'object') -const noAlso: FilterFunction = (item: {title: string}) => - !(/also/gi).test(item.title) +const noHeading: FilterFunction = (item) => + (isObject(item)) && !(/heading/gi).test((item as any).title) + +const noAlso: FilterFunction = (item) => (isObject(item)) && !(/also/gi).test((item as any).title) // Tests -test.skip('should filter out item', (t) => { - const def = { - mapping: { +test('should filter out item', (t) => { + const def = [ + { title: 'content.heading' }, - filter: noHeading - } + filter(noHeading) + ] const data = { content: { heading: 'The heading' } } - const expected = null + const expected = undefined const ret = mapTransform(def)(data) t.deepEqual(ret, expected) }) -test.skip('should filter out items in array', (t) => { - const def = { - mapping: { +test('should filter out items in array', (t) => { + const def = [ + { title: 'content.heading' }, - filter: noHeading - } + filter(noHeading) + ] const data = [ { content: { heading: 'The heading' } }, { content: { heading: 'Just this' } }, @@ -51,13 +51,14 @@ test.skip('should filter out items in array', (t) => { t.deepEqual(ret, expected) }) -test.skip('should filter with array of filters', (t) => { - const def = { - mapping: { +test('should filter with several filters', (t) => { + const def = [ + { title: 'content.heading' }, - filter: [ noHeading, noAlso ] - } + filter(noHeading), + filter(noAlso) + ] const data = [ { content: { heading: 'The heading' } }, { content: { heading: 'Just this' } }, @@ -73,34 +74,14 @@ test.skip('should filter with array of filters', (t) => { t.deepEqual(ret, expected) }) -test.skip('should keep all when filter is empty array', (t) => { +test('should set filtered items on path', (t) => { const def = { - mapping: { - title: 'content.heading' - }, - filter: [] - } - const data = [ - { content: { heading: 'The heading' } }, - { content: { heading: 'Just this' } } - ] - const expected = [ - { title: 'The heading' }, - { title: 'Just this' } - ] - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should set filtered items on pathTo', (t) => { - const def = { - mapping: { - title: 'content.heading' - }, - filter: noHeading, - pathTo: 'items[]' + 'items[]': [ + { + title: 'content.heading' + }, + filter(noHeading) + ] } const data = [ { content: { heading: 'The heading' } }, @@ -140,23 +121,6 @@ test.skip('should filter items from pathTo for reverse mapping', (t) => { t.deepEqual(ret, expected) }) -test.skip('should filter with filterTo', (t) => { - const def = { - mapping: { - title: 'content.heading' - }, - filterTo: noHeading - } - const data = { - content: { heading: 'The heading' } - } - const expected = null - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - test.skip('should filter on reverse mapping', (t) => { const def = { mapping: { @@ -203,42 +167,20 @@ test.skip('should filter with filterRev on reverse mapping', (t) => { t.deepEqual(ret, expected) }) -test.skip('should filter with filterToRev on reverse mapping', (t) => { - const def = { - mapping: { - title: 'content.heading' - }, - filterTo: [ noHeading, noAlso ], - filterToRev: [ noHeading ] - } - const data = [ - { title: 'The heading' }, - { title: 'Just this' }, - { title: 'Another heading' }, - { title: 'Also this' } - ] - const expected = [ - { content: { heading: 'Just this' } }, - { content: { heading: 'Also this' } } - ] - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should filter with filterFrom', (t) => { - const def = { - mapping: { +test('should filter before mapping', (t) => { + const def = [ + 'content', + filter(noHeading), + { heading: 'title' - }, - pathFrom: 'content', - filterFrom: noHeading - } + } + ] const data = { content: { title: 'The heading' } } - const expected = null + const expected = { + heading: undefined + } const ret = mapTransform(def)(data) diff --git a/src/tests/object-transform-test.ts b/src/tests/transform-test.ts similarity index 69% rename from src/tests/object-transform-test.ts rename to src/tests/transform-test.ts index cc6e5c4..0c6de36 100644 --- a/src/tests/object-transform-test.ts +++ b/src/tests/transform-test.ts @@ -1,40 +1,56 @@ import test from 'ava' -import { TransformFunction } from '../utils/transformPipeline' -import mapTransform = require('..') +import { mapTransform, transform, TransformFunction, Data } from '..' // Helpers -const appendAuthorToTitle: TransformFunction = - (item: {title: string, author: string}) => - ({ ...item, title: `${item.title} - by ${item.author}` }) -appendAuthorToTitle.rev = (item: {title: string, author: string}) => - ({ - ...item, - title: (item.title.endsWith(` - by ${item.author}`)) - ? item.title.substr(0, item.title.length - 6 - item.author.length) - : item.title - }) - -const setActive: TransformFunction = (item: {}) => - ({ ...item, active: true }) -setActive.rev = (item: {}) => ({ ...item, active: false }) +const isObject = (item: Data): item is object => (!!item && typeof item === 'object') + +const createTitle = (item: { title: string, author: string }) => `${item.title} - by ${item.author}` + +const appendAuthorToTitle: TransformFunction = (item) => (isObject(item)) + ? { ...item, title: createTitle(item as any) } + : item + +// appendAuthorToTitle.rev = (item: {title: string, author: string}) => +// ({ +// ...item, +// title: (item.title.endsWith(` - by ${item.author}`)) +// ? item.title.substr(0, item.title.length - 6 - item.author.length) +// : item.title +// }) + +const setActive: TransformFunction = (item) => (isObject(item)) + ? { ...item, active: true } + : item + +// setActive.rev = (item: {}) => ({ ...item, active: false }) const setAuthorName: TransformFunction = (item: {author: string}) => ({ ...item, authorName: `${item.author[0].toUpperCase()}${item.author.substr(1)}.` }) +const appendEllipsis: TransformFunction = (str) => (typeof str === 'string') ? str + ' ...' : str +// appendEllipsis.rev = (str: string) => +// (str.endsWith(' ...')) ? str.substr(0, str.length - 4) : str +// const upperCase: TransformFunction = (str: string) => str.toUpperCase() +const getLength: TransformFunction = (str) => (typeof str === 'string') ? str.length : 0 +// const enclose: TransformFunction = (str: string) => `(${str})` +// enclose.rev = (str: string) => (str.startsWith('(') && str.endsWith(')')) +// ? str.substr(1, str.length - 2) : str + + // Tests -test.skip('should map simple object with one transform function', (t) => { - const def = { - mapping: { +test('should map simple object with one transform function', (t) => { + const def = [ + { title: 'content.heading', author: 'meta.writer.username' }, - transform: appendAuthorToTitle - } + transform(appendAuthorToTitle) + ] const data = { content: { heading: 'The heading' }, meta: { writer: { username: 'johnf' } } @@ -49,14 +65,15 @@ test.skip('should map simple object with one transform function', (t) => { t.deepEqual(ret, expected) }) -test.skip('should map simple object with array of transform functions', (t) => { - const def = { - mapping: { +test('should map simple object with several transform functions', (t) => { + const def = [ + { title: 'content.heading', author: 'meta.writer.username' }, - transform: [ appendAuthorToTitle, setActive ] - } + transform(appendAuthorToTitle), + transform(setActive) + ] const data = { content: { heading: 'The heading' }, meta: { writer: { username: 'johnf' } } @@ -118,15 +135,16 @@ test.skip('should reverse map simple object with transform rev props', (t) => { t.deepEqual(ret, expected) }) -test.skip('should transform after path and before pathTo', (t) => { +test('should transform beofre data is set on outer path', (t) => { const def = { - mapping: { - title: 'content.heading', - author: 'meta.writer.username' - }, - transform: appendAuthorToTitle, - path: 'result.data', - pathTo: 'attributes' + attributes: [ + 'result.data', + { + title: 'content.heading', + author: 'meta.writer.username' + }, + transform(appendAuthorToTitle) + ] } const data = { result: { @@ -223,60 +241,14 @@ test.skip('should reverse transform with array', (t) => { t.deepEqual(ret, expected) }) -test.skip('should map with transformTo function', (t) => { - const def = { - mapping: { - title: 'content.heading', - author: 'meta.writer.username' - }, - transformTo: appendAuthorToTitle - } - const data = { - content: { heading: 'The heading' }, - meta: { writer: { username: 'johnf' } } - } - const expected = { - title: 'The heading - by johnf', - author: 'johnf' - } - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should reverse map with transformToRev', (t) => { - const def = { - mapping: { - title: 'content.heading', - author: 'meta.writer.username', - authorName: 'meta.writer.name' - }, - transformTo: [ appendAuthorToTitle, setActive ], - transformToRev: [ setAuthorName ] - } - const data = { - title: 'The heading', - author: 'johnf' - } - const expected = { - content: { heading: 'The heading' }, - meta: { writer: { username: 'johnf', name: 'Johnf.' } } - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should map with transformFrom function', (t) => { - const def = { - mapping: { +test('should transform before mapping', (t) => { + const def = [ + transform(setActive), + { title: 'content.heading', enabled: 'active' - }, - transformFrom: setActive - } + } + ] const data = { content: { heading: 'The heading' } } @@ -334,3 +306,25 @@ test.skip('should reverse map with transformFromRev function', (t) => { t.deepEqual(ret, expected) }) + +test('should apply transform functions from left to right', (t) => { + const def = [ + { + titleLength: [ + 'content.heading', + transform(appendEllipsis), + transform(getLength) + ] + } + ] + const data = { + content: { heading: 'The heading' } + } + const expected = { + titleLength: 15 + } + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) diff --git a/src/utils/filterPipeline.ts b/src/utils/filterPipeline.ts deleted file mode 100644 index 21d5c50..0000000 --- a/src/utils/filterPipeline.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as R from 'ramda' - -export interface FilterFunction { - (value: any): boolean -} - -export type FilterPipeline = FilterFunction | FilterFunction[] - -export const pipeFilter = (filters?: FilterPipeline) => (Array.isArray(filters)) - ? (filters.length > 0) ? R.allPass(filters) : R.T - : filters || R.T diff --git a/src/utils/transformPipeline.ts b/src/utils/transformPipeline.ts deleted file mode 100644 index c9c1c12..0000000 --- a/src/utils/transformPipeline.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as R from 'ramda' - -export interface BaseTransformFunction { - (value: any): any -} -export interface TransformFunction extends BaseTransformFunction { - rev?: BaseTransformFunction -} - -export type TransformPipeline = TransformFunction | TransformFunction[] - -const extractRev = (fn: TransformFunction) => fn && fn.rev || undefined - -const extractRevArray = R.pipe( - R.map(extractRev), - R.filter(R.complement(R.isNil)), - (arr) => arr.reverse() -) - -const extractRevPipeline = R.ifElse( - Array.isArray, - extractRevArray, - extractRev -) - -export const pipeTransform = ( - transform?: TransformPipeline -): TransformFunction => - (Array.isArray(transform)) - ? ((transform.length > 0) ? R.call(R.pipe, ...transform) : R.identity) - : (transform || R.identity) - -export const pipeTransformRev = ( - transformRev?: TransformPipeline, - transform?: TransformPipeline -): TransformFunction => - (transformRev || !transform) - ? pipeTransform(transformRev) - : pipeTransform(extractRevPipeline(transform)) From 16298ca16b1b6fd33a6e3b5c38e1139b8ef7a0d8 Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Thu, 9 Aug 2018 11:08:28 +0200 Subject: [PATCH 04/13] Setup reverse mapping --- README.md | 174 +++++++++--------- package.json | 2 +- src/funcs/alt.ts | 23 ++- src/funcs/get-test.ts | 81 ++++---- src/funcs/get.ts | 11 -- src/funcs/getSet.ts | 29 +++ src/funcs/mutate-test.ts | 101 +++++++++- src/funcs/mutate.ts | 34 +++- src/funcs/pipe-test.ts | 22 ++- src/funcs/pipe.ts | 7 +- src/funcs/set-test.ts | 67 +++---- src/funcs/set.ts | 28 --- src/index.ts | 17 +- src/tests/default-test.ts | 28 ++- src/tests/field-pathRev-test.ts | 72 -------- src/tests/filter-test.ts | 85 ++++----- ...bject-pathRev-test.ts => path-rev-test.ts} | 119 ++++++------ src/tests/path-test.ts | 20 ++ src/tests/transform-test.ts | 95 +++++----- src/types.ts | 8 +- src/utils/definitionHelpers.ts | 8 +- src/utils/pathSetter.ts | 4 +- src/utils/stateHelpers.ts | 21 ++- 23 files changed, 565 insertions(+), 491 deletions(-) delete mode 100644 src/funcs/get.ts create mode 100644 src/funcs/getSet.ts delete mode 100644 src/funcs/set.ts delete mode 100644 src/tests/field-pathRev-test.ts rename src/tests/{object-pathRev-test.ts => path-rev-test.ts} (70%) diff --git a/README.md b/README.md index 3ecdd9d..ebf164c 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ Map and transform objects with mapping definitions. [![Dependencies Status](https://tidelift.com/badges/github/integreat-io/map-transform?style=flat)](https://tidelift.com/subscriber/github/integreat-io/repositories/map-transform) [![Maintainability](https://api.codeclimate.com/v1/badges/fbb6638a32ee5c5f60b7/maintainability)](https://codeclimate.com/github/integreat-io/map-transform/maintainability) -Behind this boring name hides a powerful object transformer. There are a lot of -these around, but MapTransform has some differentating features: +Behind this boring name hides a powerful object transformer. + +Some highlighted features: - You pretty much define the transformation by creating the JavaScript object you want as a result, setting paths and transformation functions, etc. where they apply. @@ -21,115 +22,110 @@ defined a mapping back to the original format (with some gotchas). Let's look at a simple example: ```javascript -import mapTransform from 'map-transform' +const { mapTransform } = require('map-transform') -const def = [ - 'sections[0].articles', - { - title: 'content.headline', - author: 'meta.writer.username' - } -] +// You have this object +const source = { + data: [ + { + content: { + name: 'An interesting piece', + meta: { + author: 'fredj', + date: 1533750490952 + } + } + } + ] +} + +// You describe the object you want +const def = { + title: 'data[0].content.name', + author: 'data[0].content.meta.author', + date: 'data[0].content.meta.date' +} +// You feed it to mapTransform and get a map function const mapper = mapTransform(def) -// `mapper` is now a function that will always map as defined by `mapping` +// Now, run the source object through the mapper and get what you want +const target = mapper(source) +// --> { +// title: 'An interesting piece', +// author: 'fredj', +// date: 1533750490952 +// } -const data = { - sections: [ +// And run it in reverse to get to what you started with: +const sourc2 = mapper.rev(target) +// -> { + data: [ { - articles: [ - { - content: { - headline: 'Shocking news!', - abstract: 'An insignificant event was hyped by the media.', - date: '2018-05-31T18:43:01Z' - }, - meta: { - writer: { - name: 'Fred Johnson', - username: 'fredj' - } - } - }, - { - content: { - headline: 'Even more shocking news!', - abstract: 'The hyped event turned out to be significant after all.', - date: '2018-05-31T19:01:17Z' - }, - meta: { - writer: { - name: 'Martha Redding', - username: 'marthar' - } - } - } - ] - } + content: { + name: 'An interesting piece' + }, + meta: { + author: 'fredj', + date: 1533750490952 + } + }, ] } +``` + +You may improve this with pipelines, expressed through arrays. For instance, +retrieve the `content` object first, so you don't have to write the entiry +path for every attribute: + +```javascript +const def2 = [ + 'data[0].content', + { + title: 'name', + author: 'meta.author', + date: 'meta.date' + } +] -const mappedData = mapper(data) -// --> [ -// { -// title: 'Shocking news!', -// author: 'fredj' -// }, -// { -// title: 'Even more shocking news!', -// author: 'marthar' -// } -// ] +const target2 = mapTransform(def2)(source) +// --> { +// title: 'An interesting piece', +// author: 'fredj', +// date: 1533750490952 +// } ``` -A mapper function may also be run the other way, by calling `mapper.rev(data)`: +Maybe you want the actual date instead of the microseconds since the seventies: ```javascript -// Using the same mapper as in the first example: +const { transform } = require('map-transform') -const data = [ - { - title: 'Shocking news!', - author: 'fredj' - }, +// Write a transform function, that accepts a value and returns a value +const msToDate = (ms) => (new Date(ms)).toISOString() + +const def3 = [ + 'data[0].content', { - title: 'Even more shocking news!', - author: 'marthar' + title: 'name', + author: 'meta.author', + date: [ 'meta.date', transform(msToDate) ] } ] -const revData = mapper.rev(data) +const target3 = mapTransform(def3)(source) // --> { -// sections: [ -// { -// articles: [ -// { -// content: { -// headline: 'Shocking news!' -// }, -// meta: { -// writer: { -// username: 'fredj' -// } -// } -// }, -// { -// content: { -// headline: 'Even more shocking news!', -// }, -// meta: { -// writer: { -// username: 'marthar' -// } -// } -// } -// ] -// } -// ] +// title: 'An interesting piece', +// author: 'fredj', +// date: '2018-08-08T17:48:10.952Z' // } + +// You may also reverse this, as long as you write a reverse version of +// `msToDate` ``` +... and so on. + ## Getting started ### Prerequisits diff --git a/package.json b/package.json index 2a0c14c..5330040 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "homepage": "https://github.com/integreat-io/map-transform#readme", "dependencies": { "crocks": "^0.10.1", - "map-any": "^0.1.1", + "map-any": "^0.1.2", "ramda": "^0.25.0" }, "devDependencies": { diff --git a/src/funcs/alt.ts b/src/funcs/alt.ts index 0c35f1f..3afb8ba 100644 --- a/src/funcs/alt.ts +++ b/src/funcs/alt.ts @@ -1,14 +1,21 @@ -import { State, MapFunction, MapDefinition } from '../types' -import { setValueFromState } from '../utils/stateHelpers' +import * as mapAny from 'map-any' +import { State, Data, MapFunction, MapDefinition } from '../types' +import { setValue, getValue } from '../utils/stateHelpers' import { mapFunctionFromDef } from '../utils/definitionHelpers' +const getOne = (context: Data | Data[], index?: number) => + (typeof index === 'undefined' || !Array.isArray(context)) ? context : context[index] + +const getValueOrDefault = (state: State, runAlt: MapFunction) => (value: Data, index?: number) => + (typeof value === 'undefined') + ? getValue(runAlt({ ...state, value: getOne(state.context, index) })) + : value + export default function alt (fn: MapDefinition): MapFunction { const runAlt = mapFunctionFromDef(fn) - return (state: State) => (typeof state.value === 'undefined') - ? setValueFromState( - state, - runAlt({ ...state, value: state.context }) - ) - : state + return (state: State) => setValue( + state, + mapAny(getValueOrDefault(state, runAlt), state.value) + ) } diff --git a/src/funcs/get-test.ts b/src/funcs/get-test.ts index ed634c2..315877b 100644 --- a/src/funcs/get-test.ts +++ b/src/funcs/get-test.ts @@ -1,38 +1,22 @@ import test from 'ava' import { compose } from 'ramda' -import get from './get' - -// Helpers - -const object = { - data: { - items: [ - { id: 'item1', title: 'First item', tags: ['one', 'odd'], active: true }, - { id: 'item2', title: 'Second item', tags: ['two', 'even'], active: false }, - { id: 'item3', title: 'Third, but not last', tags: ['three', 'odd'] }, - { id: 'item4', title: 'Fourth and last', tags: ['four', 'even'], active: true } - ] - }, - meta: { - author: 'Someone', - tags: [] - }, - list: [{ id: 'no1' }, { id: 'no2' }, { id: 'no3' }] -} +import { get } from './getSet' // Tests test('should get from path and set on value', (t) => { + const data = { meta: { author: 'Someone' } } const state = { - root: object, - context: object, - value: object + root: data, + context: data, + value: data } const expected = { - root: object, - context: object, - value: 'Someone' + root: data, + context: data, + value: 'Someone', + arr: false } const ret = get('meta.author')(state) @@ -41,35 +25,50 @@ test('should get from path and set on value', (t) => { }) test('should be composable', (t) => { - const state = { - root: object, - context: object, - value: object + const data = { + data: { + items: [{ title: 'First item' }, { title: 'Second item' }] + } } - const expected = { - root: object, - context: object, - value: 'Second item' + const state = { + root: data, + context: data, + value: data } + const expectedValue = 'Second item' const ret = compose(get('title'), get('data.items[1]'))(state) - t.deepEqual(ret, expected) + t.deepEqual(ret.value, expectedValue) }) test('should set value to undefined when path is missing on value', (t) => { const state = { - root: object, - context: object, - value: object + root: {}, + context: {}, + value: {} + } + + const ret = get('unknown.path')(state) + + t.is(ret.value, undefined) +}) + +test('should set on path when reversed', (t) => { + const state = { + root: 'Someone', + context: 'Someone', + value: 'Someone', + rev: true } const expected = { - root: object, - context: object, - value: undefined + root: 'Someone', + context: 'Someone', + value: { meta: { author: 'Someone' } }, + rev: true } - const ret = get('unknown.path')(state) + const ret = get('meta.author')(state) t.deepEqual(ret, expected) }) diff --git a/src/funcs/get.ts b/src/funcs/get.ts deleted file mode 100644 index 92980d8..0000000 --- a/src/funcs/get.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MapFunction, State, Path } from '../types' -import getter from '../utils/pathGetter' - -export default function get (path: Path | null): MapFunction { - const get = getter(path) - - return (state: State): State => ({ - ...state, - value: get(state.value) - }) -} diff --git a/src/funcs/getSet.ts b/src/funcs/getSet.ts new file mode 100644 index 0000000..10b28d5 --- /dev/null +++ b/src/funcs/getSet.ts @@ -0,0 +1,29 @@ +import * as mapAny from 'map-any' +import { MapFunction, State, Path } from '../types' +import getter from '../utils/pathGetter' +import setter from '../utils/pathSetter' + +const getOrSet = (isGet: boolean) => (path: Path): MapFunction => { + const get = getter(path) + const set = setter(path) + const isArray = path.endsWith('[]') + + return (state: State): State => { + if (isGet ? !state.rev : state.rev) { + const value = mapAny(get, state.value) + return { + ...state, + value, + arr: !Array.isArray(state.value) && Array.isArray(value) + } + } else { + return { + ...state, + value: (state.arr || isArray) ? set(state.value) : mapAny(set, state.value) + } + } + } +} + +export const get = getOrSet(true) +export const set = getOrSet(false) diff --git a/src/funcs/mutate-test.ts b/src/funcs/mutate-test.ts index 9e01890..d73f4d0 100644 --- a/src/funcs/mutate-test.ts +++ b/src/funcs/mutate-test.ts @@ -1,6 +1,6 @@ import test from 'ava' import value from './value' -import get from './get' +import { get } from './getSet' import mutate from './mutate' @@ -64,10 +64,8 @@ test('should set value to undefined when no map functions', (t) => { test('should treat string as path', (t) => { const def = { - item: { - attributes: { - title: 'headline' - } + content: { + title: 'headline' } } const state = { @@ -76,10 +74,8 @@ test('should treat string as path', (t) => { value: { headline: 'The title' } } const expectedValue = { - item: { - attributes: { - title: 'The title' - } + content: { + title: 'The title' } } @@ -113,3 +109,90 @@ test('should treat array as map pipe', (t) => { t.deepEqual(ret.value, expectedValue) }) + +test('should mutate object with value array', (t) => { + const data = [{ headline: 'Entry 1' }, { headline: 'Entry 2' }] + const def = { + data: { + 'items[]': [ + { + title: get('headline') + } + ] + } + } + const state = { + root: data, + context: data, + value: data + } + const expectedValue = { + data: { + items: [ + { title: 'Entry 1' }, + { title: 'Entry 2' } + ] + } + } + + const ret = mutate(def)(state) + + t.deepEqual(ret.value, expectedValue) +}) + +test('should reverse map', (t) => { + const def = { + content: { + title: 'headline' + } + } + const state = { + root: {}, + context: {}, + value: { + content: { + title: 'The title' + } + }, + rev: true + } + const expectedValue = { headline: 'The title' } + + const ret = mutate(def)(state) + + t.deepEqual(ret.value, expectedValue) +}) + +test('should reverse map with value array', (t) => { + const data = { + data: { + items: [ + { title: 'Entry 1' }, + { title: 'Entry 2' } + ] + } + } + const def = { + data: { + items: [ + { + title: get('headline') + } + ] + } + } + const state = { + root: data, + context: data, + value: data, + rev: true + } + const expectedValue = [ + { headline: 'Entry 1' }, + { headline: 'Entry 2' } + ] + + const ret = mutate(def)(state) + + t.deepEqual(ret.value, expectedValue) +}) diff --git a/src/funcs/mutate.ts b/src/funcs/mutate.ts index eea85d4..f97113c 100644 --- a/src/funcs/mutate.ts +++ b/src/funcs/mutate.ts @@ -1,23 +1,39 @@ -import { MapFunction, State, MapDefinition, Path } from '../types' -import set from './set' -import { setValueFromState, shiftState, pipeMapFns } from '../utils/stateHelpers' -import { mapFunctionFromDef, isMapObject } from '../utils/definitionHelpers' +import { compose, mergeDeepRight } from 'ramda' +import { MapFunction, State, MapDefinition, MapPipe, Path } from '../types' +import { set } from './getSet' +import pipe from './pipe' +import { setValueFromState, setValue, getValue, liftState, pipeMapFns } from '../utils/stateHelpers' +import { isMapObject } from '../utils/definitionHelpers' const appendToPath = (path: string[], fragment: string) => [...path, fragment] +const runAndMergeState = (fn: MapFunction) => (state: State): State => { + const run = compose(getValue, fn, setValue(state)) + const value = run(state.context) + + return (Array.isArray(value)) + ? setValue(state, value) + : setValue(state, mergeDeepRight(state.value, value)) +} + +const mergeWithPath = (existing: MapPipe | Path | MapFunction, path: Path) => + (Array.isArray(existing)) ? [...existing, set(path)] : [existing, set(path)] + +const pipeWithSetPath = (existing: MapPipe | Path | MapFunction, path: Path) => + runAndMergeState(pipe(mergeWithPath(existing, path))) + const extractSetFns = (def: MapDefinition, path: string[] = []): MapFunction[] => (isMapObject(def)) ? Object.keys(def).reduce( (sets: MapFunction[], key: string) => [ ...sets, ...extractSetFns(def[key], appendToPath(path, key)) ], []) - : (def !== null) - ? [set(path.join('.'), mapFunctionFromDef(def as Path | MapFunction))] - : [] + : (def === null) ? [] : [pipeWithSetPath(def, path.join('.'))] export default function mutate (def: MapDefinition): MapFunction { - const runMutation = pipeMapFns(extractSetFns(def)) + const fns = extractSetFns(def) + const runMutation = compose(pipeMapFns(fns), liftState) return (state: State): State => setValueFromState( state, - runMutation(shiftState(state)) + runMutation(state) ) } diff --git a/src/funcs/pipe-test.ts b/src/funcs/pipe-test.ts index 733d9e4..c3a139c 100644 --- a/src/funcs/pipe-test.ts +++ b/src/funcs/pipe-test.ts @@ -1,5 +1,5 @@ import test from 'ava' -import get from './get' +import { get } from './getSet' import pipe from './pipe' @@ -48,3 +48,23 @@ test('should treat object as map object', (t) => { t.deepEqual(ret.value, expectedValue) }) + +test('should reverse map pipe on reverse mapping', (t) => { + const def = [get('data'), get('name')] + const state = { + root: 'John F.', + context: 'John F.', + value: 'John F.', + rev: true + } + const expected = { + root: 'John F.', + context: 'John F.', + value: { data: { name: 'John F.' } }, + rev: true + } + + const ret = pipe(def)(state) + + t.deepEqual(ret, expected) +}) diff --git a/src/funcs/pipe.ts b/src/funcs/pipe.ts index b298283..284c26f 100644 --- a/src/funcs/pipe.ts +++ b/src/funcs/pipe.ts @@ -1,12 +1,15 @@ +import { reverse } from 'ramda' import { MapPipe, MapFunction, State } from '../types' import { setValueFromState, pipeMapFns } from '../utils/stateHelpers' import { mapFunctionFromDef } from '../utils/definitionHelpers' export default function pipe (def: MapPipe): MapFunction { - const runPipe = pipeMapFns(def.map(mapFunctionFromDef)) + const fns = def.map(mapFunctionFromDef) + const runPipe = pipeMapFns(fns) + const runRevPipe = pipeMapFns(reverse(fns)) return (state: State) => setValueFromState( state, - runPipe(state) + (state.rev) ? runRevPipe(state) : runPipe(state) ) } diff --git a/src/funcs/set-test.ts b/src/funcs/set-test.ts index 5ce8a55..7989697 100644 --- a/src/funcs/set-test.ts +++ b/src/funcs/set-test.ts @@ -1,70 +1,55 @@ import test from 'ava' -import get from './get' -import set from './set' +import { set } from './getSet' // Tests -test('should get value from context and set on path', (t) => { +test('should set value on path', (t) => { + const data = { user: 'johnf' } const state = { - root: { user: 'johnf' }, - context: { user: 'johnf' }, - value: undefined + root: data, + context: data, + value: 'johnf' } const expected = { - root: { user: 'johnf' }, - context: { user: 'johnf' }, + root: data, + context: data, value: { meta: { author: 'johnf' } } } - const ret = set('meta.author', get('user'))(state) - - t.deepEqual(ret, expected) -}) - -test('should set on existing value', (t) => { - const state = { - root: { user: 'johnf' }, - context: { user: 'johnf' }, - value: { meta: { sections: ['news'] } } - } - const expected = { - root: { user: 'johnf' }, - context: { user: 'johnf' }, - value: { meta: { author: 'johnf', sections: ['news'] } } - } - const ret = set('meta.author', get('user'))(state) + const ret = set('meta.author')(state) t.deepEqual(ret, expected) }) -test('should get value from array and set on path', (t) => { +test('should set undefined', (t) => { const state = { - root: [{ user: 'johnf' }, { user: 'maryk' }], - context: [{ user: 'johnf' }, { user: 'maryk' }], + root: {}, + context: {}, value: undefined } - const expectedValue = [ - { meta: { author: 'johnf' } }, - { meta: { author: 'maryk' } } - ] + const expectedValue = { meta: { author: undefined } } - const ret = set('meta.author', get('user'))(state) + const ret = set('meta.author')(state) t.deepEqual(ret.value, expectedValue) }) -test('should set undefined', (t) => { +test('should get from path when reverse mapping', (t) => { + const data = { user: 'johnf' } const state = { - root: {}, - context: {}, - value: undefined + root: data, + context: data, + value: { meta: { author: 'johnf' } }, + rev: true } const expected = { - root: {}, - context: {}, - value: { meta: { author: undefined } } + root: data, + context: data, + value: 'johnf', + rev: true, + arr: false } - const ret = set('meta.author', get('user'))(state) + const ret = set('meta.author')(state) t.deepEqual(ret, expected) }) diff --git a/src/funcs/set.ts b/src/funcs/set.ts deleted file mode 100644 index 9512f3e..0000000 --- a/src/funcs/set.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { compose, identity } from 'ramda' -import { State, MapFunction, Path, Data } from '../types' -import setter from '../utils/pathSetter' -import { getValue, setValue } from '../utils/stateHelpers' - -export default function set (path: Path, mapFn: MapFunction): MapFunction { - if (mapFn === null) { - return identity - } - - const isArray = path.endsWith('[]') - const set = setter(path) - const runMapping = compose( - getValue, - mapFn, - setValue - ) - - return (state: State) => { - const getAndSet = (from: Data, to?: Data) => set(runMapping(state, from), to) - - const value = (!isArray && Array.isArray(state.context)) - ? state.context.map((val) => getAndSet(val)) - : getAndSet(state.context, state.value) - - return { ...state, value } - } -} diff --git a/src/index.ts b/src/index.ts index 3dbcee8..ec4b07f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { compose } from 'ramda' -import { MapDefinition, DataMapper } from './types' +import { MapDefinition, DataMapperWithRev } from './types' import { mapFunctionFromDef } from './utils/definitionHelpers' -import { populateState, getValue } from './utils/stateHelpers' +import { populateState, populateRevState, getValue } from './utils/stateHelpers' export { default as alt } from './funcs/alt' export { default as value } from './funcs/value' @@ -9,10 +9,13 @@ export { default as transform, TransformFunction } from './funcs/transform' export { default as filter, FilterFunction } from './funcs/filter' export { Data } from './types' -export function mapTransform (def: MapDefinition): DataMapper { - return compose( - getValue, - mapFunctionFromDef(def), - populateState +export function mapTransform (def: MapDefinition): DataMapperWithRev { + const mapFn = mapFunctionFromDef(def) + + return Object.assign( + compose(getValue, mapFn, populateState), + { + rev: compose(getValue, mapFn, populateRevState) + } ) } diff --git a/src/tests/default-test.ts b/src/tests/default-test.ts index 145a16a..211e7c6 100644 --- a/src/tests/default-test.ts +++ b/src/tests/default-test.ts @@ -1,4 +1,5 @@ import test from 'ava' +import { get } from '../funcs/getSet' import { mapTransform, alt, value } from '..' @@ -23,6 +24,27 @@ test('should use default value', (t) => { t.deepEqual(ret, expected) }) +test('should use default value in array', (t) => { + const def = { + id: [ + 'id', + alt(get('key')) + ] + } + const data = [ + { id: 'id1', key: 'key1' }, + { key: 'key2' } + ] + const expected = [ + { id: 'id1' }, + { id: 'key2' } + ] + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + test('should set missing values to undefined when no default', (t) => { const def = { title: 'content.heading' @@ -106,7 +128,7 @@ test.skip('should not use default values', (t) => { { title: 'From data' } ] - const ret = mapTransform(def).noDefaults(data) + const ret = mapTransform(def)(data) // .noDefaults(data) t.deepEqual(ret, expected) }) @@ -126,7 +148,7 @@ test.skip('should not set missing prop to undefined', (t) => { { title: 'From data' } ] - const ret = mapTransform(def).noDefaults(data) + const ret = mapTransform(def)(data) // .noDefaults t.deepEqual(ret, expected) }) @@ -150,7 +172,7 @@ test.skip('should not use default values on rev', (t) => { { content: { heading: 'From data' } } ] - const ret = mapTransform(def).rev.noDefaults(data) + const ret = mapTransform(def).rev(data) // .noDefaults t.deepEqual(ret, expected) }) diff --git a/src/tests/field-pathRev-test.ts b/src/tests/field-pathRev-test.ts deleted file mode 100644 index e9fe203..0000000 --- a/src/tests/field-pathRev-test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import test from 'ava' - -import * as mapTransform from '..' - -test.skip('should reverse map simple object', (t) => { - const def = { - mapping: { - title: 'content.heading', - author: 'meta.writer.username' - } - } - const data = { - title: 'The heading', - author: 'johnf' - } - const expected = { - content: { heading: 'The heading' }, - meta: { writer: { username: 'johnf' } } - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should reverse map array of objects', (t) => { - const def = { - mapping: { - title: 'content.heading', - author: 'meta.writer.username' - } - } - const data = [ - { title: 'The heading', author: 'johnf' }, - { title: 'Second heading', author: 'maryk' } - ] - const expected = [ - { - content: { heading: 'The heading' }, - meta: { writer: { username: 'johnf' } } - }, - { - content: { heading: 'Second heading' }, - meta: { writer: { username: 'maryk' } } - } - ] - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should reverse map with array path', (t) => { - const def = { - mapping: { - title: 'content.heading', - author: 'meta.writer[0].username' - } - } - const data = { - title: 'The heading', - author: 'johnf' - } - const expected = { - content: { heading: 'The heading' }, - meta: { writer: [{ username: 'johnf' }] } - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) diff --git a/src/tests/filter-test.ts b/src/tests/filter-test.ts index 392b483..8637e74 100644 --- a/src/tests/filter-test.ts +++ b/src/tests/filter-test.ts @@ -6,7 +6,7 @@ import { mapTransform, filter, FilterFunction, Data } from '..' const isObject = (item: Data): item is object => (!!item && typeof item === 'object') -const noHeading: FilterFunction = (item) => +const noHeadingTitle: FilterFunction = (item) => (isObject(item)) && !(/heading/gi).test((item as any).title) const noAlso: FilterFunction = (item) => (isObject(item)) && !(/also/gi).test((item as any).title) @@ -18,7 +18,7 @@ test('should filter out item', (t) => { { title: 'content.heading' }, - filter(noHeading) + filter(noHeadingTitle) ] const data = { content: { heading: 'The heading' } @@ -35,7 +35,7 @@ test('should filter out items in array', (t) => { { title: 'content.heading' }, - filter(noHeading) + filter(noHeadingTitle) ] const data = [ { content: { heading: 'The heading' } }, @@ -56,7 +56,7 @@ test('should filter with several filters', (t) => { { title: 'content.heading' }, - filter(noHeading), + filter(noHeadingTitle), filter(noAlso) ] const data = [ @@ -80,7 +80,7 @@ test('should set filtered items on path', (t) => { { title: 'content.heading' }, - filter(noHeading) + filter(noHeadingTitle) ] } const data = [ @@ -98,13 +98,14 @@ test('should set filtered items on path', (t) => { t.deepEqual(ret, expected) }) -test.skip('should filter items from pathTo for reverse mapping', (t) => { +test('should filter items from parent mapping for reverse mapping', (t) => { const def = { - mapping: { - title: 'content.heading' - }, - filter: [ noHeading ], - pathTo: 'items[]' + 'items[]': [ + { + title: 'content.heading' + }, + filter(noHeadingTitle) + ] } const data = { items: [ @@ -121,13 +122,14 @@ test.skip('should filter items from pathTo for reverse mapping', (t) => { t.deepEqual(ret, expected) }) -test.skip('should filter on reverse mapping', (t) => { - const def = { - mapping: { +test('should filter on reverse mapping', (t) => { + const def = [ + { title: 'content.heading' }, - filter: [ noHeading, noAlso ] - } + filter(noHeadingTitle), + filter(noAlso) + ] const data = [ { title: 'The heading' }, { title: 'Just this' }, @@ -144,13 +146,14 @@ test.skip('should filter on reverse mapping', (t) => { }) test.skip('should filter with filterRev on reverse mapping', (t) => { - const def = { - mapping: { + const def = [ + { title: 'content.heading' }, - filter: [ noHeading, noAlso ], - filterRev: [ noHeading ] - } + filter(noHeadingTitle), + filter(noAlso), + filter(noHeadingTitle) // filterRev + ] const data = [ { title: 'The heading' }, { title: 'Just this' }, @@ -170,7 +173,7 @@ test.skip('should filter with filterRev on reverse mapping', (t) => { test('should filter before mapping', (t) => { const def = [ 'content', - filter(noHeading), + filter(noHeadingTitle), { heading: 'title' } @@ -187,40 +190,18 @@ test('should filter before mapping', (t) => { t.deepEqual(ret, expected) }) -test.skip('should filter with filterFrom on reverse mapping', (t) => { - const def = { - mapping: { +test('should filter with filter before mapping on reverse mapping', (t) => { + const def = [ + 'content', + filter(noHeadingTitle), + { heading: 'title' - }, - pathFrom: 'content', - filterFrom: noHeading - } + } + ] const data = { heading: 'The heading' } - const expected = { content: null } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should filter with filterFromRev on reverse mapping', (t) => { - const def = { - mapping: { - heading: 'title' - }, - pathFrom: 'content', - filterFrom: noHeading, - filterFromRev: noAlso - } - const data = [ - { heading: 'The heading' }, - { heading: 'Also not this' } - ] - const expected = { - content: [{ title: 'The heading' }] - } + const expected = { content: undefined } const ret = mapTransform(def).rev(data) diff --git a/src/tests/object-pathRev-test.ts b/src/tests/path-rev-test.ts similarity index 70% rename from src/tests/object-pathRev-test.ts rename to src/tests/path-rev-test.ts index 588fe58..b98b1ce 100644 --- a/src/tests/object-pathRev-test.ts +++ b/src/tests/path-rev-test.ts @@ -1,25 +1,19 @@ import test from 'ava' -import * as mapTransform from '..' +import { mapTransform } from '..' -test.skip('should reverse map with object path', (t) => { +test('should reverse map simple object', (t) => { const def = { - mapping: { - title: { path: 'content.heading' } - }, - path: 'content.articles' + title: 'content.heading', + author: 'meta.writer.username' + } + const data = { + title: 'The heading', + author: 'johnf' } - const data = [ - { title: 'Heading 1' }, - { title: 'Heading 2' } - ] const expected = { - content: { - articles: [ - { content: { heading: 'Heading 1' } }, - { content: { heading: 'Heading 2' } } - ] - } + content: { heading: 'The heading' }, + meta: { writer: { username: 'johnf' } } } const ret = mapTransform(def).rev(data) @@ -27,13 +21,38 @@ test.skip('should reverse map with object path', (t) => { t.deepEqual(ret, expected) }) -test.skip('should map with object array path', (t) => { +test.skip('should reverse map array of objects', (t) => { const def = { - mapping: { - title: { path: 'content.heading' } - }, - path: 'content.articles[]' + title: 'content.heading', + author: 'meta.writer.username' } + const data = [ + { title: 'The heading', author: 'johnf' }, + { title: 'Second heading', author: 'maryk' } + ] + const expected = [ + { + content: { heading: 'The heading' }, + meta: { writer: { username: 'johnf' } } + }, + { + content: { heading: 'Second heading' }, + meta: { writer: { username: 'maryk' } } + } + ] + + const ret = mapTransform(def).rev(data) + + t.deepEqual(ret, expected) +}) + +test('should reverse map with object array path', (t) => { + const def = [ + 'content.articles[]', + { + title: 'content.heading' + } + ] const data = [ { title: 'Heading 1' }, { title: 'Heading 2' } @@ -52,13 +71,13 @@ test.skip('should map with object array path', (t) => { t.deepEqual(ret, expected) }) -test.skip('should map with root array path', (t) => { - const def = { - mapping: { - title: { path: 'content.heading' } - }, - path: '[]' - } +test('should reverse map with root array path', (t) => { + const def = [ + '[]', + { + title: 'content.heading' + } + ] const data = [ { title: 'Heading 1' }, { title: 'Heading 2' } @@ -73,10 +92,10 @@ test.skip('should map with root array path', (t) => { t.deepEqual(ret, expected) }) -test.skip('should reverse map data as is when no mapping', (t) => { - const def = { - path: 'content' - } +test('should reverse map data as is when no mapping', (t) => { + const def = [ + 'content' + ] const data = { title: 'The heading', author: 'johnf' @@ -93,31 +112,13 @@ test.skip('should reverse map data as is when no mapping', (t) => { t.deepEqual(ret, expected) }) -test.skip('should reverse map with object pathFrom', (t) => { - const def = { - mapping: { - title: { path: 'content.heading' } - }, - pathFrom: 'content.articles' - } - const data = [{ title: 'Heading 1' }] - const expected = { - content: { - articles: [{ content: { heading: 'Heading 1' } }] - } - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should reverse map with object pathTo', (t) => { +test('should reverse map with nested mapping', (t) => { const def = { - mapping: { - title: { path: 'content.heading' } - }, - pathTo: 'content.articles' + 'content.articles': [ + { + title: 'content.heading' + } + ] } const data = { content: { @@ -215,7 +216,7 @@ test.skip('should reverse map with empty pathRev and pathToRev', (t) => { t.deepEqual(ret, expected) }) -test.skip('should return data when no mapping def', (t) => { +test('should return data when no mapping def and reverse mapping', (t) => { const def = null const data = [ { content: { heading: 'Heading 1' } }, @@ -228,13 +229,13 @@ test.skip('should return data when no mapping def', (t) => { t.deepEqual(ret, expected) }) -test.skip('should return data when mapping def is empty', (t) => { +test.skip('should return undefined when mapping def is empty', (t) => { const def = {} const data = [ { content: { heading: 'Heading 1' } }, { content: { heading: 'Heading 2' } } ] - const expected = data + const expected = undefined const ret = mapTransform(def).rev(data) diff --git a/src/tests/path-test.ts b/src/tests/path-test.ts index 00db4e4..208a156 100644 --- a/src/tests/path-test.ts +++ b/src/tests/path-test.ts @@ -143,6 +143,26 @@ test('should map with array index in middle of path', (t) => { t.deepEqual(ret, expected) }) +test('should return undefined from non-matching path with array index in middle', (t) => { + const def = [ + 'content.articles[0].content.heading' + ] + const data = { + content: { + articles: { + content: { + heading: 'Heading 1' + } + } + } + } + const expected = undefined + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + test('should not map fields without paths', (t) => { const def = { title: null, diff --git a/src/tests/transform-test.ts b/src/tests/transform-test.ts index 0c6de36..a462656 100644 --- a/src/tests/transform-test.ts +++ b/src/tests/transform-test.ts @@ -26,10 +26,12 @@ const setActive: TransformFunction = (item) => (isObject(item)) // setActive.rev = (item: {}) => ({ ...item, active: false }) -const setAuthorName: TransformFunction = (item: {author: string}) => ({ - ...item, - authorName: `${item.author[0].toUpperCase()}${item.author.substr(1)}.` -}) +// const prepareAuthorName = ({ author }: { author: string }) => +// `${author[0].toUpperCase()}${author.substr(1)}.` +// +// const setAuthorName: TransformFunction = (item) => (isObject(item)) +// ? ({ ...item, authorName: prepareAuthorName(item as any) }) +// : item const appendEllipsis: TransformFunction = (str) => (typeof str === 'string') ? str + ' ...' : str // appendEllipsis.rev = (str: string) => @@ -40,7 +42,6 @@ const getLength: TransformFunction = (str) => (typeof str === 'string') ? str.le // enclose.rev = (str: string) => (str.startsWith('(') && str.endsWith(')')) // ? str.substr(1, str.length - 2) : str - // Tests test('should map simple object with one transform function', (t) => { @@ -90,15 +91,15 @@ test('should map simple object with several transform functions', (t) => { }) test.skip('should reverse map simple object with transformRev', (t) => { - const def = { - mapping: { + const def = [ + // transform: [ appendAuthorToTitle, setActive ], + // transformRev: [ setAuthorName ], + { title: 'content.heading', author: 'meta.writer.username', authorName: 'meta.writer.name' - }, - transform: [ appendAuthorToTitle, setActive ], - transformRev: [ setAuthorName ] - } + } + ] const data = { title: 'The heading', author: 'johnf' @@ -114,13 +115,14 @@ test.skip('should reverse map simple object with transformRev', (t) => { }) test.skip('should reverse map simple object with transform rev props', (t) => { - const def = { - mapping: { + const def = [ + transform(appendAuthorToTitle), + transform(setActive), + { title: 'content.heading', author: 'meta.writer.username' - }, - transform: [ appendAuthorToTitle, setActive ] - } + } + ] const data = { title: 'The heading - by johnf', author: 'johnf' @@ -168,15 +170,16 @@ test('should transform beofre data is set on outer path', (t) => { test.skip('should reverse transform after pathTo and before path', (t) => { const def = { - mapping: { - title: 'content.heading', - author: 'meta.writer.username', - authorName: 'meta.writer.name' - }, - transform: [ appendAuthorToTitle, setActive ], - transformRev: [ setAuthorName ], - path: 'result.data', - pathTo: 'attributes' + attributes: [ + 'result.data', + { + title: 'content.heading', + author: 'meta.writer.username', + authorName: 'meta.writer.name' + } + // transform: [ appendAuthorToTitle, setActive ], + // transformRev: [ setAuthorName ] + ] } const data = { attributes: { @@ -199,12 +202,12 @@ test.skip('should reverse transform after pathTo and before path', (t) => { }) test.skip('should transform with array', (t) => { - const def = { - mapping: { + const def = [ + // transform: [ setActive ], + { title: 'content.heading' - }, - transform: [ setActive ] - } + } + ] const data = [ { content: { heading: 'The heading' } }, { content: { heading: 'Another heading' } } @@ -220,13 +223,13 @@ test.skip('should transform with array', (t) => { }) test.skip('should reverse transform with array', (t) => { - const def = { - mapping: { + const def = [ + // transform: [ setActive ], + { title: 'content.heading', active: 'content.active' - }, - transform: [ setActive ] - } + } + ] const data = [ { title: 'The heading', active: true }, { title: 'Another heading', active: true } @@ -263,12 +266,12 @@ test('should transform before mapping', (t) => { }) test.skip('should reverse map with transformFrom function', (t) => { - const def = { - mapping: { + const def = [ + // transformFrom: setActive, + { title: 'content.heading' - }, - transformFrom: setActive - } + } + ] const data = { title: 'The heading' } @@ -285,13 +288,13 @@ test.skip('should reverse map with transformFrom function', (t) => { }) test.skip('should reverse map with transformFromRev function', (t) => { - const def = { - mapping: { + const def = [ + { title: 'content.heading' - }, - transformFrom: setActive, - transformFromRev: setActive - } + } + // transformFrom: setActive, + // transformFromRev: setActive + ] const data = { title: 'The heading' } diff --git a/src/types.ts b/src/types.ts index 0387b66..48c0295 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,7 +9,9 @@ export type Data = Prop | Prop[] export interface State { root: Data, context: Data, - value: Data + value: Data, + rev?: boolean, + arr?: boolean } export type Path = string @@ -29,3 +31,7 @@ export type MapDefinition = MapObject | MapFunction | MapPipe | Path | null export interface DataMapper { (data: Data): Data } + +export interface DataMapperWithRev extends DataMapper { + rev: DataMapper +} diff --git a/src/utils/definitionHelpers.ts b/src/utils/definitionHelpers.ts index bc70f90..9147533 100644 --- a/src/utils/definitionHelpers.ts +++ b/src/utils/definitionHelpers.ts @@ -1,6 +1,6 @@ -import { identity } from 'ramda' +import { identity, isNil } from 'ramda' import { MapFunction, MapDefinition, MapObject, Path, MapPipe } from '../types' -import get from '../funcs/get' +import { get } from '../funcs/getSet' import mutate from '../funcs/mutate' import pipe from '../funcs/pipe' @@ -9,8 +9,8 @@ export const isMapObject = (def: MapDefinition): def is MapObject => def !== nul export const isMapPipe = (def: MapDefinition): def is MapPipe => Array.isArray(def) export const mapFunctionFromDef = (def: MapDefinition): MapFunction => - (def === null) ? identity - : isPath(def) ? get(def) + (isNil(def)) ? identity : isMapObject(def) ? mutate(def) + : isPath(def) ? get(def) : isMapPipe(def) ? pipe(def) : def diff --git a/src/utils/pathSetter.ts b/src/utils/pathSetter.ts index ca2c9ed..eb3f51f 100644 --- a/src/utils/pathSetter.ts +++ b/src/utils/pathSetter.ts @@ -58,7 +58,7 @@ const getSetters = R.compose( split ) -type SetFunction = (value: Data, object: Data | null) => Data +type SetFunction = (value: Data, object?: Data | null) => Data /** * Set `value` at `path` in `object`. Note that a new object is returned, and @@ -73,7 +73,7 @@ type SetFunction = (value: Data, object: Data | null) => Data export default function pathSetter (path: Path): SetFunction { const setters = getSetters(path) - return (value, object) => { + return (value, object = null) => { const data = setters(value) return (object) ? R.mergeDeepRight(object, data) : data } diff --git a/src/utils/stateHelpers.ts b/src/utils/stateHelpers.ts index a9a73e3..5d338c4 100644 --- a/src/utils/stateHelpers.ts +++ b/src/utils/stateHelpers.ts @@ -1,6 +1,7 @@ +import { curry } from 'ramda' import { State, MapFunction, Data } from '../types' -export const setValue = (state: State, value: Data): State => ({ ...state, value }) +export const setValue = curry((state: State, value: Data): State => ({ ...state, value })) export const getValue = (state: State): Data => state.value export const setValueFromState = (state: State, { value }: State) => ({ @@ -8,17 +9,27 @@ export const setValueFromState = (state: State, { value }: State) => ({ value }) -export const shiftState = (state: State) => ({ +export const liftState = (state: State) => ({ ...state, context: state.value, - value: undefined + value: undefined, + arr: false +}) + +export const lowerState = (state: State) => ({ + ...state, + value: state.context }) export const pipeMapFns = (fns: MapFunction[]) => (state: State): State => fns.reduce((state: State, fn: MapFunction) => fn(state), state) -export const populateState = (data: Data): State => ({ +const initState = (rev: boolean) => (data: Data): State => ({ root: data, context: data, - value: data + value: data, + rev }) + +export const populateState = initState(false) +export const populateRevState = initState(true) From c02fc70e3c1533a1b27e20ad4b8fb5ab9302716e Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Sat, 11 Aug 2018 20:58:46 +0200 Subject: [PATCH 05/13] Mutate root object without mapping array --- src/funcs/alt.ts | 6 ++-- src/funcs/filter.ts | 4 +-- src/funcs/mutate-test.ts | 30 ----------------- src/funcs/mutate.ts | 43 ++++++------------------ src/funcs/transform.ts | 4 +-- src/index.ts | 11 ++++--- src/tests/path-rev-test.ts | 4 +-- src/tests/path-test.ts | 56 ++++++++++++++++++++++++++++++++ src/utils/objectToMapFunction.ts | 35 ++++++++++++++++++++ src/utils/stateHelpers.ts | 4 +-- 10 files changed, 118 insertions(+), 79 deletions(-) create mode 100644 src/utils/objectToMapFunction.ts diff --git a/src/funcs/alt.ts b/src/funcs/alt.ts index 3afb8ba..732baff 100644 --- a/src/funcs/alt.ts +++ b/src/funcs/alt.ts @@ -1,6 +1,6 @@ import * as mapAny from 'map-any' import { State, Data, MapFunction, MapDefinition } from '../types' -import { setValue, getValue } from '../utils/stateHelpers' +import { setStateValue, getStateValue } from '../utils/stateHelpers' import { mapFunctionFromDef } from '../utils/definitionHelpers' const getOne = (context: Data | Data[], index?: number) => @@ -8,13 +8,13 @@ const getOne = (context: Data | Data[], index?: number) => const getValueOrDefault = (state: State, runAlt: MapFunction) => (value: Data, index?: number) => (typeof value === 'undefined') - ? getValue(runAlt({ ...state, value: getOne(state.context, index) })) + ? getStateValue(runAlt({ ...state, value: getOne(state.context, index) })) : value export default function alt (fn: MapDefinition): MapFunction { const runAlt = mapFunctionFromDef(fn) - return (state: State) => setValue( + return (state: State) => setStateValue( state, mapAny(getValueOrDefault(state, runAlt), state.value) ) diff --git a/src/funcs/filter.ts b/src/funcs/filter.ts index 2098701..63fa382 100644 --- a/src/funcs/filter.ts +++ b/src/funcs/filter.ts @@ -1,6 +1,6 @@ import { compose, unless, always, ifElse, filter as filterR, identity } from 'ramda' import { MapFunction, Data, State } from '../types' -import { getValue } from '../utils/stateHelpers' +import { getStateValue } from '../utils/stateHelpers' export interface FilterFunction { (data: Data): boolean @@ -17,7 +17,7 @@ export default function filter (fn: FilterFunction): MapFunction { filterR(fn), unless(fn, always(undefined)) ), - getValue + getStateValue ) return (state: State) => ({ diff --git a/src/funcs/mutate-test.ts b/src/funcs/mutate-test.ts index d73f4d0..2e7d744 100644 --- a/src/funcs/mutate-test.ts +++ b/src/funcs/mutate-test.ts @@ -110,36 +110,6 @@ test('should treat array as map pipe', (t) => { t.deepEqual(ret.value, expectedValue) }) -test('should mutate object with value array', (t) => { - const data = [{ headline: 'Entry 1' }, { headline: 'Entry 2' }] - const def = { - data: { - 'items[]': [ - { - title: get('headline') - } - ] - } - } - const state = { - root: data, - context: data, - value: data - } - const expectedValue = { - data: { - items: [ - { title: 'Entry 1' }, - { title: 'Entry 2' } - ] - } - } - - const ret = mutate(def)(state) - - t.deepEqual(ret.value, expectedValue) -}) - test('should reverse map', (t) => { const def = { content: { diff --git a/src/funcs/mutate.ts b/src/funcs/mutate.ts index f97113c..2a5af06 100644 --- a/src/funcs/mutate.ts +++ b/src/funcs/mutate.ts @@ -1,39 +1,16 @@ -import { compose, mergeDeepRight } from 'ramda' -import { MapFunction, State, MapDefinition, MapPipe, Path } from '../types' -import { set } from './getSet' -import pipe from './pipe' -import { setValueFromState, setValue, getValue, liftState, pipeMapFns } from '../utils/stateHelpers' -import { isMapObject } from '../utils/definitionHelpers' - -const appendToPath = (path: string[], fragment: string) => [...path, fragment] - -const runAndMergeState = (fn: MapFunction) => (state: State): State => { - const run = compose(getValue, fn, setValue(state)) - const value = run(state.context) - - return (Array.isArray(value)) - ? setValue(state, value) - : setValue(state, mergeDeepRight(state.value, value)) -} - -const mergeWithPath = (existing: MapPipe | Path | MapFunction, path: Path) => - (Array.isArray(existing)) ? [...existing, set(path)] : [existing, set(path)] - -const pipeWithSetPath = (existing: MapPipe | Path | MapFunction, path: Path) => - runAndMergeState(pipe(mergeWithPath(existing, path))) - -const extractSetFns = (def: MapDefinition, path: string[] = []): MapFunction[] => (isMapObject(def)) - ? Object.keys(def).reduce( - (sets: MapFunction[], key: string) => [ ...sets, ...extractSetFns(def[key], appendToPath(path, key)) ], - []) - : (def === null) ? [] : [pipeWithSetPath(def, path.join('.'))] +import * as mapAny from 'map-any' +import { MapFunction, State, MapDefinition } from '../types' +import { getStateValue, setStateValue } from '../utils/stateHelpers' +import objectToMapFunction from '../utils/objectToMapFunction' export default function mutate (def: MapDefinition): MapFunction { - const fns = extractSetFns(def) - const runMutation = compose(pipeMapFns(fns), liftState) + const runMutation = objectToMapFunction(def) - return (state: State): State => setValueFromState( + return (state: State): State => setStateValue( state, - runMutation(state) + mapAny( + (value) => getStateValue(runMutation(setStateValue(state, value))), + state.value + ) ) } diff --git a/src/funcs/transform.ts b/src/funcs/transform.ts index c376014..08c3fdf 100644 --- a/src/funcs/transform.ts +++ b/src/funcs/transform.ts @@ -1,6 +1,6 @@ import { compose, identity } from 'ramda' import { Data, MapFunction, State } from '../types' -import { getValue } from '../utils/stateHelpers' +import { getStateValue } from '../utils/stateHelpers' export interface TransformFunction { (data: Data): Data @@ -11,7 +11,7 @@ export default function transform (fn: TransformFunction): MapFunction { return identity } - const runTransform = compose(fn, getValue) + const runTransform = compose(fn, getStateValue) return (state: State) => ({ ...state, diff --git a/src/index.ts b/src/index.ts index ec4b07f..f0fe43a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,8 @@ import { compose } from 'ramda' import { MapDefinition, DataMapperWithRev } from './types' -import { mapFunctionFromDef } from './utils/definitionHelpers' -import { populateState, populateRevState, getValue } from './utils/stateHelpers' +import { mapFunctionFromDef, isMapObject } from './utils/definitionHelpers' +import { populateState, populateRevState, getStateValue } from './utils/stateHelpers' +import objectToMapFunction from './utils/objectToMapFunction' export { default as alt } from './funcs/alt' export { default as value } from './funcs/value' @@ -10,12 +11,12 @@ export { default as filter, FilterFunction } from './funcs/filter' export { Data } from './types' export function mapTransform (def: MapDefinition): DataMapperWithRev { - const mapFn = mapFunctionFromDef(def) + const mapFn = (isMapObject(def)) ? objectToMapFunction(def) : mapFunctionFromDef(def) return Object.assign( - compose(getValue, mapFn, populateState), + compose(getStateValue, mapFn, populateState), { - rev: compose(getValue, mapFn, populateRevState) + rev: compose(getStateValue, mapFn, populateRevState) } ) } diff --git a/src/tests/path-rev-test.ts b/src/tests/path-rev-test.ts index b98b1ce..1276c13 100644 --- a/src/tests/path-rev-test.ts +++ b/src/tests/path-rev-test.ts @@ -21,7 +21,7 @@ test('should reverse map simple object', (t) => { t.deepEqual(ret, expected) }) -test.skip('should reverse map array of objects', (t) => { +test('should reverse map array of objects', (t) => { const def = { title: 'content.heading', author: 'meta.writer.username' @@ -229,7 +229,7 @@ test('should return data when no mapping def and reverse mapping', (t) => { t.deepEqual(ret, expected) }) -test.skip('should return undefined when mapping def is empty', (t) => { +test('should return undefined when mapping def is empty', (t) => { const def = {} const data = [ { content: { heading: 'Heading 1' } }, diff --git a/src/tests/path-test.ts b/src/tests/path-test.ts index 208a156..f2c6f8c 100644 --- a/src/tests/path-test.ts +++ b/src/tests/path-test.ts @@ -143,6 +143,31 @@ test('should map with array index in middle of path', (t) => { t.deepEqual(ret, expected) }) +test('should map with value array', (t) => { + const def = { + data: { + 'items[]': [ + { + title: 'headline' + } + ] + } + } + const data = [{ headline: 'Entry 1' }, { headline: 'Entry 2' }] + const expected = { + data: { + items: [ + { title: 'Entry 1' }, + { title: 'Entry 2' } + ] + } + } + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + test('should return undefined from non-matching path with array index in middle', (t) => { const def = [ 'content.articles[0].content.heading' @@ -246,6 +271,37 @@ test('should map with nested mappings', (t) => { t.deepEqual(ret, expected) }) +test('should map array of objects', (t) => { + const def = { + content: { + heading: 'title' + }, + meta: { + writer: { + username: 'author' + } + } + } + const data = [ + { title: 'The heading', author: 'johnf' }, + { title: 'Second heading', author: 'maryk' } + ] + const expected = [ + { + content: { heading: 'The heading' }, + meta: { writer: { username: 'johnf' } } + }, + { + content: { heading: 'Second heading' }, + meta: { writer: { username: 'maryk' } } + } + ] + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + test('should return data when no mapping def', (t) => { const def = null const data = [ diff --git a/src/utils/objectToMapFunction.ts b/src/utils/objectToMapFunction.ts new file mode 100644 index 0000000..af8aef0 --- /dev/null +++ b/src/utils/objectToMapFunction.ts @@ -0,0 +1,35 @@ +import { compose, mergeDeepRight } from 'ramda' +import { MapDefinition, MapFunction, MapPipe, Path, State, Data } from '../types' +import { setStateValue, pipeMapFns, liftState, lowerState } from '../utils/stateHelpers' +import { set } from '../funcs/getSet' +import pipe from '../funcs/pipe' +import { isMapObject } from './definitionHelpers' + +const appendToPath = (path: string[], fragment: string) => [...path, fragment] + +const mergeToArray = (arr: Data[]) => (val: Data, index: number) => mergeDeepRight(arr[index], val) + +const runAndMergeState = (fn: MapFunction) => (state: State): State => { + const nextState = fn(lowerState(state)) + + return (Array.isArray(nextState.value)) + ? setStateValue(state, (Array.isArray(state.value)) ? nextState.value.map(mergeToArray(state.value)) : nextState.value) + : setStateValue(state, mergeDeepRight(state.value, nextState.value)) +} + +const concatToArray = (existing: MapPipe | Path | MapFunction, setFn: MapFunction) => + (Array.isArray(existing)) ? [...existing, setFn] : [existing, setFn] + +const pipeWithSetPath = (existing: MapPipe | Path | MapFunction, setFn: MapFunction) => + runAndMergeState(pipe(concatToArray(existing, setFn))) + +const extractSetFns = (def: MapDefinition, path: string[] = []): MapFunction[] => (isMapObject(def)) + ? Object.keys(def).reduce( + (sets: MapFunction[], key: string) => [ ...sets, ...extractSetFns(def[key], appendToPath(path, key)) ], + []) + : (def === null) ? [] : [pipeWithSetPath(def, set(path.join('.')))] + +export default function objectToMapFunction (def: MapDefinition): MapFunction { + const fns = extractSetFns(def) + return compose(pipeMapFns(fns), liftState) +} diff --git a/src/utils/stateHelpers.ts b/src/utils/stateHelpers.ts index 5d338c4..158c864 100644 --- a/src/utils/stateHelpers.ts +++ b/src/utils/stateHelpers.ts @@ -1,8 +1,8 @@ import { curry } from 'ramda' import { State, MapFunction, Data } from '../types' -export const setValue = curry((state: State, value: Data): State => ({ ...state, value })) -export const getValue = (state: State): Data => state.value +export const setStateValue = curry((state: State, value: Data): State => ({ ...state, value })) +export const getStateValue = (state: State): Data => state.value export const setValueFromState = (state: State, { value }: State) => ({ ...state, From 81d0b7f6df221635d181afdbc7f178bb30c56b80 Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Sat, 15 Sep 2018 18:03:07 +0200 Subject: [PATCH 06/13] Provide directional filters (fwd and rev) --- src/funcs/directionals-test.ts | 117 +++++++++++++++++++++++++++++++++ src/funcs/directionals.ts | 14 ++++ src/index.ts | 2 + src/tests/default-test.ts | 64 ++++++++---------- src/tests/filter-test.ts | 34 ++++++++-- src/tests/path-rev-test.ts | 68 +++---------------- src/tests/path-test.ts | 32 ++++++++- src/utils/stateHelpers.ts | 3 +- 8 files changed, 231 insertions(+), 103 deletions(-) create mode 100644 src/funcs/directionals-test.ts create mode 100644 src/funcs/directionals.ts diff --git a/src/funcs/directionals-test.ts b/src/funcs/directionals-test.ts new file mode 100644 index 0000000..1032d67 --- /dev/null +++ b/src/funcs/directionals-test.ts @@ -0,0 +1,117 @@ +import test from 'ava' +import { MapFunction } from '../types' + +import { fwd, rev } from './directionals' + +// Helpers + +const upperCase = (str: string | any) => (typeof str === 'string') ? str.toUpperCase() : str +const upper: MapFunction = (state) => ({ ...state, value: upperCase(state.value) }) + +// Tests -- forward + +test('should apply function when not rev', (t) => { + const state = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'Entry 1', + rev: false + } + const expected = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'ENTRY 1', + rev: false + } + + const ret = fwd(upper)(state) + + t.deepEqual(ret, expected) +}) + +test('should not apply function when rev', (t) => { + const state = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'Entry 1', + rev: true + } + const expected = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'Entry 1', + rev: true + } + + const ret = fwd(upper)(state) + + t.deepEqual(ret, expected) +}) + +test('should treat string as get path in fwd', (t) => { + const state = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: { title: 'Entry 1' }, + rev: false + } + const expectedValue = 'Entry 1' + + const ret = fwd('title')(state) + + t.deepEqual(ret.value, expectedValue) +}) + +// Tests -- reverse + +test('should apply function when rev', (t) => { + const state = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'Entry 1', + rev: true + } + const expected = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'ENTRY 1', + rev: true + } + + const ret = rev(upper)(state) + + t.deepEqual(ret, expected) +}) + +test('should not apply function when fwd', (t) => { + const state = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'Entry 1', + rev: false + } + const expected = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'Entry 1', + rev: false + } + + const ret = rev(upper)(state) + + t.deepEqual(ret, expected) +}) + +test('should treat string as get path in rev', (t) => { + const state = { + root: 'Entry 1', + context: 'Entry 1', + value: 'Entry 1', + rev: true + } + const expectedValue = { title: 'Entry 1' } + + const ret = rev('title')(state) + + t.deepEqual(ret.value, expectedValue) +}) diff --git a/src/funcs/directionals.ts b/src/funcs/directionals.ts new file mode 100644 index 0000000..8fb9b5c --- /dev/null +++ b/src/funcs/directionals.ts @@ -0,0 +1,14 @@ +import { MapDefinition, MapFunction } from '../types' +import { mapFunctionFromDef } from '../utils/definitionHelpers' + +const applyInDirection = (def: MapDefinition, rev: boolean): MapFunction => { + const fn = mapFunctionFromDef(def) + return (state) => (rev ? state.rev : !state.rev) ? fn(state) : state +} +export function fwd (def: MapDefinition): MapFunction { + return applyInDirection(def, false) +} + +export function rev (def: MapDefinition): MapFunction { + return applyInDirection(def, true) +} diff --git a/src/index.ts b/src/index.ts index f0fe43a..fb11a98 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,10 +4,12 @@ import { mapFunctionFromDef, isMapObject } from './utils/definitionHelpers' import { populateState, populateRevState, getStateValue } from './utils/stateHelpers' import objectToMapFunction from './utils/objectToMapFunction' +export { get, set } from './funcs/getSet' export { default as alt } from './funcs/alt' export { default as value } from './funcs/value' export { default as transform, TransformFunction } from './funcs/transform' export { default as filter, FilterFunction } from './funcs/filter' +export { fwd, rev } from './funcs/directionals' export { Data } from './types' export function mapTransform (def: MapDefinition): DataMapperWithRev { diff --git a/src/tests/default-test.ts b/src/tests/default-test.ts index 211e7c6..e8b4f6b 100644 --- a/src/tests/default-test.ts +++ b/src/tests/default-test.ts @@ -1,7 +1,7 @@ import test from 'ava' import { get } from '../funcs/getSet' -import { mapTransform, alt, value } from '..' +import { mapTransform, alt, value, fwd, rev } from '..' test('should use default value', (t) => { const def = { @@ -63,44 +63,42 @@ test('should set missing values to undefined when no default', (t) => { t.deepEqual(ret, expected) }) -test.skip('should use defaultRev value', (t) => { +test('should use directional default value - forward', (t) => { const def = { - mapping: { - title: { - path: 'content.heading', - default: 'Wrong way', - defaultRev: 'Default heading' - } - } + title: [ + 'content.heading', + fwd(alt(value('Default heading'))), + rev(alt(value('Wrong way'))) + ] } const data = [ {}, - { title: 'From data' } + { content: { heading: 'From data' } } ] const expected = [ - { content: { heading: 'Default heading' } }, - { content: { heading: 'From data' } } + { title: 'Default heading' }, + { title: 'From data' } ] - const ret = mapTransform(def).rev(data) + const ret = mapTransform(def)(data) t.deepEqual(ret, expected) }) -test.skip('should set missing value to undefined when no defaultRev', (t) => { +test('should use directional default value - reverse', (t) => { const def = { - mapping: { - title: { - path: 'content.heading' - } - } + title: [ + 'content.heading', + fwd(alt(value('Wrong way'))), + rev(alt(value('Default heading'))) + ] } const data = [ {}, { title: 'From data' } ] const expected = [ - { content: { heading: undefined } }, + { content: { heading: 'Default heading' } }, { content: { heading: 'From data' } } ] @@ -111,13 +109,10 @@ test.skip('should set missing value to undefined when no defaultRev', (t) => { test.skip('should not use default values', (t) => { const def = { - mapping: { - title: { - path: 'content.heading', - default: 'Default heading', - defaultRev: 'Wrong way' - } - } + title: [ + 'content.heading', + alt(value('Default heading')) + ] } const data = [ { content: {} }, @@ -135,9 +130,7 @@ test.skip('should not use default values', (t) => { test.skip('should not set missing prop to undefined', (t) => { const def = { - mapping: { - title: 'content.heading' - } + title: 'content.heading' } const data = [ { content: {} }, @@ -155,13 +148,10 @@ test.skip('should not set missing prop to undefined', (t) => { test.skip('should not use default values on rev', (t) => { const def = { - mapping: { - title: { - path: 'content.heading', - default: 'Wrong way', - defaultRev: 'Default heading' - } - } + title: [ + 'content.heading', + alt(value('Default heading')) + ] } const data = [ {}, diff --git a/src/tests/filter-test.ts b/src/tests/filter-test.ts index 8637e74..639b86f 100644 --- a/src/tests/filter-test.ts +++ b/src/tests/filter-test.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { mapTransform, filter, FilterFunction, Data } from '..' +import { mapTransform, filter, fwd, rev, FilterFunction, Data } from '..' // Helpers @@ -145,14 +145,38 @@ test('should filter on reverse mapping', (t) => { t.deepEqual(ret, expected) }) -test.skip('should filter with filterRev on reverse mapping', (t) => { +test('should use directional filters - going forward', (t) => { const def = [ { title: 'content.heading' }, - filter(noHeadingTitle), - filter(noAlso), - filter(noHeadingTitle) // filterRev + fwd(filter(noAlso)), + rev(filter(noHeadingTitle)) + ] + const data = [ + { content: { heading: 'The heading' } }, + { content: { heading: 'Just this' } }, + { content: { heading: 'Another heading' } }, + { content: { heading: 'Also this' } } + ] + const expected = [ + { title: 'The heading' }, + { title: 'Just this' }, + { title: 'Another heading' } + ] + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + +test('should use directional filters - going reverse', (t) => { + const def = [ + { + title: 'content.heading' + }, + fwd(filter(noAlso)), + rev(filter(noHeadingTitle)) ] const data = [ { title: 'The heading' }, diff --git a/src/tests/path-rev-test.ts b/src/tests/path-rev-test.ts index 1276c13..d2f3b6a 100644 --- a/src/tests/path-rev-test.ts +++ b/src/tests/path-rev-test.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { mapTransform } from '..' +import { mapTransform, get, set, fwd, rev } from '..' test('should reverse map simple object', (t) => { const def = { @@ -138,16 +138,16 @@ test('should reverse map with nested mapping', (t) => { t.deepEqual(ret, expected) }) -test.skip('should reverse map with object pathRev and pathToRev', (t) => { - const def = { - mapping: { - title: { path: 'content.heading' } +test('should reverse map with directional paths', (t) => { + const def = [ + fwd(get('wrong.path')), + rev(get('content.articles')), + { + title: 'content.heading' }, - path: 'wrong.path', - pathRev: 'content.articles', - pathTo: 'wrong.path', - pathToRev: 'items' - } + fwd(set('wrong.path')), + rev(set('items')) + ] const data = { items: [ { title: 'Heading 1' }, @@ -168,54 +168,6 @@ test.skip('should reverse map with object pathRev and pathToRev', (t) => { t.deepEqual(ret, expected) }) -test.skip('should reverse map with object pathFromRev', (t) => { - const def = { - mapping: { - title: { path: 'content.heading' } - }, - pathFrom: 'wrong.path', - pathFromRev: 'content.articles' - } - const data = [ - { title: 'Heading 1' } - ] - const expected = { - content: { - articles: [ - { content: { heading: 'Heading 1' } } - ] - } - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should reverse map with empty pathRev and pathToRev', (t) => { - const def = { - mapping: { - title: { path: 'content.heading' } - }, - path: 'wrong.path', - pathRev: null, - pathTo: 'wrong.path', - pathToRev: null - } - const data = [ - { title: 'Heading 1' }, - { title: 'Heading 2' } - ] - const expected = [ - { content: { heading: 'Heading 1' } }, - { content: { heading: 'Heading 2' } } - ] - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - test('should return data when no mapping def and reverse mapping', (t) => { const def = null const data = [ diff --git a/src/tests/path-test.ts b/src/tests/path-test.ts index f2c6f8c..d2f3532 100644 --- a/src/tests/path-test.ts +++ b/src/tests/path-test.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { mapTransform } from '..' +import { mapTransform, get, set, fwd, rev } from '..' test('should map with object path', (t) => { const def = [ @@ -271,6 +271,36 @@ test('should map with nested mappings', (t) => { t.deepEqual(ret, expected) }) +test('should forward map with directional paths', (t) => { + const def = [ + fwd(get('content.articles')), + rev(get('wrong.path')), + { + title: 'content.heading' + }, + fwd(set('items')), + rev(set('wrong.path')) + ] + const data = { + content: { + articles: [ + { content: { heading: 'Heading 1' } }, + { content: { heading: 'Heading 2' } } + ] + } + } + const expected = { + items: [ + { title: 'Heading 1' }, + { title: 'Heading 2' } + ] + } + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + test('should map array of objects', (t) => { const def = { content: { diff --git a/src/utils/stateHelpers.ts b/src/utils/stateHelpers.ts index 158c864..24f1078 100644 --- a/src/utils/stateHelpers.ts +++ b/src/utils/stateHelpers.ts @@ -1,7 +1,6 @@ -import { curry } from 'ramda' import { State, MapFunction, Data } from '../types' -export const setStateValue = curry((state: State, value: Data): State => ({ ...state, value })) +export const setStateValue = (state: State, value: Data): State => ({ ...state, value }) export const getStateValue = (state: State): Data => state.value export const setValueFromState = (state: State, { value }: State) => ({ From bfa39a708c6aec9cb50e3bd879376eee40f5565a Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Sat, 15 Sep 2018 18:43:52 +0200 Subject: [PATCH 07/13] Allow dedicated reverse transform function --- src/funcs/transform-test.ts | 58 +++++++++++++ src/funcs/transform.ts | 23 +++-- src/tests/transform-test.ts | 169 ++++-------------------------------- 3 files changed, 88 insertions(+), 162 deletions(-) diff --git a/src/funcs/transform-test.ts b/src/funcs/transform-test.ts index 03ac72d..808718d 100644 --- a/src/funcs/transform-test.ts +++ b/src/funcs/transform-test.ts @@ -5,6 +5,7 @@ import transform, { TransformFunction } from './transform' // Helpers const upper: TransformFunction = (str) => (typeof str === 'string') ? str.toUpperCase() : str +const lower: TransformFunction = (str) => (typeof str === 'string') ? str.toLowerCase() : str // Tests @@ -36,3 +37,60 @@ test('should not touch value run with anything else than a function', (t) => { t.deepEqual(ret, state) }) + +test('should run transform in reverse', (t) => { + const state = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'Entry 1', + rev: true + } + const expected = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'ENTRY 1', + rev: true + } + + const ret = transform(upper)(state) + + t.deepEqual(ret, expected) +}) + +test('should run dedicated transform in reverse', (t) => { + const state = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'Entry 1', + rev: true + } + const expected = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'entry 1', + rev: true + } + + const ret = transform(upper, lower)(state) + + t.deepEqual(ret, expected) +}) + +test('should not mind dedicated reverse transform going forward', (t) => { + const state = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'Entry 1', + rev: false + } + const expected = { + root: { title: 'Entry 1' }, + context: { title: 'Entry 1' }, + value: 'ENTRY 1', + rev: false + } + + const ret = transform(upper, lower)(state) + + t.deepEqual(ret, expected) +}) diff --git a/src/funcs/transform.ts b/src/funcs/transform.ts index 08c3fdf..80242b0 100644 --- a/src/funcs/transform.ts +++ b/src/funcs/transform.ts @@ -1,20 +1,19 @@ -import { compose, identity } from 'ramda' -import { Data, MapFunction, State } from '../types' -import { getStateValue } from '../utils/stateHelpers' +import { compose, identity, converge } from 'ramda' +import { Data, MapFunction } from '../types' +import { getStateValue, setStateValue } from '../utils/stateHelpers' export interface TransformFunction { (data: Data): Data } -export default function transform (fn: TransformFunction): MapFunction { - if (typeof fn !== 'function') { - return identity - } +export default function transform (fn: TransformFunction, revFn?: TransformFunction): MapFunction { + const fwdTransform = (typeof fn === 'function') + ? converge(setStateValue, [identity, compose(fn, getStateValue)]) + : identity - const runTransform = compose(fn, getStateValue) + const revTransform = (typeof revFn === 'function') + ? converge(setStateValue, [identity, compose(revFn, getStateValue)]) + : fwdTransform - return (state: State) => ({ - ...state, - value: runTransform(state) - }) + return (state) => (state.rev) ? revTransform(state) : fwdTransform(state) } diff --git a/src/tests/transform-test.ts b/src/tests/transform-test.ts index a462656..95ad307 100644 --- a/src/tests/transform-test.ts +++ b/src/tests/transform-test.ts @@ -1,46 +1,38 @@ import test from 'ava' -import { mapTransform, transform, TransformFunction, Data } from '..' +import { mapTransform, transform, rev, TransformFunction, Data } from '..' // Helpers const isObject = (item: Data): item is object => (!!item && typeof item === 'object') const createTitle = (item: { title: string, author: string }) => `${item.title} - by ${item.author}` +const removeAuthor = (item: { title: string, author: string }) => (item.title.endsWith(` - by ${item.author}`)) + ? item.title.substr(0, item.title.length - 6 - item.author.length) + : item.title const appendAuthorToTitle: TransformFunction = (item) => (isObject(item)) ? { ...item, title: createTitle(item as any) } : item -// appendAuthorToTitle.rev = (item: {title: string, author: string}) => -// ({ -// ...item, -// title: (item.title.endsWith(` - by ${item.author}`)) -// ? item.title.substr(0, item.title.length - 6 - item.author.length) -// : item.title -// }) +const removeAuthorFromTitle: TransformFunction = (item) => (isObject(item)) + ? ({ ...item, title: removeAuthor(item as any) }) + : item const setActive: TransformFunction = (item) => (isObject(item)) ? { ...item, active: true } : item -// setActive.rev = (item: {}) => ({ ...item, active: false }) +const prepareAuthorName = ({ author }: { author: string }) => + `${author[0].toUpperCase()}${author.substr(1)}.` -// const prepareAuthorName = ({ author }: { author: string }) => -// `${author[0].toUpperCase()}${author.substr(1)}.` -// -// const setAuthorName: TransformFunction = (item) => (isObject(item)) -// ? ({ ...item, authorName: prepareAuthorName(item as any) }) -// : item +const setAuthorName: TransformFunction = (item) => (isObject(item)) + ? ({ ...item, authorName: prepareAuthorName(item as any) }) + : item const appendEllipsis: TransformFunction = (str) => (typeof str === 'string') ? str + ' ...' : str -// appendEllipsis.rev = (str: string) => -// (str.endsWith(' ...')) ? str.substr(0, str.length - 4) : str -// const upperCase: TransformFunction = (str: string) => str.toUpperCase() + const getLength: TransformFunction = (str) => (typeof str === 'string') ? str.length : 0 -// const enclose: TransformFunction = (str: string) => `(${str})` -// enclose.rev = (str: string) => (str.startsWith('(') && str.endsWith(')')) -// ? str.substr(1, str.length - 2) : str // Tests @@ -90,15 +82,14 @@ test('should map simple object with several transform functions', (t) => { t.deepEqual(ret, expected) }) -test.skip('should reverse map simple object with transformRev', (t) => { +test('should reverse map simple object with rev transform', (t) => { const def = [ - // transform: [ appendAuthorToTitle, setActive ], - // transformRev: [ setAuthorName ], { title: 'content.heading', author: 'meta.writer.username', authorName: 'meta.writer.name' - } + }, + rev(transform(setAuthorName)) ] const data = { title: 'The heading', @@ -114,14 +105,13 @@ test.skip('should reverse map simple object with transformRev', (t) => { t.deepEqual(ret, expected) }) -test.skip('should reverse map simple object with transform rev props', (t) => { +test('should reverse map simple object with dedicated rev transform', (t) => { const def = [ - transform(appendAuthorToTitle), - transform(setActive), { title: 'content.heading', author: 'meta.writer.username' - } + }, + transform(appendAuthorToTitle, removeAuthorFromTitle) ] const data = { title: 'The heading - by johnf', @@ -168,82 +158,6 @@ test('should transform beofre data is set on outer path', (t) => { t.deepEqual(ret, expected) }) -test.skip('should reverse transform after pathTo and before path', (t) => { - const def = { - attributes: [ - 'result.data', - { - title: 'content.heading', - author: 'meta.writer.username', - authorName: 'meta.writer.name' - } - // transform: [ appendAuthorToTitle, setActive ], - // transformRev: [ setAuthorName ] - ] - } - const data = { - attributes: { - title: 'The heading', - author: 'johnf' - } - } - const expected = { - result: { - data: { - content: { heading: 'The heading' }, - meta: { writer: { username: 'johnf', name: 'Johnf.' } } - } - } - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should transform with array', (t) => { - const def = [ - // transform: [ setActive ], - { - title: 'content.heading' - } - ] - const data = [ - { content: { heading: 'The heading' } }, - { content: { heading: 'Another heading' } } - ] - const expected = [ - { title: 'The heading', active: true }, - { title: 'Another heading', active: true } - ] - - const ret = mapTransform(def)(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should reverse transform with array', (t) => { - const def = [ - // transform: [ setActive ], - { - title: 'content.heading', - active: 'content.active' - } - ] - const data = [ - { title: 'The heading', active: true }, - { title: 'Another heading', active: true } - ] - const expected = [ - { content: { heading: 'The heading', active: false } }, - { content: { heading: 'Another heading', active: false } } - ] - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - test('should transform before mapping', (t) => { const def = [ transform(setActive), @@ -265,51 +179,6 @@ test('should transform before mapping', (t) => { t.deepEqual(ret, expected) }) -test.skip('should reverse map with transformFrom function', (t) => { - const def = [ - // transformFrom: setActive, - { - title: 'content.heading' - } - ] - const data = { - title: 'The heading' - } - const expected = { - content: { - heading: 'The heading' - }, - active: false - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - -test.skip('should reverse map with transformFromRev function', (t) => { - const def = [ - { - title: 'content.heading' - } - // transformFrom: setActive, - // transformFromRev: setActive - ] - const data = { - title: 'The heading' - } - const expected = { - content: { - heading: 'The heading' - }, - active: true - } - - const ret = mapTransform(def).rev(data) - - t.deepEqual(ret, expected) -}) - test('should apply transform functions from left to right', (t) => { const def = [ { From 9ce03b790b87f6279eca73ef3189caa085ba29e1 Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Sat, 15 Sep 2018 18:48:38 +0200 Subject: [PATCH 08/13] Update deps --- package-lock.json | 3330 +++++++++++---------------------------------- package.json | 12 +- 2 files changed, 827 insertions(+), 2515 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a16ae4..258ec23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,1163 +5,343 @@ "requires": true, "dependencies": { "@ava/babel-plugin-throws-helper": { - "version": "3.0.0-beta.7", - "resolved": "https://registry.npmjs.org/@ava/babel-plugin-throws-helper/-/babel-plugin-throws-helper-3.0.0-beta.7.tgz", - "integrity": "sha512-1nCjzg5iRbxwuI1F31bh6k4JxlgJnhKxhKpdZCC28v8Hl0STonIudeNcqgTvGMA2uTiWTvmyFYU3ZqTJHoJuCA==", - "dev": true - }, - "@ava/babel-preset-stage-4": { - "version": "2.0.0-beta.8", - "resolved": "https://registry.npmjs.org/@ava/babel-preset-stage-4/-/babel-preset-stage-4-2.0.0-beta.8.tgz", - "integrity": "sha512-5z0YY5DXT0Cjaq1Wa7bt5OIGwMhq+M1Dhk1kG7c2d1AJyzoFCxvLWrAjzEnN660Ur5LKgNZUcNXqVMR9mzFdyQ==", - "dev": true, - "requires": { - "@babel/plugin-proposal-async-generator-functions": "7.0.0-beta.51", - "@babel/plugin-proposal-object-rest-spread": "7.0.0-beta.51", - "@babel/plugin-proposal-optional-catch-binding": "7.0.0-beta.51", - "@babel/plugin-transform-async-to-generator": "7.0.0-beta.51", - "@babel/plugin-transform-dotall-regex": "7.0.0-beta.51", - "@babel/plugin-transform-exponentiation-operator": "7.0.0-beta.51", - "@babel/plugin-transform-modules-commonjs": "7.0.0-beta.51" - } - }, - "@ava/babel-preset-transform-test-files": { - "version": "4.0.0-beta.7", - "resolved": "https://registry.npmjs.org/@ava/babel-preset-transform-test-files/-/babel-preset-transform-test-files-4.0.0-beta.7.tgz", - "integrity": "sha512-c9OjmbSueJlqY6hvb6TnZkaIdo1bvyKltKzElBW+RxP7SxmFUK8wOENMJ7TZD8RyOhifu0Ql70TfPR06THqDQw==", - "dev": true, - "requires": { - "@ava/babel-plugin-throws-helper": "3.0.0-beta.7", - "babel-plugin-espower": "3.0.0-beta.1" - } - }, - "@ava/write-file-atomic": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ava/write-file-atomic/-/write-file-atomic-2.2.0.tgz", - "integrity": "sha512-BTNB3nGbEfJT+69wuqXFr/bQH7Vr7ihx2xGOMNqPgDGhwspoZhiWumDDZNjBy7AScmqS5CELIOGtPVXESyrnDA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "slide": "^1.1.5" - } - }, - "@babel/code-frame": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.49.tgz", - "integrity": "sha1-vs2AVIJzREDJ0TfkbXc0DmTX9Rs=", - "dev": true, - "requires": { - "@babel/highlight": "7.0.0-beta.49" - } - }, - "@babel/core": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.0-beta.51.tgz", - "integrity": "sha1-DlS9a2OHNrKuWTwxpH8JaeKyuW0=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/generator": "7.0.0-beta.51", - "@babel/helpers": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "convert-source-map": "^1.1.0", - "debug": "^3.1.0", - "json5": "^0.5.0", - "lodash": "^4.17.5", - "micromatch": "^3.1.10", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz", - "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=", - "dev": true, - "requires": { - "@babel/highlight": "7.0.0-beta.51" - } - }, - "@babel/generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.51.tgz", - "integrity": "sha1-bHV1/952HQdIXgS67cA5LG2eMPY=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51", - "jsesc": "^2.5.1", - "lodash": "^4.17.5", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.51.tgz", - "integrity": "sha1-IbSHSiJ8+Z7K/MMKkDAtpaJkBWE=", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.51.tgz", - "integrity": "sha1-MoGy0EWvlcFyzpGyCCXYXqRnZBE=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.51.tgz", - "integrity": "sha1-imw/ZsTSZTUvwHdIT59ugKUauXg=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/highlight": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz", - "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" - } - }, - "@babel/parser": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.51.tgz", - "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", - "dev": true - }, - "@babel/template": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz", - "integrity": "sha1-lgKkCuvPNXrpZ34lMu9fyBD1+/8=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "lodash": "^4.17.5" - } - }, - "@babel/traverse": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.51.tgz", - "integrity": "sha1-mB2vLOw0emIx06odnhgDsDqqpKg=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/generator": "7.0.0-beta.51", - "@babel/helper-function-name": "7.0.0-beta.51", - "@babel/helper-split-export-declaration": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "debug": "^3.1.0", - "globals": "^11.1.0", - "invariant": "^2.2.0", - "lodash": "^4.17.5" - } - }, - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" - } - }, - "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", - "dev": true - }, - "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.49.tgz", - "integrity": "sha1-6c/9qROZaszseTu8JauRvBnQv3o=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.49", - "jsesc": "^2.5.1", - "lodash": "^4.17.5", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", - "dev": true - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-beta.51.tgz", - "integrity": "sha1-OM95IL9fM4oif3VOKGtvut7gS1g=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - }, - "dependencies": { - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0-beta.51.tgz", - "integrity": "sha1-ITP//j4vcVkeQhR7lHKRyirTkjc=", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" - }, - "dependencies": { - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0-beta.51.tgz", - "integrity": "sha1-mHUzKti11cmC+kgcuCtzFwPyzS0=", - "dev": true, - "requires": { - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz", - "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=", - "dev": true, - "requires": { - "@babel/highlight": "7.0.0-beta.51" - } - }, - "@babel/generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.51.tgz", - "integrity": "sha1-bHV1/952HQdIXgS67cA5LG2eMPY=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51", - "jsesc": "^2.5.1", - "lodash": "^4.17.5", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.51.tgz", - "integrity": "sha1-IbSHSiJ8+Z7K/MMKkDAtpaJkBWE=", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.51.tgz", - "integrity": "sha1-MoGy0EWvlcFyzpGyCCXYXqRnZBE=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.51.tgz", - "integrity": "sha1-imw/ZsTSZTUvwHdIT59ugKUauXg=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/highlight": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz", - "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" - } - }, - "@babel/parser": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.51.tgz", - "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", - "dev": true - }, - "@babel/template": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz", - "integrity": "sha1-lgKkCuvPNXrpZ34lMu9fyBD1+/8=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "lodash": "^4.17.5" - } - }, - "@babel/traverse": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.51.tgz", - "integrity": "sha1-mB2vLOw0emIx06odnhgDsDqqpKg=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/generator": "7.0.0-beta.51", - "@babel/helper-function-name": "7.0.0-beta.51", - "@babel/helper-split-export-declaration": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "debug": "^3.1.0", - "globals": "^11.1.0", - "invariant": "^2.2.0", - "lodash": "^4.17.5" - } - }, - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" - } - }, - "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", - "dev": true - }, - "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, - "@babel/helper-function-name": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.49.tgz", - "integrity": "sha1-olwRGbnwNSeGcBJuAiXAMEHI3jI=", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.49", - "@babel/template": "7.0.0-beta.49", - "@babel/types": "7.0.0-beta.49" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.49.tgz", - "integrity": "sha1-z1Aj8y0q2S0Ic3STnOwJUby1FEE=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.49" - } - }, - "@babel/helper-module-imports": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.51.tgz", - "integrity": "sha1-zgBCgEX7t9XrwOp7+DV4nxU2arI=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51", - "lodash": "^4.17.5" - }, - "dependencies": { - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, - "@babel/helper-module-transforms": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-beta.51.tgz", - "integrity": "sha1-E68MjuQfJ3dDyPxD1EQxXbIyb3M=", - "dev": true, - "requires": { - "@babel/helper-module-imports": "7.0.0-beta.51", - "@babel/helper-simple-access": "7.0.0-beta.51", - "@babel/helper-split-export-declaration": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "lodash": "^4.17.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz", - "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=", - "dev": true, - "requires": { - "@babel/highlight": "7.0.0-beta.51" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.51.tgz", - "integrity": "sha1-imw/ZsTSZTUvwHdIT59ugKUauXg=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/highlight": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz", - "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" - } - }, - "@babel/parser": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.51.tgz", - "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", - "dev": true - }, - "@babel/template": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz", - "integrity": "sha1-lgKkCuvPNXrpZ34lMu9fyBD1+/8=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "lodash": "^4.17.5" - } - }, - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, - "@babel/helper-plugin-utils": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-beta.51.tgz", - "integrity": "sha1-D2pfK20cZERBP4+rYJQNebY8IDE=", - "dev": true - }, - "@babel/helper-regex": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0-beta.51.tgz", - "integrity": "sha1-mXIqPAxwRZavsSMoSwqIihoAPYI=", - "dev": true, - "requires": { - "lodash": "^4.17.5" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-beta.51.tgz", - "integrity": "sha1-DtxX4F3LXd4qC27m+NAmGYLe8l8=", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "7.0.0-beta.51", - "@babel/helper-wrap-function": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz", - "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=", - "dev": true, - "requires": { - "@babel/highlight": "7.0.0-beta.51" - } - }, - "@babel/generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.51.tgz", - "integrity": "sha1-bHV1/952HQdIXgS67cA5LG2eMPY=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51", - "jsesc": "^2.5.1", - "lodash": "^4.17.5", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.51.tgz", - "integrity": "sha1-IbSHSiJ8+Z7K/MMKkDAtpaJkBWE=", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.51.tgz", - "integrity": "sha1-MoGy0EWvlcFyzpGyCCXYXqRnZBE=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.51.tgz", - "integrity": "sha1-imw/ZsTSZTUvwHdIT59ugKUauXg=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/highlight": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz", - "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" - } - }, - "@babel/parser": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.51.tgz", - "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", - "dev": true - }, - "@babel/template": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz", - "integrity": "sha1-lgKkCuvPNXrpZ34lMu9fyBD1+/8=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "lodash": "^4.17.5" - } - }, - "@babel/traverse": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.51.tgz", - "integrity": "sha1-mB2vLOw0emIx06odnhgDsDqqpKg=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/generator": "7.0.0-beta.51", - "@babel/helper-function-name": "7.0.0-beta.51", - "@babel/helper-split-export-declaration": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "debug": "^3.1.0", - "globals": "^11.1.0", - "invariant": "^2.2.0", - "lodash": "^4.17.5" - } - }, - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" - } - }, - "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", - "dev": true - }, - "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, - "@babel/helper-simple-access": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.51.tgz", - "integrity": "sha1-ydf+zYShgdUKOvzEIvyUqWi+MFA=", - "dev": true, - "requires": { - "@babel/template": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "lodash": "^4.17.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz", - "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=", - "dev": true, - "requires": { - "@babel/highlight": "7.0.0-beta.51" - } - }, - "@babel/highlight": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz", - "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" - } - }, - "@babel/parser": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.51.tgz", - "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", - "dev": true - }, - "@babel/template": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz", - "integrity": "sha1-lgKkCuvPNXrpZ34lMu9fyBD1+/8=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "lodash": "^4.17.5" - } - }, - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.49.tgz", - "integrity": "sha1-QNeO2glo0BGxxShm5XRs+yPldUg=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.49" - } - }, - "@babel/helper-wrap-function": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-beta.51.tgz", - "integrity": "sha1-bFFvsEQQmWTuAxwiUAqDAxOGL7E=", - "dev": true, - "requires": { - "@babel/helper-function-name": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz", - "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=", - "dev": true, - "requires": { - "@babel/highlight": "7.0.0-beta.51" - } - }, - "@babel/generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.51.tgz", - "integrity": "sha1-bHV1/952HQdIXgS67cA5LG2eMPY=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51", - "jsesc": "^2.5.1", - "lodash": "^4.17.5", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.51.tgz", - "integrity": "sha1-IbSHSiJ8+Z7K/MMKkDAtpaJkBWE=", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.51.tgz", - "integrity": "sha1-MoGy0EWvlcFyzpGyCCXYXqRnZBE=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.51.tgz", - "integrity": "sha1-imw/ZsTSZTUvwHdIT59ugKUauXg=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/highlight": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz", - "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" - } - }, - "@babel/parser": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.51.tgz", - "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", - "dev": true - }, - "@babel/template": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz", - "integrity": "sha1-lgKkCuvPNXrpZ34lMu9fyBD1+/8=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "lodash": "^4.17.5" - } - }, - "@babel/traverse": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.51.tgz", - "integrity": "sha1-mB2vLOw0emIx06odnhgDsDqqpKg=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/generator": "7.0.0-beta.51", - "@babel/helper-function-name": "7.0.0-beta.51", - "@babel/helper-split-export-declaration": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "debug": "^3.1.0", - "globals": "^11.1.0", - "invariant": "^2.2.0", - "lodash": "^4.17.5" - } - }, - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" - } - }, - "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", - "dev": true - }, - "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } - } - }, - "@babel/helpers": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0-beta.51.tgz", - "integrity": "sha1-lScr4qtGNNaCBCX4klAxqSiRg5c=", - "dev": true, - "requires": { - "@babel/template": "7.0.0-beta.51", - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz", - "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=", - "dev": true, - "requires": { - "@babel/highlight": "7.0.0-beta.51" - } - }, - "@babel/generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.51.tgz", - "integrity": "sha1-bHV1/952HQdIXgS67cA5LG2eMPY=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51", - "jsesc": "^2.5.1", - "lodash": "^4.17.5", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/helper-function-name": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.51.tgz", - "integrity": "sha1-IbSHSiJ8+Z7K/MMKkDAtpaJkBWE=", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.51.tgz", - "integrity": "sha1-MoGy0EWvlcFyzpGyCCXYXqRnZBE=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.51.tgz", - "integrity": "sha1-imw/ZsTSZTUvwHdIT59ugKUauXg=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51" - } - }, - "@babel/highlight": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz", - "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" - } - }, - "@babel/parser": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.51.tgz", - "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", - "dev": true - }, - "@babel/template": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz", - "integrity": "sha1-lgKkCuvPNXrpZ34lMu9fyBD1+/8=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "lodash": "^4.17.5" - } - }, - "@babel/traverse": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.51.tgz", - "integrity": "sha1-mB2vLOw0emIx06odnhgDsDqqpKg=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/generator": "7.0.0-beta.51", - "@babel/helper-function-name": "7.0.0-beta.51", - "@babel/helper-split-export-declaration": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", - "debug": "^3.1.0", - "globals": "^11.1.0", - "invariant": "^2.2.0", - "lodash": "^4.17.5" - } - }, - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" - } - }, - "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", - "dev": true - }, + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@ava/babel-plugin-throws-helper/-/babel-plugin-throws-helper-3.0.0.tgz", + "integrity": "sha512-mN9UolOs4WX09QkheU1ELkVy2WPnwonlO3XMdN8JF8fQqRVgVTR21xDbvEOUsbwz6Zwjq7ji9yzyjuXqDPalxg==", + "dev": true + }, + "@ava/babel-preset-stage-4": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ava/babel-preset-stage-4/-/babel-preset-stage-4-2.0.0.tgz", + "integrity": "sha512-OWqMYeTSZ16AfLx0Vn0Uj7tcu+uMRlbKmks+DVCFlln7vomVsOtst+Oz+HCussDSFGpE+30VtHAUHLy6pLDpHQ==", + "dev": true, + "requires": { + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.0.0", + "@babel/plugin-transform-dotall-regex": "^7.0.0", + "@babel/plugin-transform-exponentiation-operator": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0" + } + }, + "@ava/babel-preset-transform-test-files": { + "version": "4.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@ava/babel-preset-transform-test-files/-/babel-preset-transform-test-files-4.0.0-beta.9.tgz", + "integrity": "sha512-22KVcr0Xr5q5TXlb5WVuEB5krG69XKOUHSrWkkq26O400ZYAlnh7FKB1Xf5hAiCQHX228gRp6yyYKG+OqeNgaw==", + "dev": true, + "requires": { + "@ava/babel-plugin-throws-helper": "^3.0.0", + "babel-plugin-espower": "3.0.0-beta.2" + } + }, + "@ava/write-file-atomic": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ava/write-file-atomic/-/write-file-atomic-2.2.0.tgz", + "integrity": "sha512-BTNB3nGbEfJT+69wuqXFr/bQH7Vr7ihx2xGOMNqPgDGhwspoZhiWumDDZNjBy7AScmqS5CELIOGtPVXESyrnDA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + }, + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.1.tgz", + "integrity": "sha512-7Yy2vRB6KYbhWeIrrwJmKv9UwDxokmlo43wi6AV84oNs4Gi71NTNGh3YxY/hK3+CxuSc6wcKSl25F2tQOhm1GQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.0.0", + "@babel/helpers": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "convert-source-map": "^1.1.0", + "debug": "^3.1.0", + "json5": "^0.5.0", + "lodash": "^4.17.10", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0.tgz", + "integrity": "sha512-/BM2vupkpbZXq22l1ALO7MqXJZH2k8bKVv8Y+pABFnzWdztDB/ZLveP5At21vLz5c2YtSE6p7j2FZEsqafMz5Q==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.10", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { "jsesc": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true } } }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0.tgz", + "integrity": "sha512-9HdU8lrAc4FUZOy+y2w//kUhynSpkGIRYDzJW1oKJx7+v8m6UEAbAd2tSvxirsq2kJTXJZZS6Eo8FnUDUH0ZWw==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.0.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0.tgz", + "integrity": "sha512-5gLPwdDnYf8GfPsjS+UmZUtYE1jaXTFm1P+ymGobqvXbA0q3ANgpH60+C6zDrRAWXYbQXYvzzQC/r0gJVNNltQ==", + "dev": true, + "requires": { + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0.tgz", + "integrity": "sha512-Zo+LGvfYp4rMtz84BLF3bavFTdf8y4rJtMPTe2J+rxYmnDOIeH8le++VFI/pRJU+rQhjqiXxE4LMaIau28Tv1Q==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0.tgz", + "integrity": "sha512-QdwmTTlPmT7TZcf30dnqm8pem+o48tVt991xXogE5CQCwqSpWKuzH2E9v8VWeccQ66a6/CmrLZ+bwp66JYeM5A==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.0.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/types": "^7.0.0", + "lodash": "^4.17.10" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0.tgz", + "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0.tgz", + "integrity": "sha512-3o4sYLOsK6m0A7t1P0saTanBPmk5MAlxVnp9773Of4L8PMVLukU7loZix5KoJgflxSo2c2ETTzseptc0rQEp7A==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0.tgz", + "integrity": "sha512-CNeuX52jbQSq4j1n+R+21xrjbTjsnXa9n1aERbgHRD/p9h4Udkxr1n24yPMQmnTETHdnQDvkVSYWFw/ETAymYg==", + "dev": true, + "requires": { + "@babel/template": "^7.0.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-wrap-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0.tgz", + "integrity": "sha512-kjprWPDNVPZ/9pyLRXcZBvfjnFwqokmXTPTaC4AV8Ns7WRl7ewSxrB19AWZzQsC/WSPQLOw1ciR8uPYkAM1znA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helpers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0.tgz", + "integrity": "sha512-jbvgR8iLZPnyk6m/UqdXYsSxbVtRi7Pd3CzB4OPwPBnmhNG1DWjiiy777NTuoyIcniszK51R40L5pgfXAfHDtw==", + "dev": true, + "requires": { + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0" + } + }, "@babel/highlight": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.49.tgz", - "integrity": "sha1-lr3GtD4TSCASumaRsQGEktOWIsw=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", - "js-tokens": "^3.0.0" + "js-tokens": "^4.0.0" + }, + "dependencies": { + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + } } }, "@babel/parser": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.49.tgz", - "integrity": "sha1-lE0MW6KBK7FZ7b0iZ0Ov0mUXm9w=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0.tgz", + "integrity": "sha512-RgJhNdRinpO8zibnoHbzTTexNs4c8ROkXFBanNDZTLHjwbdLk8J5cJSKulx/bycWTLYmKVNCkxRtVCoJnqPk+g==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0-beta.51.tgz", - "integrity": "sha1-99aS+Uakp/ynjkM2QHoAvq+KTeo=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0.tgz", + "integrity": "sha512-QsXmmjLrFADCcDQAfdQn7tfBRLjpTzRWaDpKpW4ZXW1fahPG4SvjcF1xfvVnXGC662RSExYXL+6DAqbtgqMXeA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/helper-remap-async-to-generator": "7.0.0-beta.51", - "@babel/plugin-syntax-async-generators": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.0.0", + "@babel/plugin-syntax-async-generators": "^7.0.0" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0-beta.51.tgz", - "integrity": "sha1-W8Rp5ebRuEpdYEa1npDKAWwghtY=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0.tgz", + "integrity": "sha512-14fhfoPcNu7itSen7Py1iGN0gEm87hX/B+8nZPqkdmANyyYWYMY2pjA3r8WXbWVKMzfnSNS0xY8GVS0IjXi/iw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/plugin-syntax-object-rest-spread": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0-beta.51.tgz", - "integrity": "sha1-PsxtKRnVLJTL+uhiXaM1ghAvs9Y=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0.tgz", + "integrity": "sha512-JPqAvLG1s13B/AuoBjdBYvn38RqW6n1TzrQO839/sIpqLpbnXKacsAgpZHzLD83Sm8SDXMkkrAvEnJ25+0yIpw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/plugin-syntax-optional-catch-binding": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.0.0" } }, "@babel/plugin-syntax-async-generators": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0-beta.51.tgz", - "integrity": "sha1-aSGvHcPaD87d4KYQc+7Hl7jKpwc=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0.tgz", + "integrity": "sha512-im7ged00ddGKAjcZgewXmp1vxSZQQywuQXe2B1A7kajjZmDeY/ekMPmWr9zJgveSaQH0k7BcGrojQhcK06l0zA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-object-rest-spread": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0-beta.51.tgz", - "integrity": "sha1-bVehGcHwZMRY5FutRb7wqD7RDAA=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0.tgz", + "integrity": "sha512-5A0n4p6bIiVe5OvQPxBnesezsgFJdHhSs3uFSvaPdMqtsovajLZ+G2vZyvNe10EzJBWWo3AcHGKhAFUxqwp2dw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0-beta.51.tgz", - "integrity": "sha1-ziZ1cgy0EkjCZDNRXJDJS50Bpv0=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0.tgz", + "integrity": "sha512-Wc+HVvwjcq5qBg1w5RG9o9RVzmCaAg/Vp0erHCKpAYV8La6I94o4GQAmFYNmkzoMO6gzoOSulpKeSSz6mPEoZw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0-beta.51.tgz", - "integrity": "sha1-lFOFBVoubTVmv1WvEnyNclzToXM=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0.tgz", + "integrity": "sha512-CiWNhSMZzj1n3uEKUUS/oL+a7Xi8hnPQB6GpC1WfL/ZYvxBLDBn14sHMo5EyOaeArccSonyk5jFIKMRRbrHOnQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "7.0.0-beta.51", - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/helper-remap-async-to-generator": "7.0.0-beta.51" + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.0.0" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0-beta.51.tgz", - "integrity": "sha1-mAVYoeX34ohQ9f/eIEBCkeKqM/s=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0.tgz", + "integrity": "sha512-00THs8eJxOJUFVx1w8i1MBF4XH4PsAjKjQ1eqN/uCH3YKwP21GCKfrn6YZFZswbOk9+0cw1zGQPHVc1KBlSxig==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/helper-regex": "7.0.0-beta.51", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", "regexpu-core": "^4.1.3" }, "dependencies": { @@ -1197,72 +377,70 @@ } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0-beta.51.tgz", - "integrity": "sha1-BLTj5As3AREt1u2jliUTJ1eIH9Q=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0.tgz", + "integrity": "sha512-Ig74elCuFQ0mvHkWUq5qDCNI3qHWlop5w4TcDxdtJiOk8Egqe2uxDRY9XnXGSlmWClClmnixcoYumyvbAuj4dA==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "7.0.0-beta.51", - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-beta.51.tgz", - "integrity": "sha1-QDj54VJE4QkAy4n1t5bQUPHrGVs=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0.tgz", + "integrity": "sha512-BIcQLgPFCxi7YygtNpz5xj+7HxhOprbCGZKeLW6Kxsn1eHS6sJZMw4MfmqFZagl/v6IVa0AJoMHdDXLVrpd3Aw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "7.0.0-beta.51", - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/helper-simple-access": "7.0.0-beta.51" + "@babel/helper-module-transforms": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.0.0" } }, "@babel/template": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.49.tgz", - "integrity": "sha1-44q+ghfLl5P0YaUwbXrXRdg+HSc=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0.tgz", + "integrity": "sha512-VLQZik/G5mjYJ6u19U3W2u7eM+rA/NGzH+GtHDFFkLTKLW66OasFrxZ/yK7hkyQcswrmvugFyZpDFRW0DjcjCw==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.49", - "@babel/parser": "7.0.0-beta.49", - "@babel/types": "7.0.0-beta.49", - "lodash": "^4.17.5" + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/types": "^7.0.0" } }, "@babel/traverse": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.49.tgz", - "integrity": "sha1-TypzaCoYM07WYl0QCo0nMZ98LWg=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0.tgz", + "integrity": "sha512-ka/lwaonJZTlJyn97C4g5FYjPOx+Oxd3ab05hbDr1Mx9aP1FclJ+SUHyLx3Tx40sGmOVJApDxE6puJhd3ld2kw==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.49", - "@babel/generator": "7.0.0-beta.49", - "@babel/helper-function-name": "7.0.0-beta.49", - "@babel/helper-split-export-declaration": "7.0.0-beta.49", - "@babel/parser": "7.0.0-beta.49", - "@babel/types": "7.0.0-beta.49", + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.0.0", + "@babel/helper-function-name": "^7.0.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/types": "^7.0.0", "debug": "^3.1.0", "globals": "^11.1.0", - "invariant": "^2.2.0", - "lodash": "^4.17.5" + "lodash": "^4.17.10" }, "dependencies": { "globals": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz", - "integrity": "sha512-hYyf+kI8dm3nORsiiXUQigOU62hDLfJ9G01uyGMxhc6BKsircrUhC4uJPQPUSuq2GrTmiiEt7ewxlMdBewfmKQ==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", "dev": true } } }, "@babel/types": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.49.tgz", - "integrity": "sha1-t+Oxw/TUz+Eb34yJ8e/V4WF7h6Y=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0.tgz", + "integrity": "sha512-5tPDap4bGKTLPtci2SUl/B7Gv8RnuJFuQoWx26RJobS0fFrz4reUA3JnwIM+HVHEmWE0C1mzKhDtTp8NsWY02Q==", "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.5", + "lodash": "^4.17.10", "to-fast-properties": "^2.0.0" }, "dependencies": { @@ -1290,9 +468,9 @@ "dev": true }, "@types/ramda": { - "version": "0.25.36", - "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.25.36.tgz", - "integrity": "sha512-LAeBECPU9IG+K0pRdGl2L9lxanb0CKgue0IGpdn9nmpmKefDrCglrYwkxFYoQxCtY0w/mTxpJaQ4VAChcSIN+A==", + "version": "0.25.38", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.25.38.tgz", + "integrity": "sha512-6vkbUF1cg9CIaeZiSvbk6e25rOBbXBXixiZiV4Eai4WgwrmI3wBaH/TKE0tp9JEfzQFrv3eOoI0EjdBJaUfnIQ==", "dev": true }, "ajv": { @@ -1452,31 +630,25 @@ "dev": true }, "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", - "dev": true - }, - "auto-bind": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-1.2.1.tgz", - "integrity": "sha512-/W9yj1yKmBLwpexwAujeD9YHwYmRuWFGV8HWE7smQab797VeHa4/cnE2NFeDhA+E+5e/OGBI8763EhLjfZ/MXA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, "ava": { - "version": "1.0.0-beta.6", - "resolved": "https://registry.npmjs.org/ava/-/ava-1.0.0-beta.6.tgz", - "integrity": "sha512-QHP8MSrmJYmxW8n0LTNDWLHuW4ck2+0BJZeScxZtC/0c6w39Uq7s+YNpOh8qOE53XQZUUxDqvwWwYP9TGK4rag==", + "version": "1.0.0-beta.8", + "resolved": "https://registry.npmjs.org/ava/-/ava-1.0.0-beta.8.tgz", + "integrity": "sha512-aEG/JoBOP/iMC+0vGbfFnvQzTUKgpYm5i17j+VRLEy/qnGmynQJfPW4Hot/Cv1VaiRbpZ/S52O4BAI4N2HHpbA==", "dev": true, "requires": { - "@ava/babel-preset-stage-4": "2.0.0-beta.8", - "@ava/babel-preset-transform-test-files": "4.0.0-beta.7", + "@ava/babel-preset-stage-4": "^2.0.0", + "@ava/babel-preset-transform-test-files": "4.0.0-beta.9", "@ava/write-file-atomic": "^2.2.0", - "@babel/core": "7.0.0-beta.51", - "@babel/generator": "7.0.0-beta.51", - "@babel/plugin-syntax-async-generators": "7.0.0-beta.51", - "@babel/plugin-syntax-object-rest-spread": "7.0.0-beta.51", - "@babel/plugin-syntax-optional-catch-binding": "7.0.0-beta.51", + "@babel/core": "^7.0.0", + "@babel/generator": "^7.0.0", + "@babel/plugin-syntax-async-generators": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.0.0", "@concordance/react": "^1.0.0", "ansi-escapes": "^3.1.0", "ansi-styles": "^3.2.1", @@ -1484,15 +656,14 @@ "array-union": "^1.0.1", "array-uniq": "^2.0.0", "arrify": "^1.0.0", - "auto-bind": "^1.2.0", "bluebird": "^3.5.1", "chalk": "^2.4.1", - "chokidar": "^2.0.3", + "chokidar": "^2.0.4", + "ci-parallel-vars": "^1.0.0", "clean-stack": "^1.1.1", "clean-yaml-object": "^0.1.0", "cli-cursor": "^2.1.0", "cli-truncate": "^1.1.0", - "co-with-promise": "^4.6.0", "code-excerpt": "^2.1.1", "common-path-prefix": "^1.0.0", "concordance": "^3.0.0", @@ -1501,20 +672,19 @@ "debug": "^3.1.0", "del": "^3.0.0", "dot-prop": "^4.2.0", - "emittery": "^0.3.0", - "empower-core": "^0.6.1", + "emittery": "^0.4.1", + "empower-core": "^1.2.0", "equal-length": "^1.0.0", "escape-string-regexp": "^1.0.5", - "esm": "^3.0.40", + "esm": "^3.0.80", "figures": "^2.0.0", - "get-port": "^3.2.0", + "get-port": "^4.0.0", "globby": "^7.1.1", "ignore-by-default": "^1.0.0", "import-local": "^1.0.0", "indent-string": "^3.2.0", - "is-ci": "^1.1.0", + "is-ci": "^1.2.0", "is-error": "^2.2.1", - "is-generator-fn": "^1.0.0", "is-observable": "^1.1.0", "is-plain-object": "^2.0.4", "is-promise": "^2.1.0", @@ -1532,7 +702,7 @@ "ms": "^2.1.1", "multimatch": "^2.1.0", "observable-to-promise": "^0.5.0", - "ora": "^2.1.0", + "ora": "^3.0.0", "package-hash": "^2.0.0", "pkg-conf": "^2.1.0", "plur": "^3.0.1", @@ -1540,53 +710,26 @@ "require-precompiled": "^0.1.0", "resolve-cwd": "^2.0.0", "slash": "^2.0.0", - "source-map-support": "^0.5.6", + "source-map-support": "^0.5.9", "stack-utils": "^1.0.1", "strip-ansi": "^4.0.0", "strip-bom-buf": "^1.0.0", "supertap": "^1.0.0", - "supports-color": "^5.4.0", + "supports-color": "^5.5.0", "trim-off-newlines": "^1.0.1", "trim-right": "^1.0.1", "unique-temp-dir": "^1.0.0", "update-notifier": "^2.5.0" }, "dependencies": { - "@babel/generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.51.tgz", - "integrity": "sha1-bHV1/952HQdIXgS67cA5LG2eMPY=", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.51", - "jsesc": "^2.5.1", - "lodash": "^4.17.5", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.5", - "to-fast-properties": "^2.0.0" + "has-flag": "^3.0.0" } - }, - "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true } } }, @@ -1795,26 +938,18 @@ } }, "babel-plugin-espower": { - "version": "3.0.0-beta.1", - "resolved": "https://registry.npmjs.org/babel-plugin-espower/-/babel-plugin-espower-3.0.0-beta.1.tgz", - "integrity": "sha512-mYTgLnrzk3zuevZWQZVIvu33cTleDiLKJe5LsdUEB5KDm4EI+u4GqcHahA5ZyOvKgTTJbpHXrGnz0v1cFYqnCQ==", + "version": "3.0.0-beta.2", + "resolved": "https://registry.npmjs.org/babel-plugin-espower/-/babel-plugin-espower-3.0.0-beta.2.tgz", + "integrity": "sha512-oK85tacH/SRebgt+f3NCu/CSGhutlgSXiJ87UMDN4pBTW1rgHrlYoqcKCdQZv23SztVkUy7o7bBEAtH/NyRpJQ==", "dev": true, "requires": { - "@babel/generator": "^7.0.0-beta.35", - "babylon": "^7.0.0-beta.35", + "@babel/generator": "^7.0.0-beta.54", + "@babel/parser": "^7.0.0-beta.54", "call-matcher": "^1.0.0", "core-js": "^2.0.0", "espower-location-detector": "^1.0.0", "espurify": "^1.6.0", "estraverse": "^4.1.1" - }, - "dependencies": { - "babylon": { - "version": "7.0.0-beta.47", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", - "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", - "dev": true - } } }, "babel-plugin-syntax-async-functions": { @@ -2301,9 +1436,9 @@ "dev": true }, "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", + "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", "dev": true }, "boxen": { @@ -2371,9 +1506,9 @@ } }, "buffer-from": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", - "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, "builtin-modules": { @@ -2400,9 +1535,9 @@ } }, "call-matcher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.0.1.tgz", - "integrity": "sha1-UTTQd5hPcSpU2tPL9i3ijc5BbKg=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/call-matcher/-/call-matcher-1.1.0.tgz", + "integrity": "sha512-IoQLeNwwf9KTNbtSA7aEBb1yfDbdnzwjCetjkC8io5oGeOmK2CBNdg0xr+tadRYKO0p7uQyZzvon0kXlZbvGrw==", "dev": true, "requires": { "core-js": "^2.0.0", @@ -2441,9 +1576,9 @@ "dev": true }, "capture-stack-trace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", "dev": true }, "caseless": { @@ -2485,9 +1620,15 @@ } }, "ci-info": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", - "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.5.1.tgz", + "integrity": "sha512-fKFIKXaYiL1exImwJ0AhR/6jxFPSKQBk2ayV5NiNoruUs2+rxC2kNw0EG+1Z9dugZRdCrppskQ8DN2cyaUM1Hw==", + "dev": true + }, + "ci-parallel-vars": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.0.tgz", + "integrity": "sha512-u6dx20FBXm+apMi+5x7UVm6EH7BL1gc4XrcnQewjcB7HWRcor/V5qWc3RG2HwpgDJ26gIi2DSEu3B7sXynAw/g==", "dev": true }, "class-utils": { @@ -2568,15 +1709,6 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, - "co-with-promise": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co-with-promise/-/co-with-promise-4.6.0.tgz", - "integrity": "sha1-QT59tvWJOmC5Qs9JLEvsk9tBWrc=", - "dev": true, - "requires": { - "pinkie-promise": "^1.0.0" - } - }, "code-excerpt": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-2.1.1.tgz", @@ -2678,10 +1810,13 @@ } }, "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } }, "convert-to-spaces": { "version": "1.0.2", @@ -2788,20 +1923,12 @@ } }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "ms": "^2.1.1" } }, "decamelize": { @@ -2936,21 +2063,6 @@ "dev": true } } - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } } } }, @@ -3032,15 +2144,15 @@ "dev": true }, "emittery": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.3.0.tgz", - "integrity": "sha512-Bn/IFhx+BQIjTKn0vq7YWwo/yfTNeBZMqOGufY5FEV07tbwy5heDROFDCkMO2PcO5s7B9FDDXZc+JGgl6KzBOQ==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.4.1.tgz", + "integrity": "sha512-r4eRSeStEGf6M5SKdrQhhLK5bOwOBxQhIE3YSTnZE3GpKiLfnnhE+tPtrJE79+eDJgm39BM6LSoI8SCx4HbwlQ==", "dev": true }, "empower-core": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-0.6.2.tgz", - "integrity": "sha1-Wt71ZgiOMfuoC6CjbfR9cJQWkUQ=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/empower-core/-/empower-core-1.2.0.tgz", + "integrity": "sha512-g6+K6Geyc1o6FdXs9HwrXleCFan7d66G5xSCfSF7x1mJDCes6t0om9lFQG3zOrzh3Bkb/45N0cZ5Gqsf7YrzGQ==", "dev": true, "requires": { "call-signature": "0.0.2", @@ -3075,9 +2187,9 @@ "dev": true }, "esm": { - "version": "3.0.72", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.0.72.tgz", - "integrity": "sha512-xmh7Ay/71Wl41EPM3FVomp/GZKMgEGoo3x/qWdbGi+0DODbte+2l1sSXAiDKahBQn0zioG30ZNJTkxz08pEsMw==", + "version": "3.0.82", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.0.82.tgz", + "integrity": "sha512-vakh2il2Q9QdwCUEiFQqtamOANcGATh5OlMRLaXsvOhuuzr/SXdngYw1rwJjesrljbnsq3+UZ5+3Y3uszc/U/w==", "dev": true }, "espower-location-detector": { @@ -3915,9 +3027,9 @@ "dev": true }, "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.0.0.tgz", + "integrity": "sha512-Yy3yNI2oShgbaWg4cmPhWjkZfktEvpKI09aDX4PZzNtlU9obuYrX7x2mumQsrNxlF+Ls7OtMQW/u+X4s896bOQ==", "dev": true }, "get-stream": { @@ -4015,7 +3127,7 @@ }, "got": { "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "dev": true, "requires": { @@ -4069,6 +3181,12 @@ "ansi-regex": "^2.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -4246,12 +3364,12 @@ } }, "is-ci": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", - "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "dev": true, "requires": { - "ci-info": "^1.0.0" + "ci-info": "^1.5.0" } }, "is-data-descriptor": { @@ -4317,12 +3435,6 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "is-generator-fn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", - "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", - "dev": true - }, "is-glob": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", @@ -4370,7 +3482,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, @@ -4495,24 +3607,153 @@ "dev": true }, "istanbul-lib-coverage": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", - "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==", "dev": true }, "istanbul-lib-instrument": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-2.1.0.tgz", - "integrity": "sha512-3ly7GAJiPKqgbGKh2s01ysk3jd/egpE1i84PYu3BvPkssqrKMXZY9KRGX0mfZ+cmCfTR1IFVnnn/tDHxTer4nA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-2.3.2.tgz", + "integrity": "sha512-l7TD/VnBsIB2OJvSyxaLW/ab1+92dxZNH9wLH7uHPPioy3JZ8tnx2UXUdKmdkgmP2EFPzg64CToUP6dAS3U32Q==", "dev": true, "requires": { - "@babel/generator": "7.0.0-beta.49", - "@babel/parser": "7.0.0-beta.49", - "@babel/template": "7.0.0-beta.49", - "@babel/traverse": "7.0.0-beta.49", - "@babel/types": "7.0.0-beta.49", - "istanbul-lib-coverage": "^1.2.0", - "semver": "^5.3.0" + "@babel/generator": "7.0.0-beta.51", + "@babel/parser": "7.0.0-beta.51", + "@babel/template": "7.0.0-beta.51", + "@babel/traverse": "7.0.0-beta.51", + "@babel/types": "7.0.0-beta.51", + "istanbul-lib-coverage": "^2.0.1", + "semver": "^5.5.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz", + "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=", + "dev": true, + "requires": { + "@babel/highlight": "7.0.0-beta.51" + } + }, + "@babel/generator": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.51.tgz", + "integrity": "sha1-bHV1/952HQdIXgS67cA5LG2eMPY=", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.51", + "jsesc": "^2.5.1", + "lodash": "^4.17.5", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-function-name": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.51.tgz", + "integrity": "sha1-IbSHSiJ8+Z7K/MMKkDAtpaJkBWE=", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "7.0.0-beta.51", + "@babel/template": "7.0.0-beta.51", + "@babel/types": "7.0.0-beta.51" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.51.tgz", + "integrity": "sha1-MoGy0EWvlcFyzpGyCCXYXqRnZBE=", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.51" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.51.tgz", + "integrity": "sha1-imw/ZsTSZTUvwHdIT59ugKUauXg=", + "dev": true, + "requires": { + "@babel/types": "7.0.0-beta.51" + } + }, + "@babel/highlight": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz", + "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" + } + }, + "@babel/parser": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.51.tgz", + "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", + "dev": true + }, + "@babel/template": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz", + "integrity": "sha1-lgKkCuvPNXrpZ34lMu9fyBD1+/8=", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0-beta.51", + "@babel/parser": "7.0.0-beta.51", + "@babel/types": "7.0.0-beta.51", + "lodash": "^4.17.5" + } + }, + "@babel/traverse": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.51.tgz", + "integrity": "sha1-mB2vLOw0emIx06odnhgDsDqqpKg=", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0-beta.51", + "@babel/generator": "7.0.0-beta.51", + "@babel/helper-function-name": "7.0.0-beta.51", + "@babel/helper-split-export-declaration": "7.0.0-beta.51", + "@babel/parser": "7.0.0-beta.51", + "@babel/types": "7.0.0-beta.51", + "debug": "^3.1.0", + "globals": "^11.1.0", + "invariant": "^2.2.0", + "lodash": "^4.17.5" + } + }, + "@babel/types": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", + "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.5", + "to-fast-properties": "^2.0.0" + } + }, + "globals": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "dev": true + }, + "jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } } }, "js-string-escape": { @@ -4749,15 +3990,15 @@ } }, "make-error": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", - "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", "dev": true }, "map-any": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/map-any/-/map-any-0.1.1.tgz", - "integrity": "sha512-+RJOQquxMzzfK2CHMVnC1AHex17Z+gV2jsS2O8NCGUQL1vPPMogfeeT7O21Nx15gC0NkaQmFqGwg779JEE9K0A==" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/map-any/-/map-any-0.1.2.tgz", + "integrity": "sha512-PWnY5+yDvki2/Kn4VyshxvWIhI54xbn+ELhGY+Re4DCo0TetSeNEbL6xMIF3YaL2oQsV6PJnk3ltXFv2XJ52Bw==" }, "map-cache": { "version": "0.2.2", @@ -4874,7 +4115,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -4911,7 +4152,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -4937,9 +4178,9 @@ } }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", "dev": true, "optional": true }, @@ -4993,38 +4234,36 @@ } }, "nyc": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-12.0.2.tgz", - "integrity": "sha1-ikpO1pCWbBHsWH/4fuoMEsl0upk=", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.0.1.tgz", + "integrity": "sha512-Op/bjhEF74IMtzMmgYt+ModTeMHoPZzHe4qseUguPBwg5qC6r4rYMBt1L3yRXQIbjUpEqmn24/1xAC/umQGU7w==", "dev": true, "requires": { "archy": "^1.0.0", "arrify": "^1.0.1", - "caching-transform": "^1.0.0", + "caching-transform": "^2.0.0", "convert-source-map": "^1.5.1", "debug-log": "^1.0.1", - "default-require-extensions": "^1.0.0", - "find-cache-dir": "^0.1.1", - "find-up": "^2.1.0", - "foreground-child": "^1.5.3", - "glob": "^7.0.6", - "istanbul-lib-coverage": "^1.2.0", - "istanbul-lib-hook": "^1.1.0", - "istanbul-lib-instrument": "^2.1.0", - "istanbul-lib-report": "^1.1.3", - "istanbul-lib-source-maps": "^1.2.5", - "istanbul-reports": "^1.4.1", - "md5-hex": "^1.2.0", + "find-cache-dir": "^2.0.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.2", + "istanbul-lib-coverage": "^2.0.1", + "istanbul-lib-hook": "^2.0.1", + "istanbul-lib-instrument": "^2.3.2", + "istanbul-lib-report": "^2.0.1", + "istanbul-lib-source-maps": "^2.0.1", + "istanbul-reports": "^2.0.0", + "make-dir": "^1.3.0", "merge-source-map": "^1.1.0", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.0", - "resolve-from": "^2.0.0", + "resolve-from": "^4.0.0", "rimraf": "^2.6.2", - "signal-exit": "^3.0.1", + "signal-exit": "^3.0.2", "spawn-wrap": "^1.4.2", - "test-exclude": "^4.2.0", + "test-exclude": "^5.0.0", + "uuid": "^3.3.2", "yargs": "11.1.0", - "yargs-parser": "^8.0.0" + "yargs-parser": "^9.0.2" }, "dependencies": { "align-text": { @@ -5048,11 +4287,11 @@ "dev": true }, "append-transform": { - "version": "0.4.0", + "version": "1.0.0", "bundled": true, "dev": true, "requires": { - "default-require-extensions": "^1.0.0" + "default-require-extensions": "^2.0.0" } }, "archy": { @@ -5060,106 +4299,21 @@ "bundled": true, "dev": true }, - "arr-diff": { - "version": "4.0.0", - "bundled": true, - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "bundled": true, - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "bundled": true, - "dev": true - }, "arrify": { "version": "1.0.1", "bundled": true, "dev": true }, - "assign-symbols": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, "async": { "version": "1.5.2", "bundled": true, "dev": true }, - "atob": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, "balanced-match": { "version": "1.0.0", "bundled": true, "dev": true }, - "base": { - "version": "0.11.2", - "bundled": true, - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "bundled": true, - "dev": true - } - } - }, "brace-expansion": { "version": "1.1.11", "bundled": true, @@ -5169,62 +4323,20 @@ "concat-map": "0.0.1" } }, - "braces": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "builtin-modules": { "version": "1.1.1", "bundled": true, "dev": true }, - "cache-base": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, "caching-transform": { - "version": "1.0.1", + "version": "2.0.0", "bundled": true, "dev": true, "requires": { - "md5-hex": "^1.2.0", - "mkdirp": "^0.5.1", - "write-file-atomic": "^1.1.4" + "make-dir": "^1.0.0", + "md5-hex": "^2.0.0", + "package-hash": "^2.0.0", + "write-file-atomic": "^2.0.0" } }, "camelcase": { @@ -5243,27 +4355,6 @@ "lazy-cache": "^1.0.3" } }, - "class-utils": { - "version": "0.3.6", - "bundled": true, - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "bundled": true, - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "cliui": { "version": "2.1.0", "bundled": true, @@ -5288,37 +4379,18 @@ "bundled": true, "dev": true }, - "collection-visit": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, "commondir": { "version": "1.0.1", "bundled": true, "dev": true }, - "component-emitter": { - "version": "1.2.1", - "bundled": true, - "dev": true - }, "concat-map": { "version": "0.0.1", "bundled": true, "dev": true }, "convert-source-map": { - "version": "1.5.1", - "bundled": true, - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", + "version": "1.5.1", "bundled": true, "dev": true }, @@ -5349,69 +4421,27 @@ "bundled": true, "dev": true }, - "decode-uri-component": { - "version": "0.2.0", - "bundled": true, - "dev": true - }, "default-require-extensions": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "strip-bom": "^2.0.0" - } - }, - "define-property": { - "version": "2.0.2", + "version": "2.0.0", "bundled": true, "dev": true, "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "bundled": true, - "dev": true - } + "strip-bom": "^3.0.0" } }, "error-ex": { - "version": "1.3.1", + "version": "1.3.2", "bundled": true, "dev": true, "requires": { "is-arrayish": "^0.2.1" } }, + "es6-error": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, "execa": { "version": "0.7.0", "bundled": true, @@ -5438,173 +4468,24 @@ } } }, - "expand-brackets": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "bundled": true, - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "bundled": true, - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "bundled": true, - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "bundled": true, - "dev": true - } - } - }, - "fill-range": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "find-cache-dir": { - "version": "0.1.1", + "version": "2.0.0", "bundled": true, "dev": true, "requires": { "commondir": "^1.0.1", - "mkdirp": "^0.5.1", - "pkg-dir": "^1.0.0" + "make-dir": "^1.0.0", + "pkg-dir": "^3.0.0" } }, "find-up": { - "version": "2.1.0", + "version": "3.0.0", "bundled": true, "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "^3.0.0" } }, - "for-in": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, "foreground-child": { "version": "1.5.6", "bundled": true, @@ -5614,21 +4495,13 @@ "signal-exit": "^3.0.0" } }, - "fragment-cache": { - "version": "0.2.1", - "bundled": true, - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, "fs.realpath": { "version": "1.0.0", "bundled": true, "dev": true }, "get-caller-file": { - "version": "1.0.2", + "version": "1.0.3", "bundled": true, "dev": true }, @@ -5637,11 +4510,6 @@ "bundled": true, "dev": true }, - "get-value": { - "version": "2.0.6", - "bundled": true, - "dev": true - }, "glob": { "version": "7.1.2", "bundled": true, @@ -5681,245 +4549,128 @@ } } }, - "has-value": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", + "has-flag": { + "version": "3.0.0", "bundled": true, - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "bundled": true, - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "dev": true }, "hosted-git-info": { - "version": "2.6.0", + "version": "2.7.1", "bundled": true, "dev": true }, "imurmurhash": { - "version": "0.1.4", - "bundled": true, - "dev": true - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "invert-kv": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "bundled": true, - "dev": true - }, - "is-buffer": { - "version": "1.1.6", - "bundled": true, - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "bundled": true, - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "bundled": true, - "dev": true - } - } - }, - "is-extendable": { - "version": "0.1.1", - "bundled": true, - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, - "is-number": { - "version": "3.0.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } + "version": "0.1.4", + "bundled": true, + "dev": true }, - "is-odd": { - "version": "2.0.0", + "inflight": { + "version": "1.0.6", "bundled": true, "dev": true, "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "bundled": true, - "dev": true - } + "once": "^1.3.0", + "wrappy": "1" } }, - "is-plain-object": { - "version": "2.0.4", + "inherits": { + "version": "2.0.3", "bundled": true, - "dev": true, - "requires": { - "isobject": "^3.0.1" - } + "dev": true }, - "is-stream": { - "version": "1.1.0", + "invert-kv": { + "version": "1.0.0", "bundled": true, "dev": true }, - "is-utf8": { + "is-arrayish": { "version": "0.2.1", "bundled": true, "dev": true }, - "is-windows": { - "version": "1.0.2", + "is-buffer": { + "version": "1.1.6", "bundled": true, "dev": true }, - "isarray": { + "is-builtin-module": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } }, - "isexe": { + "is-fullwidth-code-point": { "version": "2.0.0", "bundled": true, "dev": true }, - "isobject": { - "version": "3.0.1", + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", "bundled": true, "dev": true }, "istanbul-lib-coverage": { - "version": "1.2.0", + "version": "2.0.1", "bundled": true, "dev": true }, "istanbul-lib-hook": { - "version": "1.1.0", + "version": "2.0.1", "bundled": true, "dev": true, "requires": { - "append-transform": "^0.4.0" + "append-transform": "^1.0.0" } }, "istanbul-lib-report": { - "version": "1.1.3", + "version": "2.0.1", "bundled": true, "dev": true, "requires": { - "istanbul-lib-coverage": "^1.1.2", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "bundled": true, - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } + "istanbul-lib-coverage": "^2.0.1", + "make-dir": "^1.3.0", + "supports-color": "^5.4.0" } }, "istanbul-lib-source-maps": { - "version": "1.2.5", + "version": "2.0.1", "bundled": true, "dev": true, "requires": { "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.0", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" + "istanbul-lib-coverage": "^2.0.1", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } } }, "istanbul-reports": { - "version": "1.4.1", + "version": "2.0.0", "bundled": true, "dev": true, "requires": { - "handlebars": "^4.0.3" + "handlebars": "^4.0.11" } }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, "kind-of": { "version": "3.2.2", "bundled": true, @@ -5943,33 +4694,30 @@ } }, "load-json-file": { - "version": "1.1.0", + "version": "4.0.0", "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" } }, "locate-path": { - "version": "2.0.0", + "version": "3.0.0", "bundled": true, "dev": true, "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" - }, - "dependencies": { - "path-exists": { - "version": "3.0.0", - "bundled": true, - "dev": true - } } }, + "lodash.flattendeep": { + "version": "4.4.0", + "bundled": true, + "dev": true + }, "longest": { "version": "1.0.1", "bundled": true, @@ -5984,21 +4732,16 @@ "yallist": "^2.1.2" } }, - "map-cache": { - "version": "0.2.2", - "bundled": true, - "dev": true - }, - "map-visit": { - "version": "1.0.0", + "make-dir": { + "version": "1.3.0", "bundled": true, "dev": true, "requires": { - "object-visit": "^1.0.0" + "pify": "^3.0.0" } }, "md5-hex": { - "version": "1.3.0", + "version": "2.0.0", "bundled": true, "dev": true, "requires": { @@ -6033,33 +4776,6 @@ } } }, - "micromatch": { - "version": "3.1.10", - "bundled": true, - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "bundled": true, - "dev": true - } - } - }, "mimic-fn": { "version": "1.2.0", "bundled": true, @@ -6074,68 +4790,30 @@ } }, "minimist": { - "version": "0.0.8", + "version": "0.0.10", "bundled": true, "dev": true }, - "mixin-deep": { - "version": "1.3.1", + "mkdirp": { + "version": "0.5.1", "bundled": true, "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "minimist": "0.0.8" }, "dependencies": { - "is-extendable": { - "version": "1.0.1", + "minimist": { + "version": "0.0.8", "bundled": true, - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } + "dev": true } } }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, "ms": { "version": "2.0.0", "bundled": true, "dev": true }, - "nanomatch": { - "version": "1.2.9", - "bundled": true, - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-odd": "^2.0.0", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "bundled": true, - "dev": true - } - } - }, "normalize-package-data": { "version": "2.4.0", "bundled": true, @@ -6160,47 +4838,6 @@ "bundled": true, "dev": true }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "bundled": true, - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "bundled": true, - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "object-visit": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.pick": { - "version": "1.3.0", - "bundled": true, - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, "once": { "version": "1.4.0", "bundled": true, @@ -6239,47 +4876,51 @@ "dev": true }, "p-limit": { - "version": "1.2.0", + "version": "2.0.0", "bundled": true, "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", + "version": "3.0.0", "bundled": true, "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.0.0" } }, "p-try": { - "version": "1.0.0", + "version": "2.0.0", "bundled": true, "dev": true }, - "parse-json": { - "version": "2.2.0", + "package-hash": { + "version": "2.0.0", "bundled": true, "dev": true, "requires": { - "error-ex": "^1.2.0" + "graceful-fs": "^4.1.11", + "lodash.flattendeep": "^4.4.0", + "md5-hex": "^2.0.0", + "release-zalgo": "^1.0.0" } }, - "pascalcase": { - "version": "0.1.1", - "bundled": true, - "dev": true - }, - "path-exists": { - "version": "2.1.0", + "parse-json": { + "version": "4.0.0", "bundled": true, "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "bundled": true, @@ -6290,139 +4931,76 @@ "bundled": true, "dev": true }, - "path-parse": { - "version": "1.0.5", - "bundled": true, - "dev": true - }, "path-type": { - "version": "1.1.0", + "version": "3.0.0", "bundled": true, "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "pify": "^3.0.0" } }, "pify": { - "version": "2.3.0", - "bundled": true, - "dev": true - }, - "pinkie": { - "version": "2.0.4", + "version": "3.0.0", "bundled": true, "dev": true }, - "pinkie-promise": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, "pkg-dir": { - "version": "1.0.0", + "version": "3.0.0", "bundled": true, "dev": true, "requires": { - "find-up": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - } + "find-up": "^3.0.0" } }, - "posix-character-classes": { - "version": "0.1.1", - "bundled": true, - "dev": true - }, "pseudomap": { "version": "1.0.2", "bundled": true, "dev": true }, "read-pkg": { - "version": "1.1.0", + "version": "3.0.0", "bundled": true, "dev": true, "requires": { - "load-json-file": "^1.0.0", + "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "path-type": "^3.0.0" } }, "read-pkg-up": { - "version": "1.0.1", + "version": "4.0.0", "bundled": true, "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - } + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" } }, - "regex-not": { - "version": "1.0.2", + "release-zalgo": { + "version": "1.0.0", "bundled": true, "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "es6-error": "^4.0.1" } }, - "repeat-element": { - "version": "1.1.2", - "bundled": true, - "dev": true - }, "repeat-string": { "version": "1.6.1", "bundled": true, "dev": true - }, - "require-directory": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "resolve-from": { - "version": "2.0.0", + }, + "require-directory": { + "version": "2.1.1", "bundled": true, "dev": true }, - "resolve-url": { - "version": "0.2.1", + "require-main-filename": { + "version": "1.0.1", "bundled": true, "dev": true }, - "ret": { - "version": "0.1.15", + "resolve-from": { + "version": "4.0.0", "bundled": true, "dev": true }, @@ -6443,14 +5021,6 @@ "glob": "^7.0.5" } }, - "safe-regex": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, "semver": { "version": "5.5.0", "bundled": true, @@ -6461,27 +5031,6 @@ "bundled": true, "dev": true }, - "set-value": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "shebang-command": { "version": "1.2.0", "bundled": true, @@ -6500,132 +5049,11 @@ "bundled": true, "dev": true }, - "slide": { - "version": "1.1.6", - "bundled": true, - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "bundled": true, - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "bundled": true, - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "bundled": true, - "dev": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^3.2.0" - } - }, "source-map": { "version": "0.5.7", "bundled": true, - "dev": true - }, - "source-map-resolve": { - "version": "0.5.2", - "bundled": true, "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "version": "0.4.0", - "bundled": true, - "dev": true + "optional": true }, "spawn-wrap": { "version": "1.4.2", @@ -6668,33 +5096,6 @@ "bundled": true, "dev": true }, - "split-string": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "static-extend": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "bundled": true, - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "string-width": { "version": "2.1.1", "bundled": true, @@ -6713,56 +5114,32 @@ } }, "strip-bom": { - "version": "2.0.0", + "version": "3.0.0", "bundled": true, - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } + "dev": true }, "strip-eof": { "version": "1.0.0", "bundled": true, "dev": true }, - "test-exclude": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "requires": { - "arrify": "^1.0.1", - "micromatch": "^3.1.8", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", - "require-main-filename": "^1.0.1" - } - }, - "to-object-path": { - "version": "0.3.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", + "supports-color": { + "version": "5.4.0", "bundled": true, "dev": true, "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "has-flag": "^3.0.0" } }, - "to-regex-range": { - "version": "2.1.1", + "test-exclude": { + "version": "5.0.0", "bundled": true, "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^1.0.1" } }, "uglify-js": { @@ -6796,94 +5173,11 @@ "dev": true, "optional": true }, - "union-value": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "bundled": true, - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } - } - }, - "unset-value": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "bundled": true, - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "bundled": true, - "dev": true - } - } - }, - "urix": { - "version": "0.1.0", + "uuid": { + "version": "3.3.2", "bundled": true, "dev": true }, - "use": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "bundled": true, - "dev": true - } - } - }, "validate-npm-package-license": { "version": "3.0.3", "bundled": true, @@ -6965,13 +5259,13 @@ "dev": true }, "write-file-atomic": { - "version": "1.3.4", + "version": "2.3.0", "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", - "slide": "^1.1.5" + "signal-exit": "^3.0.2" } }, "y18n": { @@ -7003,11 +5297,6 @@ "yargs-parser": "^9.0.2" }, "dependencies": { - "camelcase": { - "version": "4.1.0", - "bundled": true, - "dev": true - }, "cliui": { "version": "4.1.0", "bundled": true, @@ -7018,18 +5307,48 @@ "wrap-ansi": "^2.0.0" } }, - "yargs-parser": { - "version": "9.0.2", + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", "bundled": true, "dev": true, "requires": { - "camelcase": "^4.1.0" + "p-limit": "^1.1.0" } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true } } }, "yargs-parser": { - "version": "8.1.0", + "version": "9.0.2", "bundled": true, "dev": true, "requires": { @@ -7154,9 +5473,9 @@ } }, "ora": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-2.1.0.tgz", - "integrity": "sha512-hNNlAd3gfv/iPmsNxYoAPLvxg7HuPozww7fFonMZvL84tP6Ox5igfk5j/+a9rtJJwqMgKK+JgWsAQik5o0HTLA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.0.0.tgz", + "integrity": "sha512-LBS97LFe2RV6GJmXBi6OKcETKyklHNMV0xw7BtsVn2MlsgsydyZetSCbCANr+PFLmDyv4KV88nn0eCKza665Mg==", "dev": true, "requires": { "chalk": "^2.3.1", @@ -7313,18 +5632,18 @@ "dev": true }, "pinkie": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz", - "integrity": "sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", "dev": true }, "pinkie-promise": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz", - "integrity": "sha1-0dpn9UglY7t89X8oauKCLs+/NnA=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "^1.0.0" + "pinkie": "^2.0.0" } }, "pkg-conf": { @@ -7431,7 +5750,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -7460,7 +5779,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -7474,15 +5793,14 @@ } }, "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" } }, "redent": { @@ -7598,9 +5916,9 @@ "dev": true }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "dev": true }, "repeat-string": { @@ -7740,12 +6058,6 @@ "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=", "dev": true }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -7953,9 +6265,9 @@ } }, "source-map-support": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", - "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -8003,9 +6315,9 @@ } }, "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", + "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", "dev": true }, "split-string": { @@ -8269,9 +6581,9 @@ "dev": true }, "ts-node": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.0.tgz", - "integrity": "sha512-klJsfswHP0FuOLsvBZ/zzCfUvakOSSxds78mVeK7I+qP76YWtxf16hEZsp3U+b0kIo82R5UatGFeblYMqabb2Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", "dev": true, "requires": { "arrify": "^1.0.0", @@ -8286,7 +6598,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -8319,23 +6631,23 @@ } }, "tslint-config-standard": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/tslint-config-standard/-/tslint-config-standard-7.1.0.tgz", - "integrity": "sha512-cETzxZcEQ1RKjwtEScGryAtqwiRFc55xBxhZP6bePyOfXmo6i1/QKQrTgFKBiM4FjCvcqTjJq20/KGrh+TzTfQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/tslint-config-standard/-/tslint-config-standard-8.0.1.tgz", + "integrity": "sha512-OWG+NblgjQlVuUS/Dmq3ax2v5QDZwRx4L0kEuDi7qFY9UI6RJhhNfoCV1qI4el8Fw1c5a5BTrjQJP0/jhGXY/Q==", "dev": true, "requires": { "tslint-eslint-rules": "^5.3.1" } }, "tslint-eslint-rules": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.3.1.tgz", - "integrity": "sha512-qq2H/AU/FlFbQJKXuxhtIk+ni/nQu9jHHhsFKa6hnA0/n3zl1/RWRc3TVFlL8HfWFMzkST350VeTrFpy1u4OUg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz", + "integrity": "sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==", "dev": true, "requires": { "doctrine": "0.7.2", "tslib": "1.9.0", - "tsutils": "2.8.0" + "tsutils": "^3.0.0" }, "dependencies": { "tslib": { @@ -8345,12 +6657,12 @@ "dev": true }, "tsutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.8.0.tgz", - "integrity": "sha1-AWAXNymzvxOGKN0UoVN+AIUdgUo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.0.0.tgz", + "integrity": "sha512-LjHBWR0vWAUHWdIAoTjoqi56Kz+FDKBgVEuL+gVPG/Pv7QW5IdaDDeK9Txlr6U0Cmckp5EgCIq1T25qe3J6hyw==", "dev": true, "requires": { - "tslib": "^1.7.1" + "tslib": "^1.8.1" } } } @@ -8381,9 +6693,9 @@ "optional": true }, "typescript": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz", - "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.3.tgz", + "integrity": "sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg==", "dev": true }, "uid2": { @@ -8579,9 +6891,9 @@ "dev": true }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", diff --git a/package.json b/package.json index 5330040..659484b 100644 --- a/package.json +++ b/package.json @@ -64,15 +64,15 @@ }, "devDependencies": { "@types/deep-freeze": "^0.1.1", - "@types/ramda": "^0.25.36", - "ava": "1.0.0-beta.6", + "@types/ramda": "^0.25.38", + "ava": "1.0.0-beta.8", "babel-preset-env": "^1.7.0", "coveralls": "^3.0.2", "deep-freeze": "0.0.1", - "nyc": "^12.0.2", - "ts-node": "^7.0.0", + "nyc": "^13.0.1", + "ts-node": "^7.0.1", "tslint": "^5.11.0", - "tslint-config-standard": "^7.1.0", - "typescript": "^3.0.1" + "tslint-config-standard": "^8.0.1", + "typescript": "^3.0.3" } } From 956b08322036a80a8f831d17989614ea34094355 Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Sun, 16 Sep 2018 14:13:34 +0200 Subject: [PATCH 09/13] Implement .onlyMappedValues --- src/funcs/alt-test.ts | 19 +++++++++++++++++++ src/funcs/alt.ts | 10 ++++++---- src/funcs/getSet.ts | 40 ++++++++++++++++++++------------------- src/funcs/set-test.ts | 28 +++++++++++++++++++++++++++ src/index.ts | 19 ++++++++++++++----- src/tests/default-test.ts | 18 +++++++++--------- src/types.ts | 5 +++++ src/utils/pathGetter.ts | 2 +- src/utils/pathSetter.ts | 12 ++++++------ src/utils/stateHelpers.ts | 8 +++----- 10 files changed, 112 insertions(+), 49 deletions(-) diff --git a/src/funcs/alt-test.ts b/src/funcs/alt-test.ts index 6a62f2a..132bd68 100644 --- a/src/funcs/alt-test.ts +++ b/src/funcs/alt-test.ts @@ -43,6 +43,25 @@ test('should do nothing when value is set', (t) => { t.deepEqual(ret, expected) }) +test('should do nothing when onlyMapped is true', (t) => { + const state = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: undefined, + onlyMapped: true + } + const expected = { + root: { user: 'johnf' }, + context: { user: 'johnf' }, + value: undefined, + onlyMapped: true + } + + const ret = alt(getUser)(state) + + t.deepEqual(ret, expected) +}) + test('should treat string as path', (t) => { const state = { root: { user: 'johnf' }, diff --git a/src/funcs/alt.ts b/src/funcs/alt.ts index 732baff..e9e57bb 100644 --- a/src/funcs/alt.ts +++ b/src/funcs/alt.ts @@ -14,8 +14,10 @@ const getValueOrDefault = (state: State, runAlt: MapFunction) => (value: Data, i export default function alt (fn: MapDefinition): MapFunction { const runAlt = mapFunctionFromDef(fn) - return (state: State) => setStateValue( - state, - mapAny(getValueOrDefault(state, runAlt), state.value) - ) + return (state: State) => (state.onlyMapped) + ? state + : setStateValue( + state, + mapAny(getValueOrDefault(state, runAlt), state.value) + ) } diff --git a/src/funcs/getSet.ts b/src/funcs/getSet.ts index 10b28d5..2f96215 100644 --- a/src/funcs/getSet.ts +++ b/src/funcs/getSet.ts @@ -1,28 +1,30 @@ import * as mapAny from 'map-any' import { MapFunction, State, Path } from '../types' -import getter from '../utils/pathGetter' -import setter from '../utils/pathSetter' +import getter, { GetFunction } from '../utils/pathGetter' +import setter, { SetFunction } from '../utils/pathSetter' + +const getValue = (get: GetFunction, state: State): State => { + const value = mapAny(get, state.value) + const arr = !Array.isArray(state.value) && Array.isArray(value) + + return { ...state, value, arr } +} + +const setValue = (set: SetFunction, isArray: boolean, state: State): State => { + const setFn: SetFunction = (value) => (state.onlyMapped && typeof value === 'undefined') ? value : set(value) + const value = (state.arr || isArray) ? setFn(state.value) : mapAny(setFn, state.value) + + return { ...state, value } +} const getOrSet = (isGet: boolean) => (path: Path): MapFunction => { - const get = getter(path) - const set = setter(path) + const getFn = getter(path) + const setFn = setter(path) const isArray = path.endsWith('[]') - return (state: State): State => { - if (isGet ? !state.rev : state.rev) { - const value = mapAny(get, state.value) - return { - ...state, - value, - arr: !Array.isArray(state.value) && Array.isArray(value) - } - } else { - return { - ...state, - value: (state.arr || isArray) ? set(state.value) : mapAny(set, state.value) - } - } - } + return (state: State): State => (isGet ? !state.rev : state.rev) + ? getValue(getFn, state) + : setValue(setFn, isArray, state) } export const get = getOrSet(true) diff --git a/src/funcs/set-test.ts b/src/funcs/set-test.ts index 7989697..bbf3a44 100644 --- a/src/funcs/set-test.ts +++ b/src/funcs/set-test.ts @@ -34,6 +34,34 @@ test('should set undefined', (t) => { t.deepEqual(ret.value, expectedValue) }) +test('should not set undefined when onlyMapped is true', (t) => { + const state = { + root: {}, + context: {}, + value: undefined, + onlyMapped: true + } + const expectedValue = undefined + + const ret = set('meta.author')(state) + + t.deepEqual(ret.value, expectedValue) +}) + +test('should not set undefined in array when onlyMapped is true', (t) => { + const state = { + root: {}, + context: {}, + value: [undefined, 'johnf'], + onlyMapped: true + } + const expectedValue = [undefined, { meta: { author: 'johnf' } }] + + const ret = set('meta.author')(state) + + t.deepEqual(ret.value, expectedValue) +}) + test('should get from path when reverse mapping', (t) => { const data = { user: 'johnf' } const state = { diff --git a/src/index.ts b/src/index.ts index fb11a98..251c0c8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { compose } from 'ramda' -import { MapDefinition, DataMapperWithRev } from './types' +import { MapDefinition, MapTransform, MapFunction, State } from './types' import { mapFunctionFromDef, isMapObject } from './utils/definitionHelpers' -import { populateState, populateRevState, getStateValue } from './utils/stateHelpers' +import { populateState, getStateValue } from './utils/stateHelpers' import objectToMapFunction from './utils/objectToMapFunction' export { get, set } from './funcs/getSet' @@ -12,13 +12,22 @@ export { default as filter, FilterFunction } from './funcs/filter' export { fwd, rev } from './funcs/directionals' export { Data } from './types' -export function mapTransform (def: MapDefinition): DataMapperWithRev { +const composeMapFunction = (mapFn: MapFunction, initialState: Partial) => + compose(getStateValue, mapFn, populateState(initialState)) + +export function mapTransform (def: MapDefinition): MapTransform { const mapFn = (isMapObject(def)) ? objectToMapFunction(def) : mapFunctionFromDef(def) return Object.assign( - compose(getStateValue, mapFn, populateState), + composeMapFunction(mapFn, {}), { - rev: compose(getStateValue, mapFn, populateRevState) + rev: composeMapFunction(mapFn, { rev: true }), + onlyMappedValues: Object.assign( + composeMapFunction(mapFn, { onlyMapped: true }), + { + rev: composeMapFunction(mapFn, { rev: true, onlyMapped: true }) + } + ) } ) } diff --git a/src/tests/default-test.ts b/src/tests/default-test.ts index e8b4f6b..8a94da2 100644 --- a/src/tests/default-test.ts +++ b/src/tests/default-test.ts @@ -107,7 +107,7 @@ test('should use directional default value - reverse', (t) => { t.deepEqual(ret, expected) }) -test.skip('should not use default values', (t) => { +test('should not use default values', (t) => { const def = { title: [ 'content.heading', @@ -119,16 +119,16 @@ test.skip('should not use default values', (t) => { { content: { heading: 'From data' } } ] const expected = [ - {}, + undefined, { title: 'From data' } ] - const ret = mapTransform(def)(data) // .noDefaults(data) + const ret = mapTransform(def).onlyMappedValues(data) t.deepEqual(ret, expected) }) -test.skip('should not set missing prop to undefined', (t) => { +test('should not set missing prop to undefined', (t) => { const def = { title: 'content.heading' } @@ -137,16 +137,16 @@ test.skip('should not set missing prop to undefined', (t) => { { content: { heading: 'From data' } } ] const expected = [ - {}, + undefined, { title: 'From data' } ] - const ret = mapTransform(def)(data) // .noDefaults + const ret = mapTransform(def).onlyMappedValues(data) t.deepEqual(ret, expected) }) -test.skip('should not use default values on rev', (t) => { +test('should not use default values on rev', (t) => { const def = { title: [ 'content.heading', @@ -158,11 +158,11 @@ test.skip('should not use default values on rev', (t) => { { title: 'From data' } ] const expected = [ - {}, + undefined, { content: { heading: 'From data' } } ] - const ret = mapTransform(def).rev(data) // .noDefaults + const ret = mapTransform(def).onlyMappedValues.rev(data) t.deepEqual(ret, expected) }) diff --git a/src/types.ts b/src/types.ts index 48c0295..0a304f7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ export interface State { context: Data, value: Data, rev?: boolean, + onlyMapped?: boolean, arr?: boolean } @@ -35,3 +36,7 @@ export interface DataMapper { export interface DataMapperWithRev extends DataMapper { rev: DataMapper } + +export interface MapTransform extends DataMapperWithRev { + onlyMappedValues: DataMapperWithRev +} diff --git a/src/utils/pathGetter.ts b/src/utils/pathGetter.ts index a9f03cb..d21094a 100644 --- a/src/utils/pathGetter.ts +++ b/src/utils/pathGetter.ts @@ -29,7 +29,7 @@ const getGetters = R.compose( split ) -type GetFunction = (object?: Data | null) => Data +export type GetFunction = (object?: Data | null) => Data /** * Get the value at `path` in `object`. diff --git a/src/utils/pathSetter.ts b/src/utils/pathSetter.ts index eb3f51f..bac07a4 100644 --- a/src/utils/pathSetter.ts +++ b/src/utils/pathSetter.ts @@ -1,4 +1,4 @@ -import * as R from 'ramda' +import { mergeDeepRight, compose, binary, map, apply } from 'ramda' import { Data, Path } from '../types' const preparePathPart = (part: string, isAfterOpenArray: boolean) => @@ -52,13 +52,13 @@ const setter = (prop: string) => { } } -const getSetters = R.compose( - R.binary(R.apply(R.compose)), - R.map(setter), +const getSetters = compose( + binary(apply(compose)), + map(setter), split ) -type SetFunction = (value: Data, object?: Data | null) => Data +export type SetFunction = (value: Data, object?: Data | null) => Data /** * Set `value` at `path` in `object`. Note that a new object is returned, and @@ -75,6 +75,6 @@ export default function pathSetter (path: Path): SetFunction { return (value, object = null) => { const data = setters(value) - return (object) ? R.mergeDeepRight(object, data) : data + return (object) ? mergeDeepRight(object, data) : data } } diff --git a/src/utils/stateHelpers.ts b/src/utils/stateHelpers.ts index 24f1078..d4cdf88 100644 --- a/src/utils/stateHelpers.ts +++ b/src/utils/stateHelpers.ts @@ -23,12 +23,10 @@ export const lowerState = (state: State) => ({ export const pipeMapFns = (fns: MapFunction[]) => (state: State): State => fns.reduce((state: State, fn: MapFunction) => fn(state), state) -const initState = (rev: boolean) => (data: Data): State => ({ +export const populateState = ({ rev = false, onlyMapped = false }) => (data: Data): State => ({ root: data, context: data, value: data, - rev + rev, + onlyMapped }) - -export const populateState = initState(false) -export const populateRevState = initState(true) From f6dbaace5da8613fd52964889736a9efe5e28c22 Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Mon, 17 Sep 2018 13:40:04 +0200 Subject: [PATCH 10/13] Switch .rev and .onlyMappedValues --- src/index.ts | 8 ++++---- src/tests/default-test.ts | 2 +- src/types.ts | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 251c0c8..4156be8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,11 +21,11 @@ export function mapTransform (def: MapDefinition): MapTransform { return Object.assign( composeMapFunction(mapFn, {}), { - rev: composeMapFunction(mapFn, { rev: true }), - onlyMappedValues: Object.assign( - composeMapFunction(mapFn, { onlyMapped: true }), + onlyMappedValues: composeMapFunction(mapFn, { onlyMapped: true }), + rev: Object.assign( + composeMapFunction(mapFn, { rev: true }), { - rev: composeMapFunction(mapFn, { rev: true, onlyMapped: true }) + onlyMappedValues: composeMapFunction(mapFn, { rev: true, onlyMapped: true }) } ) } diff --git a/src/tests/default-test.ts b/src/tests/default-test.ts index 8a94da2..aaea6d0 100644 --- a/src/tests/default-test.ts +++ b/src/tests/default-test.ts @@ -162,7 +162,7 @@ test('should not use default values on rev', (t) => { { content: { heading: 'From data' } } ] - const ret = mapTransform(def).onlyMappedValues.rev(data) + const ret = mapTransform(def).rev.onlyMappedValues(data) t.deepEqual(ret, expected) }) diff --git a/src/types.ts b/src/types.ts index 0a304f7..babca5b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -33,10 +33,10 @@ export interface DataMapper { (data: Data): Data } -export interface DataMapperWithRev extends DataMapper { - rev: DataMapper +export interface DataMapperWithOnlyMappedValues extends DataMapper { + onlyMappedValues: DataMapper } -export interface MapTransform extends DataMapperWithRev { - onlyMappedValues: DataMapperWithRev +export interface MapTransform extends DataMapperWithOnlyMappedValues { + rev: DataMapperWithOnlyMappedValues } From 127b69553df9ca5d78ab0ed7dc48febc72aaca76 Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Mon, 17 Sep 2018 13:40:11 +0200 Subject: [PATCH 11/13] Update readme --- README.md | 619 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 460 insertions(+), 159 deletions(-) diff --git a/README.md b/README.md index ebf164c..1337949 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ const target = mapper(source) // } // And run it in reverse to get to what you started with: -const sourc2 = mapper.rev(target) +const source2 = mapper.rev(target) // -> { data: [ { @@ -99,7 +99,9 @@ const target2 = mapTransform(def2)(source) Maybe you want the actual date instead of the microseconds since the seventies: ```javascript -const { transform } = require('map-transform') +const { mapTransform, transform } = require('map-transform') + +// .... // Write a transform function, that accepts a value and returns a value const msToDate = (ms) => (new Date(ms)).toISOString() @@ -121,7 +123,7 @@ const target3 = mapTransform(def3)(source) // } // You may also reverse this, as long as you write a reverse version of -// `msToDate` +// `msToDate` and provide as a second argument to the `trasform()` function. ``` ... and so on. @@ -132,7 +134,7 @@ const target3 = mapTransform(def3)(source) Requires node v8.6. -### Installing and using +### Installing Install from npm: @@ -140,185 +142,484 @@ Install from npm: npm install map-transform --save ``` -### Mapping definition +## Usage + +### The transform object + +Think of the transform object as a description of the object structure you want. -**Note:** This description is obsolete. All the same features (and more) will be -available in version 0.2, but with different syntax. +#### Keys on the transform object +In essence, the keys on the transform object will be the keys on the target +object. You may, however, specifiy a key with dot notation, which will be split +out to child objects on the target. You can also specify the child objects +directly on the transform object, so in most cases this is just a matter of +taste. ```javascript -{ - filterFrom: , - filterFromRev: , - pathFrom: , - pathFromRev: , - transformFrom: , - transformFromRev: , - mapping: { - : { - path: , - default: , - defaultRev: , - transform: , - transformRev: +const def1 = { + 'data.entry.title': 'heading' +} + +const def2 = { + data: { + entry: { + title: 'heading' } - }, - transformTo: , - transformToRev: , - filterTo: , - filterToRev: , - pathTo: , - pathToRev: + } } + +// def1 and def2 are identical, and will result in an object like this: +// { +// data: { +// entry: { +// title: 'The actual heading' +// } +// } +// } ``` -(The order of the props hints at the order in which they are applied during the -mapping process.) - -There are four path strings here (plus the ones ending in 'Rev'), which are dot -paths like e.g. `'documents.content'`. - -First of all, if there's the `pathFrom` property on the root object is set, with -`path` as a handy alias. It is used to retrieve an object or an array of objects -from the source data. When this `path` is not set, the source data is just used -as is. - -Then the `path` property of each object on `mapping`, is used to retrieve field -values from the object(s) returned from the root `path`, using the value of -`default` when the path does not match a value in the source data. - -Next, the path string used as keys for the object in `mapping`, is used to set -each value on the target object(s). - -Finally, if a `pathTo` is set on the root object, the object or array of objects -we have at this point is set at this path on an empty object and returned. - -When using the `rev()` method, this is performed in the opposite order, and -`defaultRev` is used as default value instead of `default`. If a `pathToRev` is -specified, it is used instead of `pathTo` when extracting _from_ the data (to is -now from - as confusing as that is), and a `pathRev` or `pathFromRev` is used -instead of `path` or `pathRev` to set the data on an empty object just before -returning it. - -There is a shortcut when defining mappings without default values. The `mapping` -object `{'title': 'content.heading'}` is exactly the same as `{'title': {path: -'content.heading'}}`. - -The `path` properties below the `mapping` object will relate to the data -extracted and transformed (see below) by the definition properties on the root -object. If you need to get values from the original data object, prefix the path -with a `$`. E.g. when mapping items from `content.items[]`, a field may still -have a path like `$meta.author.id` to retrieve a value from the root of the -original data. Note that `$` paths will never be used to set values, as this -could lead to many items setting the same root property, which would be -unpredictable. - -The `transformTo` and `transformToRev` props, with `transform` and -`transformRev` as aliases, will transform a field or an object, and should be -set to a transform function or an array of transform functions, also called a -transform pipeline. A transform function is a pure function that accepts a value -and returns a value, and whatever happens between that is up to the transform -function. An array of transform functions will be run from left to right, and -each function will be called with the return value from the previous function. -The return value of the final function is set used as the field value or the -object. - -There's also `tranformFrom` and `transformFromRev`, which are applied just -before the field mapping (or after in a reverse mapping). - -Note that MapTransform does not put any restriction on the transform functions, -so it is up to you to make sure the transformations make sense for the field or -object it is used on. - -A special feature of the transform pipeline, is that a transform function might -have another transform function specified on a `rev` prop, that should do the -opposite of the base function. When specifying a `transform` pipeline and no -`transformRev`, MapTransform will create a reverse pipeline from the `rev` -functions on the `transform` pipeline, which will be executed from right to -left. This might be handy in some cases, as you might have reusable transform -functions that will know how to reverse their transformations, and by defining -a transform pipeline, you also define the reverse option. - -The filter props lets you filter the transformed objects at different stages in -the process. The most common is perhaps `filterTo`, with the alias `filter`, -that will be applied just before the transformed data is set on `pathTo` -(if present). The filter pipeline is a filter function or an array of filter -functions, where each function accepts the object as it is at that time in the -mapping process, and returns true to include it and false to filter it out of -the final result. When several filter functions are set, all of them must return -true for the item to be included. - -The filter pipelines are used on reverse mapping as well, in the opposite order. -They all have reverse counterparts with the 'Rev' postfix, and these might be -set to `null` to keep a filter pipeline from being applied on reverse mapping. - -The `mapping` object defines the shape of the target item(s) to map _to_, with -options for how values _from_ the source object should be mapped to it. Another -way of specifying this shape, is simply supplying it as nested objects. In the -following example, `def1` and `def2` are two ways of defining the exact same -mapping, it's simply a matter of taste: +You may also add brackets to keys, to ensure that you will end up with an array. +If MapTransform happens upon an array in the source data, it will map it and +set an array where each item is mapped according to the mapping object. But to +ensure that you get an array, even when the source data contains only an object, +suffix the key with `[]`. -``` -const def1 = { - mapping: { - 'attributes.title': {path: 'headline', default: 'Untitled'}, - 'attributes.text': 'content.text' +```javascript +const def3 = { + 'data.entries[]': { + title: 'heading' } } -const def2 = { - mapping: { - attributes: { - title: {path: 'headline', default: 'Untitled'}, - text: 'content.text' +// def3 will always give you entries as an array: +// { +// data: { +// entries: [ +// {title: 'The actual heading'} +// ] +// } +// } +``` + +#### Values on the transform object + +The values on the transform objects define how to retrieve and transform data +from the source object, before it is set on the target object. + +As you have already seen, you may set a **transform object** as the value, which +will result in child objects on the target, but at some point, you'll have to +define how to get data from the source object. + +The simplest form is a **dot notation path**, that describes what prop(s) to pick +from the source object for this particular target key. It will retrieve whatever +is at this path on the source object. + +```javascript +const def4 = { + title: 'data.item.heading' +} + +const source1 = { + data: { + item: { + id: 'item1', + heading: 'The actual heading', + intro: 'The actual intro' } } } + +// `mapTransform(def4)(source1)` will transform to: +// { +// title: 'The actual heading' +// } +``` + +The target object will only include values from the source object that is +"mentioned" by the mapping object. + +The paths for the source data may also include brackets to indicate arrays in +the data. It is usually not necessary, as MapTransform will map any array it +finds, but it may be good to indicate what you expect from the source data, and +it may be important if you plan to reverse transform the mapping object. + +Another feature of the bracket notation, is that you may pick a single item from +an array by indicating the array index in the brackets. + +```javascript +const def5 = { + title: 'data.items[0].heading' +} + +// def5 will pull the heading from the first item in the `items` array, and will +// not return any array: +// { +// title: 'The actual heading' +// } ``` -The only difference is that `path` is a reserved property name in mapping -definitions unless it is set directly on the `mapping` object, so to map to an -object with a `path` property, you will have to use the dot notation. +Finally, a transform object value may be set to a +[**transform pipeline**](#transform-pipeline), or one function that could go in +the transform pipeline (which the dot notation path really is, and – come to +think of it – the transform object itself too). This is explained in detail +below. + +### Transform pipeline + +The idea of the transform pipeline, is that you describe a set of +transformations that will be applied to the data given to it, so that the +data will come out on the other "end" of the pipeline in another format. You may +also insert data on the other end of the pipeline, and get it out in the +original format again (although with a potential loss of data, if not all +properties are transformed). This is what you do in a +[reverse mapping](#reverse-mapping). + +One way to put it is that the pipeline describes the difference between the two +possible states of the data, and allows you to go back and forth between them. +Or you can just view it as operations applied in the order they are defined – or +back again. + +You define a pipeline as an array that may hold dot notation paths, transform +objects and transform operations of different kinds (see below). If the pipeline +holds only one of these, you may actually skip the array. This is a handy +shortcut in some cases. + +Here's an example pipeline that will retrieve an array of objects from the path +`data.items[]`, map each object to an object with the props `id`, `title`, and +`sections` (`title` is shortened to max 20 chars and `sections` will be an array +of ids pulled from an array of section objects), and finally filter away all +items with no values in the `sections` prop. + +```javascript +import { transform, filter } from 'map-transform' +const def6 = [ + 'data.items[]', + { + id: 'articleNo', + title: ['headline', transform(maxLength(20))], + sections: 'meta.sections[].id' + }, + filter(onlyItemsWithSection) +] ``` -// NOT okay: -const def3 = { - mapping: { - attributes: { - path: { path: 'meta.path'} - } - } + +(Note that in this example, both `maxLength` and `onlyItemsWithSection` are +custom functions for this case, but their implementation is not provided.) + +#### `transform(fn, fnRev)` operation + +The simple beauty of the `transform()` operation, is that it will apply whatever +function you provide it with to the data at that point in the pipeline. It's +completely up to you to write the function that does the transformation. + +You may supply a second function (`fnRev`), that will be used when +[reverse mapping](#reverse-mapping). If you only supplies one function, it will +be used in both directions. You may supply `null` for either of these, to make +it uni-directional, but it might be clearer to use `fwd()` or `rev()` operations +for this. + +The functions you write for the transform operation should accept the source +data as its only argument, and return the result of the relevant transformation. +The data may be an object, a string, a number, a boolean, or an array of these. +It's really just up to you to write the appropriate function and use it at the +right place in a transform pipeline. + +A simple transform function could, for instance, try to parse an integer from +whatever you give it. This would be very useful in the pipeline for a property +expecting numeric values, but MapTransform would not protest should you use it +on an object. You would probably just not get the end result you expected. + +```javascript +import { mapTransform, transform } from 'map-transform' + +const ensureInteger = (data) => Number.parseInt(data, 10) || 0 +const def7 = { + count: ['statistics.views', transform(ensureInteger)] } -// Okay: -const def4 = { - mapping: { - 'attributes.path': { path: 'meta.path' } +const data = { + statistics: { + view: '18', + // ... } } + +mapTransform(def7)(data) +// --> { +// count: 18 +// } +``` + +This is also a good example of a transformation that only makes sense in one +direction. This will still work in reverse, ending in almost the same object +that was provided, but with a numeric `view` property. You may supply a +reverse transform function called `ensureString`, if it makes sense in your +particular case. + +The functions you provide for the transform operation are expected to be pure, +i.e. they should not have any side effects. This means they should +1. not alter the data their are given, and +2. not rely on any state outside the function + +Principle 1 is an absolute requirement, and principle 2 should only be violated +when it is what you would expect for the particular case. As an example of the +latter, say you write the function `toAge`, that would return the number of +years since a given year or date. You would have to use the current date to be +able to do this, even though it would be a violation of principle 2. + +That said, you should always search for ways to satisfy both principles. Instead +of a `toAge` function, you could instead write a curried `yearsSince` function, +that would accept the current date (or any date) as the first argument. This +would be a truly pure function. + +Example transformation pipeline with a `yearsSince` function: +```javascript +const def8 = { + age: ['birthyear', yearsSince(new Date())] +} +``` + +#### `filter(fn)` operation + +Just like the transform operation, the filter operation will apply whatever +function you give it to the data at that point in the transform pipeline, but +instead of transformed data, you return a boolean value indicating whether to +keep the data or not. If you return `true` the data continues through the +pipeline, if you return `false` it is removed. + +When filtering an array, the function is applied to each data item in the array, +like a normal filter function, and a new array with only the items that your +function returns `true` for. For data that is not in an array, a `false` value +from your function will simply mean that it is replaced with `undefined`. + +The filter operation only accepts one argument, which is applied in both +directions through the pipeline. You'll have to use `fwd()` or `rev()` +operations to make it uni-directional. + +Functions passed to the filter operation, should also be pure, but could, when +it is expected and absolutely necessary, rely on states outside the function. +See the explanation of this under the transform operation above. + +Example of a filter, where only data of active members are returned: +```javascript +import { mapTransform, filter } from 'map-transform' + +const onlyActives = (data) => data.active +const def9 = [ + 'members' + { + name: 'name', + active: 'hasPayed' + }, + filter(onlyActives) +] +``` + +#### `value(data)` operation +The data given to the value operation, will be inserted in the pipeline in place +of any data that is already present at that point. The data may be an object, +a string, a number, a boolean, or an array of the above. + +This could be useful for: +- Setting a fixed value on a property in the target data +- Providing a default value to the alt operation + +Example of both: +```javascript +import { value, alt } from 'map-transform' + +const def10 = { + id: 'data.customerNo', + type: value('customer'), + name: ['data.name', alt(value('Anonymous'))] +} +``` + +#### `alt(pipeline)` operation +The alt operation will apply the function or pipeline it is given when the data +already in the pipeline is `undefined`. This is how you provide default values +in MapTransform. The pipeline may be as simple as a `value()` operation, a dot +notation path into the source data, or a full pipeline of several operations. + +```javascript +import { alt, transform, value } from 'map-transform' +const currentDate = (data) => new Date() +const formatDate = (data) => { /* implementation not included */ } + +const def11 = { + id: 'data.id', + name: ['data.name', alt(value('Anonymous'))], + updatedAt: [ + 'data.updateDate', + alt('data.createDate'), + alt(transform(currentDate)), + transform(formatDate) + ] +} +``` + +In the example above, we first try to set the `updatedAt` prop to the data found +at `data.updateDate` in the source data. If that does not exist (i.e. we get +`undefined`), the alt operation kicks in and try the path `data.createDate`. If +we still have `undefined`, the second alt will call a transform operation with +the `currentDate` function, that simply returns the current date as a JS object. +Finally, another transform operation pipes whatever data we get from all of this +through the `formatDate` function. + +#### `fwd(pipeline)` and `rev(pipeline)` operation + +All operations in MapTransform will apply in both directions, although some of +them will behave a bit different dependending on the direction. If you want an +operation to only apply to one direction, you need to wrap it in a `fwd()` or +`rev()` operation. The `fwd()` operation will only apply its pipeline when we're +going forward, i.e. mapping in the normal direction, and its pipeline will be +skipped when we're mapping in reverse. The `rev()` operation will only apply its +pipeline when we're mapping in reverse. + +```javascript +import { fwd, rev, transform } from 'map-transform' +const increment = (data) => data + 1 +const decrement = (data) => data - 1 + +const def12 = { + order: ['index', fwd(transform(increment)), rev(transform(decrement))] +} +``` + +In the example above, we increment a zero-based index in the source data to get +a one-based order prop. When reverse mapping, we decrement the order prop to get +back to the zero-based index. + +Note that the `order` pipeline in the example above could also have been written +as `['index', transform(increment, decrement)]`, as the transform operation +supports seperate forward and reverse functions, when it is given two functions. +In this case you may choose what you think is clearer, but in other cases, the +`fwd()` and `rev()` operations are your only friends. + +#### `get(path)` and `set(path)` operation +Both the `get()` and `set()` operations accepts a dot notation path to act on. +The get operation will pull the data at the path in the source data, and insert +it in the pipeline, while the set operation will take what's in the pipeline +and set it on the given path at a new object. + +One reason they come as a pair, is that they will switch roles for reverse +mapping. Their names might make this a bit confusing, but in reverse, the get +operation will set and the set operation will get. + +```javascript +import { get, set } from 'map-transform' + +const def13 = [ + get('data.items[].content'), + set('content[]') +] +``` + +In the example above, the get operation will return an array of whatever is in +the `content` prop at each item in the `data.items[]` array. The set operation +will then create a new object with the array from the pipeline on the `content` +prop. Reverse map this end result, and you'll get what you started with, as the +get and set operations switch roles. + +You may notice that the example above could have been written with a transform +object, and you're absolutely right. The tranform object is actually an +alternative to using get and set operations, and will be converted to get and +set operations behind the curtains. + +This example results in the exact same pipeline as the example above: +```javascript +const def14 = { + 'content[]': 'data.items[].content' +} ``` -In some cases, you might want to include only values that are present in the -source and not use `default` and `defaultRev`. To do this, call -`mapTransform(def).noDefaults(data)` or -`mapTransform(def).rev.noDefaults(data)`. Any property that is not found in -the data will not be set in the resulting object, neither with default value -nor `undefined`. - -Filters, transforms, and mappings are applied in the following order: -- pathFrom (alias: path) -- filterFrom -- transformFrom -- mapping - - path - - transform - - pathTo (the prop) -- transformTo (alias: transform) -- filterTo (alias: filter) -- pathTo - -When reverse mapping, the order is the exact opposite, but keep in mind that -transform pipelines will use the `.rev()` functions instead, and everything -except the mapping may have a reverse version, e.g. `filterToRev`. +It's simply a matter of taste and of what's easiest in each case. We believe +that the transform object is best in cases where you describe a target object +with several properties, while get and set operations is best suited to define +root paths for objects or arrays. + +The get operation aslo has a shortcut in transform pipelines: Simply provide the +path as a string, and will be treated as `get(path)`. + +### Reverse mapping + +When you define a transform pipeline for MapTransform, you also define the +reverse transformation, i.e. you can run data in both direction through the +pipeline. This comes "for free" for simple mappings, but might require some +extra work for more complex mappings with transform operations, alt operations, +etc. + +You should also keep in mind that, depending on your defined pipeline, the +mapping may result in data loss, as only the data that is mapped to the target +object is kept. This may be obvious, but it's an important fact to remember if +you plan to map back and forth between two states – all values must be mapped to +be able to map back to the original data. + +Let's see an example of reverse mapping: +```javascript +import { mapTransform, alt, value } from 'map-transform' + +const def14 = [ + 'data.customers[]', + { + id: 'customerNo', + name: ['fullname', alt(value('Anonymous'))] + } +] + +const dataInTargetState = [ + { id: 'cust1', name: 'Fred Johnsen' }, + { id: 'cust2', name: 'Lucy Knight' }, + { id: 'cust3' } +] + +const dataInSourceState = mapTransform(def14).rev(dataInTargetState) +// --> { + // data: { + // customers: [ + // { customerNo: 'cust1', fullname: 'Fred Johnsen' }, + // { customerNo: 'cust2', fullname: 'Lucy Knight' }, + // { customerNo: 'cust3', fullname: 'Anonymous' }, + // ] + // } +// } +``` + +### Mapping without fallbacks + +MapTransform will try its best to map the data it gets to the state you want, +and will always set all properties, even though the mapping you defined result +in `undefined`. You may include alt operations to provide default or fallback +values for these cases. + +But sometimes, you want just the data that is actually present in the source +data, without defaults or properties set to `undefined`. MapTransform's +`onlyMappedValues()` method gives you this. + +```javascript +import { mapTransform, alt, value } from 'map-transform' + +const def15 = { + id: 'customerNo', + name: ['fullname', alt(value('Anonymous'))] +} + +const mapper = mapTransform(def15) + +mapper({ customerNo: 'cust4' }) +// --> { id: 'cust4', name: 'Anonymous' } + +mapper.onlyMappedValues({ customerNo: 'cust4' }) +// --> { id: 'cust4' } + +mapper.onlyMappedValues({ customerNo: 'cust5', fullname: 'Alex Troy' }) +// --> { id: 'cust5', name: 'Alex Troy' } + +// The method is also available for reverse mapping +mapper.rev.onlyMappedValues({ id: 'cust4' }) +// -> { customerNo: 'cust4' } +``` ### Running the tests From 2ff705eec527504be78acdd06d30abfb6c1f80af Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Mon, 17 Sep 2018 14:29:13 +0200 Subject: [PATCH 12/13] Implement root operation --- README.md | 38 +++++++++++++++++++++++++++++++++++++- src/funcs/root-test.ts | 22 ++++++++++++++++++++++ src/funcs/root.ts | 8 ++++++++ src/index.ts | 1 + src/tests/path-test.ts | 32 +++++++++++++++++++++++++++++++- 5 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/funcs/root-test.ts create mode 100644 src/funcs/root.ts diff --git a/README.md b/README.md index 1337949..20a34ed 100644 --- a/README.md +++ b/README.md @@ -539,9 +539,45 @@ that the transform object is best in cases where you describe a target object with several properties, while get and set operations is best suited to define root paths for objects or arrays. -The get operation aslo has a shortcut in transform pipelines: Simply provide the +The get operation also has a shortcut in transform pipelines: Simply provide the path as a string, and will be treated as `get(path)`. +#### `root(pipeline)` operation + +When you pass a pipeline to the root operation, the pipeline will be apply to +the data that was original passed to the pipeline. Note that the result of a +root pipeline will still be applied at the point you are in the parent pipeline, +so this is not a way to alter data out of the pipeline. + +Let's look at an example: +```javascript +import { mapTransform, root } from 'map-transform' + +const def15 = [ + 'articles[]', + { + id: 'id', + title: 'headline', + section: root('meta.section') + } +] + +const date = { + articles: [ { id: '1', headline: 'An article' }, /* ... */ ], + meta: { section: 'news' } +} + +mapTransform(def15)(data) +// --> [ +// { id: '1', title: 'An article', section: 'news' } +// /* ... */ +// ] +``` + +As you see, every item in the `articles[]` array, will be mapped with the +`section` property from the `meta` object. This would not be available to the +items without the root operation. + ### Reverse mapping When you define a transform pipeline for MapTransform, you also define the diff --git a/src/funcs/root-test.ts b/src/funcs/root-test.ts new file mode 100644 index 0000000..3c24cbd --- /dev/null +++ b/src/funcs/root-test.ts @@ -0,0 +1,22 @@ +import test from 'ava' + +import root from './root' + +test('should apply pipeline to root', (t) => { + const state = { + root: { content: { title: 'An article' }, section: 'news' }, + context: { title: 'An article' }, + value: { title: 'An article' }, + arr: false + } + const expected = { + root: { content: { title: 'An article' }, section: 'news' }, + context: { title: 'An article' }, + value: 'news', + arr: false + } + + const ret = root('section')(state) + + t.deepEqual(ret, expected) +}) diff --git a/src/funcs/root.ts b/src/funcs/root.ts new file mode 100644 index 0000000..3fbe74e --- /dev/null +++ b/src/funcs/root.ts @@ -0,0 +1,8 @@ +import { MapDefinition, MapFunction } from '../types' +import { mapFunctionFromDef } from '../utils/definitionHelpers' + +export default function (def: MapDefinition): MapFunction { + const pipeline = mapFunctionFromDef(def) + + return (state) => pipeline({ ...state, value: state.root }) +} diff --git a/src/index.ts b/src/index.ts index 4156be8..71903d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { populateState, getStateValue } from './utils/stateHelpers' import objectToMapFunction from './utils/objectToMapFunction' export { get, set } from './funcs/getSet' +export { default as root } from './funcs/root' export { default as alt } from './funcs/alt' export { default as value } from './funcs/value' export { default as transform, TransformFunction } from './funcs/transform' diff --git a/src/tests/path-test.ts b/src/tests/path-test.ts index d2f3532..cf7df43 100644 --- a/src/tests/path-test.ts +++ b/src/tests/path-test.ts @@ -1,6 +1,6 @@ import test from 'ava' -import { mapTransform, get, set, fwd, rev } from '..' +import { mapTransform, get, set, fwd, rev, root } from '..' test('should map with object path', (t) => { const def = [ @@ -168,6 +168,36 @@ test('should map with value array', (t) => { t.deepEqual(ret, expected) }) +test('should map with root operation', (t) => { + const def = [ + 'content', + { + attributes: { + title: 'heading' + }, + relationships: { + author: root('meta.writer.username') + } + } + ] + const data = { + content: { heading: 'The heading' }, + meta: { writer: { username: 'johnf' } } + } + const expected = { + attributes: { + title: 'The heading' + }, + relationships: { + author: 'johnf' + } + } + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + test('should return undefined from non-matching path with array index in middle', (t) => { const def = [ 'content.articles[0].content.heading' From 6245b22692d7daf2203ffa4cbddf417c075a03d3 Mon Sep 17 00:00:00 2001 From: Kjell-Morten Date: Mon, 17 Sep 2018 14:41:28 +0200 Subject: [PATCH 13/13] Test with alternative path --- src/tests/default-test.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/tests/default-test.ts b/src/tests/default-test.ts index aaea6d0..d22161d 100644 --- a/src/tests/default-test.ts +++ b/src/tests/default-test.ts @@ -45,6 +45,45 @@ test('should use default value in array', (t) => { t.deepEqual(ret, expected) }) +test('should use default value in reverse', (t) => { + const def = { + title: [ + 'content.heading', + alt(value('Default heading')) + ] + } + const data = [ + {}, + { title: 'From data' } + ] + const expected = [ + { content: { heading: 'Default heading' } }, + { content: { heading: 'From data' } } + ] + + const ret = mapTransform(def).rev(data) + + t.deepEqual(ret, expected) +}) + +test('should use alternative path', (t) => { + const def = { + title: [ 'heading', alt('headline') ] + } + const data = [ + { heading: 'Entry 1' }, + { headline: 'Entry 2' } + ] + const expected = [ + { title: 'Entry 1' }, + { title: 'Entry 2' } + ] + + const ret = mapTransform(def)(data) + + t.deepEqual(ret, expected) +}) + test('should set missing values to undefined when no default', (t) => { const def = { title: 'content.heading'