From ae36f9daa9ea463b9b3832e9cdd3c97870fdb8a3 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 23 Feb 2019 14:10:57 -0500 Subject: [PATCH 01/30] fix: filter ActionSelector on expected props --- src/morphism.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/morphism.ts b/src/morphism.ts index 115c08b..9c9a6ff 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -148,7 +148,7 @@ export type Schema = { | ActionSelector }; export function isActionSelector(value: any): value is ActionSelector { - return isObject(value); + return isObject(value) && value.hasOwnProperty('fn') && value.hasOwnProperty('path'); } /** * Low Level transformer function. From a444fb65e5128ee5b1ff7a215acaff26275648f9 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 23 Feb 2019 16:16:34 -0500 Subject: [PATCH 02/30] feat: add Schema Parser. Seed Morphism Schema Tree recursively --- src/morphism.ts | 229 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 182 insertions(+), 47 deletions(-) diff --git a/src/morphism.ts b/src/morphism.ts index 9c9a6ff..3ec4701 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -28,7 +28,7 @@ import { isString, get, isFunction, zipObject, isUndefined, assignInWith, aggreg * ``` * */ -export interface ActionFunction { +export interface ActionFunction { (iteratee: S, source: S[], target: D): R; } /** @@ -54,7 +54,7 @@ export interface ActionFunction { * ``` * */ -export type ActionString = keyof T; +export type ActionString = keyof T; /** * An Array of String that allows to perform a function over source property * @@ -97,7 +97,10 @@ export type ActionAggregator = string[]; *``` * */ -export type ActionSelector = { path: string | string[]; fn: (fieldValue: any, items: Source[]) => R }; +export type ActionSelector = { + path: string | string[]; + fn: (fieldValue: any, items: Source[]) => R; +}; /** * A structure-preserving object from a source data towards a target data. @@ -150,6 +153,133 @@ export type Schema = { export function isActionSelector(value: any): value is ActionSelector { return isObject(value) && value.hasOwnProperty('fn') && value.hasOwnProperty('path'); } +function isActionAggregator(value: any): value is ActionAggregator { + return Array.isArray(value); +} +function isActionString(value: any): value is string { + return isString(value); +} +function isActionFunction(value: any): value is ActionFunction { + return isFunction(value); +} + +enum Action { + ActionFunction = 'ActionFunction', + ActionAggregator = 'ActionAggregator', + ActionString = 'ActionString', + ActionSelector = 'ActionSelector' +} + +enum NodeDataKind { + Root = 'Root' +} + +type Actions = ActionFunction | ActionAggregator | ActionString | ActionSelector; +type SchemaNodeDataKind = Action | NodeDataKind; +interface SchemaNodeData { + propertyName: string; + action: Actions; + kind: SchemaNodeDataKind; + targetPropertyPath: string; +} +interface SchemaNode { + data: SchemaNodeData; + parent: SchemaNode; + children: SchemaNode[]; +} + +type Overwrite = { [P in Exclude]: T1[P] } & T2; + +type AddNode = Overwrite< + SchemaNodeData, + { + kind?: Action; + targetPropertyPath?: string; + } +>; + +class MophismSchemaTree { + private root: SchemaNode; + constructor() { + this.root = { + data: { targetPropertyPath: '', propertyName: 'MorphismTreeRoot', action: null, kind: NodeDataKind.Root }, + parent: null, + children: [] + }; + } + *traverseBFS() { + const queue = []; + queue.push(this.root); + let node = queue.shift(); + while (node) { + for (let i = 0, length = node.children.length; i < length; i++) { + queue.push(node.children[i]); + } + yield node; + node = queue.shift(); + } + } + *traverseDFS() { + const stack = [this.root]; + let node = stack.pop(); + while (node) { + for (let i = 0, length = node.children.length; i < length; i++) { + stack.push(node.children[i]); + } + yield node; + node = stack.pop(); + } + } + add(data: AddNode, toPropertyPath?: string) { + const kind = this.getActionKind(data.action); + const nodeToAdd: SchemaNode = { data: { ...data, kind, targetPropertyPath: '' }, parent: null, children: [] }; + if (!toPropertyPath) { + nodeToAdd.parent = this.root; + nodeToAdd.data.targetPropertyPath = nodeToAdd.data.propertyName; + this.root.children.push(nodeToAdd); + } else { + for (const node of this.traverseBFS()) { + if (node.data && node.data.targetPropertyPath === toPropertyPath) { + nodeToAdd.parent = node.parent; + nodeToAdd.data.targetPropertyPath = `${node.parent.data.targetPropertyPath}.${nodeToAdd.data.propertyName}`; + node.parent.children.push(nodeToAdd); + } + } + } + } + + getActionKind(action: Actions) { + if (isActionString(action)) return Action.ActionString; + if (isFunction(action)) return Action.ActionFunction; + if (isActionSelector(action)) return Action.ActionSelector; + if (isActionAggregator(action)) return Action.ActionAggregator; + } +} + +function parseSchema(schema: Schema | StrictSchema | string | number) { + const treeSchema = new MophismSchemaTree(); + seedTreeSchema(treeSchema, schema); + return treeSchema; +} +function seedTreeSchema( + tree: MophismSchemaTree, + partialSchema: Partial | string | number, + actionKey?: string +) { + if ( + isString(partialSchema) || + isFunction(partialSchema) || + isActionSelector(partialSchema) || + isActionAggregator(partialSchema) + ) { + // console.log('action to process', actionKey, partialSchema); + tree.add({ propertyName: actionKey, action: partialSchema as Actions }); + return; + } + Object.keys(partialSchema).forEach(key => { + seedTreeSchema(tree, partialSchema[key], key); + }); +} /** * Low Level transformer function. * Take a plain object as input and transform its values using a specified schema. @@ -164,54 +294,59 @@ function transformValuesFromObject( items: Source[], objectToCompute: TDestination ) { - return Object.entries(schema) - .map(([targetProperty, action]) => { - // iterate on every action of the schema - if (isString(action)) { - // Action: string path => [ target: 'source' ] - return { [targetProperty]: get(object, action) }; - } else if (isFunction(action)) { - // Action: Free Computin - a callback called with the current object and collection [ destination: (object) => {...} ] - return { [targetProperty]: action.call(undefined, object, items, objectToCompute) }; - } else if (Array.isArray(action)) { - // Action: Aggregator - string paths => : [ destination: ['source1', 'source2', 'source3'] ] - return { [targetProperty]: aggregator(action, object) }; - } else if (isActionSelector(action)) { - // Action: a path and a function: [ destination : { path: 'source', fn:(fieldValue, items) }] - let result; - try { - let value; - if (Array.isArray(action.path)) { - value = aggregator(action.path, object); - } else if (isString(action.path)) { - value = get(object, action.path); - } - result = action.fn.call(undefined, value, object, items, objectToCompute); - } catch (e) { - e.message = `Unable to set target property [${targetProperty}]. - \n An error occured when applying [${action.fn.name}] on property [${action.path}] - \n Internal error: ${e.message}`; - throw e; - } + const tree = parseSchema(schema); + const transformChunks = []; + for (const node of tree.traverseBFS()) { + const { propertyName: targetProperty, action, kind } = node.data; + console.log('node', targetProperty, action); - return { [targetProperty]: result }; + // iterate on every action of the schema + if (isActionString(action)) { + // Action: string path => [ target: 'source' ] + transformChunks.push({ [targetProperty]: get(object, action) }); + } else if (isActionFunction(action)) { + // Action: Free Computin - a callback called with the current object and collection [ destination: (object) => {...} ] + transformChunks.push({ [targetProperty]: action.call(undefined, object, items, objectToCompute) }); + } else if (isActionAggregator(action)) { + // Action: Aggregator - string paths => : [ destination: ['source1', 'source2', 'source3'] ] + transformChunks.push({ [targetProperty]: aggregator(action, object) }); + } else if (isActionSelector(action)) { + // Action: a path and a function: [ destination : { path: 'source', fn:(fieldValue, items) }] + let result; + try { + let value; + if (Array.isArray(action.path)) { + value = aggregator(action.path, object); + } else if (isString(action.path)) { + value = get(object, action.path); + } + result = action.fn.call(undefined, value, object, items, objectToCompute); + } catch (e) { + e.message = `Unable to set target property [${targetProperty}]. + \n An error occured when applying [${action.fn.name}] on property [${action.path}] + \n Internal error: ${e.message}`; + throw e; } - }) - .reduce((finalObject, keyValue) => { - const undefinedValueCheck = (destination: any, source: any) => { - // Take the Object class value property if the incoming property is undefined - if (isUndefined(source)) { - if (!isUndefined(destination)) { - return destination; - } else { - return; // No Black Magic Fuckery here, if the source and the destination are undefined, we don't do anything - } + + transformChunks.push({ [targetProperty]: result }); + } + } + + return transformChunks.reduce((finalObject, keyValue) => { + const undefinedValueCheck = (destination: any, source: any) => { + // Take the Object class value property if the incoming property is undefined + if (isUndefined(source)) { + if (!isUndefined(destination)) { + return destination; } else { - return source; + return; // No Black Magic Fuckery here, if the source and the destination are undefined, we don't do anything } - }; - return assignInWith(finalObject, keyValue, undefinedValueCheck); - }, objectToCompute); + } else { + return source; + } + }; + return assignInWith(finalObject, keyValue, undefinedValueCheck); + }, objectToCompute); } interface Constructable { new (...args: any[]): T; From fa4495f963eacf6cb137e7eee38fa8009602c610 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 23 Feb 2019 17:33:06 -0500 Subject: [PATCH 03/30] fix: type aggregator helper --- src/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index 7802c10..04bf18b 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,5 +1,5 @@ -export const aggregator = (paths: any, object: any) => { - return paths.reduce((delta: any, path: any) => { +export const aggregator = (paths: string[], object: any) => { + return paths.reduce((delta, path) => { return set(delta, path, get(object, path)); }, {}); }; From 2b71e0f03cbbccad27d0efff4f67814c82d3363a Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 23 Feb 2019 17:34:49 -0500 Subject: [PATCH 04/30] feat: compute action ahead when node are added to the tree. --- src/morphism.ts | 155 ++++++++++++++++++++++++------------------------ 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/src/morphism.ts b/src/morphism.ts index 3ec4701..5fe02ec 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -176,15 +176,18 @@ enum NodeDataKind { type Actions = ActionFunction | ActionAggregator | ActionString | ActionSelector; type SchemaNodeDataKind = Action | NodeDataKind; + +type PreparedAction = (params: { object: any; items: any; objectToCompute: any }) => any; interface SchemaNodeData { propertyName: string; - action: Actions; + action: Actions | null; + preparedAction?: PreparedAction; kind: SchemaNodeDataKind; targetPropertyPath: string; } interface SchemaNode { data: SchemaNodeData; - parent: SchemaNode; + parent: SchemaNode | null; children: SchemaNode[]; } @@ -195,6 +198,7 @@ type AddNode = Overwrite< { kind?: Action; targetPropertyPath?: string; + preparedAction?: (...args: any) => any; } >; @@ -215,37 +219,24 @@ class MophismSchemaTree { for (let i = 0, length = node.children.length; i < length; i++) { queue.push(node.children[i]); } - yield node; - node = queue.shift(); - } - } - *traverseDFS() { - const stack = [this.root]; - let node = stack.pop(); - while (node) { - for (let i = 0, length = node.children.length; i < length; i++) { - stack.push(node.children[i]); + if (node.data.kind !== NodeDataKind.Root) { + yield node; } - yield node; - node = stack.pop(); + node = queue.shift(); } } - add(data: AddNode, toPropertyPath?: string) { + + add(data: AddNode) { + if (!data.action) throw new Error(`Action of ${data.propertyName} can't be ${data.action}.`); + const kind = this.getActionKind(data.action); + if (!kind) throw new Error(`The action specified for ${data.propertyName} is not supported.`); const nodeToAdd: SchemaNode = { data: { ...data, kind, targetPropertyPath: '' }, parent: null, children: [] }; - if (!toPropertyPath) { - nodeToAdd.parent = this.root; - nodeToAdd.data.targetPropertyPath = nodeToAdd.data.propertyName; - this.root.children.push(nodeToAdd); - } else { - for (const node of this.traverseBFS()) { - if (node.data && node.data.targetPropertyPath === toPropertyPath) { - nodeToAdd.parent = node.parent; - nodeToAdd.data.targetPropertyPath = `${node.parent.data.targetPropertyPath}.${nodeToAdd.data.propertyName}`; - node.parent.children.push(nodeToAdd); - } - } - } + nodeToAdd.data.preparedAction = this.getPreparedAction(nodeToAdd.data); + + nodeToAdd.parent = this.root; + nodeToAdd.data.targetPropertyPath = nodeToAdd.data.propertyName; + this.root.children.push(nodeToAdd); } getActionKind(action: Actions) { @@ -254,25 +245,63 @@ class MophismSchemaTree { if (isActionSelector(action)) return Action.ActionSelector; if (isActionAggregator(action)) return Action.ActionAggregator; } + + getPreparedAction(nodeData: SchemaNodeData): PreparedAction { + const { propertyName: targetProperty, action } = nodeData; + // iterate on every action of the schema + if (isActionString(action)) { + // Action: string path => [ target: 'source' ] + return ({ object }) => ({ [targetProperty]: get(object, action) }); + } else if (isActionFunction(action)) { + // Action: Free Computin - a callback called with the current object and collection [ destination: (object) => {...} ] + return ({ object, items, objectToCompute }) => ({ + [targetProperty]: action.call(undefined, object, items, objectToCompute) + }); + } else if (isActionAggregator(action)) { + // Action: Aggregator - string paths => : [ destination: ['source1', 'source2', 'source3'] ] + return ({ object }) => ({ [targetProperty]: aggregator(action, object) }); + } else if (isActionSelector(action)) { + // Action: a path and a function: [ destination : { path: 'source', fn:(fieldValue, items) }] + return ({ object, items, objectToCompute }) => { + let result; + try { + let value; + if (Array.isArray(action.path)) { + value = aggregator(action.path, object); + } else if (isString(action.path)) { + value = get(object, action.path); + } + result = action.fn.call(undefined, value, object, items, objectToCompute); + } catch (e) { + e.message = `Unable to set target property [${targetProperty}]. + \n An error occured when applying [${action.fn.name}] on property [${ + action.path + }] + \n Internal error: ${e.message}`; + throw e; + } + return { [targetProperty]: result }; + }; + } else { + throw new Error(`The action specified for ${targetProperty} is not supported.`); + } + } +} +function isValidAction(action: any) { + return isString(action) || isFunction(action) || isActionSelector(action) || isActionAggregator(action); } function parseSchema(schema: Schema | StrictSchema | string | number) { - const treeSchema = new MophismSchemaTree(); - seedTreeSchema(treeSchema, schema); - return treeSchema; + const tree = new MophismSchemaTree(); + seedTreeSchema(tree, schema); + return tree; } function seedTreeSchema( tree: MophismSchemaTree, partialSchema: Partial | string | number, actionKey?: string -) { - if ( - isString(partialSchema) || - isFunction(partialSchema) || - isActionSelector(partialSchema) || - isActionAggregator(partialSchema) - ) { - // console.log('action to process', actionKey, partialSchema); +): MophismSchemaTree { + if (isValidAction(partialSchema) && actionKey) { tree.add({ propertyName: actionKey, action: partialSchema as Actions }); return; } @@ -290,46 +319,14 @@ function seedTreeSchema( */ function transformValuesFromObject( object: Source, - schema: Schema, + tree: MophismSchemaTree, items: Source[], objectToCompute: TDestination ) { - const tree = parseSchema(schema); const transformChunks = []; for (const node of tree.traverseBFS()) { - const { propertyName: targetProperty, action, kind } = node.data; - console.log('node', targetProperty, action); - - // iterate on every action of the schema - if (isActionString(action)) { - // Action: string path => [ target: 'source' ] - transformChunks.push({ [targetProperty]: get(object, action) }); - } else if (isActionFunction(action)) { - // Action: Free Computin - a callback called with the current object and collection [ destination: (object) => {...} ] - transformChunks.push({ [targetProperty]: action.call(undefined, object, items, objectToCompute) }); - } else if (isActionAggregator(action)) { - // Action: Aggregator - string paths => : [ destination: ['source1', 'source2', 'source3'] ] - transformChunks.push({ [targetProperty]: aggregator(action, object) }); - } else if (isActionSelector(action)) { - // Action: a path and a function: [ destination : { path: 'source', fn:(fieldValue, items) }] - let result; - try { - let value; - if (Array.isArray(action.path)) { - value = aggregator(action.path, object); - } else if (isString(action.path)) { - value = get(object, action.path); - } - result = action.fn.call(undefined, value, object, items, objectToCompute); - } catch (e) { - e.message = `Unable to set target property [${targetProperty}]. - \n An error occured when applying [${action.fn.name}] on property [${action.path}] - \n Internal error: ${e.message}`; - throw e; - } - - transformChunks.push({ [targetProperty]: result }); - } + const { preparedAction } = node.data; + transformChunks.push(preparedAction({ object, objectToCompute, items })); } return transformChunks.reduce((finalObject, keyValue) => { @@ -353,6 +350,8 @@ interface Constructable { } function transformItems>(schema: TSchema, type?: Constructable) { + const tree = parseSchema(schema); + function mapper(source: any) { if (!source) { return source; @@ -361,20 +360,20 @@ function transformItems>(schema: TSchema, type return source.map(obj => { if (type) { const classObject = new type(); - return transformValuesFromObject(obj, schema, source, classObject); + return transformValuesFromObject(obj, tree, source, classObject); } else { const jsObject = {}; - return transformValuesFromObject(obj, schema, source, jsObject); + return transformValuesFromObject(obj, tree, source, jsObject); } }); } else { const object = source; if (type) { const classObject = new type(); - return transformValuesFromObject(object, schema, [object], classObject); + return transformValuesFromObject(object, tree, [object], classObject); } else { const jsObject = {}; - return transformValuesFromObject(object, schema, [object], jsObject); + return transformValuesFromObject(object, tree, [object], jsObject); } } } From 682c481a4da5e9887c90abccd8e00a8e9f067f41 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Fri, 1 Mar 2019 20:03:16 -0500 Subject: [PATCH 05/30] test: fix types in tests --- src/classObjects.spec.ts | 4 ++-- src/morphism.spec.ts | 17 ++++++++--------- tsconfig.json | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/classObjects.spec.ts b/src/classObjects.spec.ts index 160f1ad..64a56e3 100644 --- a/src/classObjects.spec.ts +++ b/src/classObjects.spec.ts @@ -5,10 +5,10 @@ describe('Morphism', () => { describe('Decorators - Function Decorator', () => { const schema = { foo: 'bar' }; interface ITarget { - foo: string; + foo: string | null; } class Target implements ITarget { - foo: string = null; + foo = null; } class Service { diff --git a/src/morphism.spec.ts b/src/morphism.spec.ts index 3a7b335..1e50a11 100644 --- a/src/morphism.spec.ts +++ b/src/morphism.spec.ts @@ -1,12 +1,12 @@ import Morphism, { StrictSchema } from './morphism'; class User { - firstName: string; - lastName: string; - phoneNumber: string; + firstName?: string; + lastName?: string; + phoneNumber?: string; type?: string; - groups?: Array; + groups: Array = new Array(); constructor(firstName?: string, lastName?: string, phoneNumber?: string) { this.firstName = firstName; @@ -14,7 +14,6 @@ class User { this.phoneNumber = phoneNumber; this.type = 'User'; // Use to test default value scenario - this.groups = new Array(); } /** @@ -22,7 +21,7 @@ class User { * @param {} group * @param {} externalTrigger */ - addToGroup?(group: any, externalTrigger: any) { + addToGroup(group: any, externalTrigger: any) { this.groups.push(group); externalTrigger(this, group); } @@ -94,7 +93,7 @@ describe('Morphism', () => { it('should throw an exception when setting a mapper with a falsy schema', function() { expect(() => { - Morphism.setMapper(User, null); + Morphism.setMapper(User, null as any); }).toThrow(); }); @@ -397,7 +396,7 @@ describe('Morphism', () => { describe('Mappers Registry', function() { it('should throw an exception when using Registration function without parameters', function() { - expect(() => Morphism.register(null, null)).toThrow(); + expect(() => Morphism.register(null as any, null)).toThrow(); }); it('should throw an exception when trying to register a mapper type more than once', function() { @@ -468,7 +467,7 @@ describe('Morphism', () => { it('should allow straight mapping from a Type without a schema', () => { let userName = 'user-name'; - let user = Morphism(null, { firstName: userName }, User); + let user = Morphism(null as any, { firstName: userName }, User); expect(user).toEqual(new User(userName)); }); diff --git a/tsconfig.json b/tsconfig.json index 862d225..3f17486 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,5 +17,5 @@ "downlevelIteration": true }, "include": ["src"], - "exclude": ["dist", "**/*/*.spec.ts"] + "exclude": ["dist"] } From e7d26da6c01537cafac3e1c2f10ec419e80aa305 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Fri, 1 Mar 2019 20:26:24 -0500 Subject: [PATCH 06/30] test: run check types on UTs on circleCI --- .circleci/config.yml | 3 +++ package-lock.json | 50 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 14 +++++++------ tsconfig.json | 2 +- tsconfig.test.json | 7 +++++++ 5 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 tsconfig.test.json diff --git a/.circleci/config.yml b/.circleci/config.yml index e815698..fdbdf18 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,6 +25,9 @@ jobs: - run: name: Run Tests command: npm test -- --coverage + - run: + name: Build Tests Types + command: npm run test:types deploy-docs: docker: diff --git a/package-lock.json b/package-lock.json index b53bed1..c0971c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1291,6 +1291,12 @@ } } }, + "@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", + "dev": true + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -1378,6 +1384,50 @@ "@types/node": "*" } }, + "@types/tapable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz", + "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==", + "dev": true + }, + "@types/uglify-js": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", + "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/webpack": { + "version": "4.4.25", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.4.25.tgz", + "integrity": "sha512-YaYVbSK1bC3xiAWFLSgDQyVHdCTNq5cLlcx633basmrwSoUxJiv4SZ0SoT1uoF15zWx98afOcCbqA1YHeCdRYA==", + "dev": true, + "requires": { + "@types/anymatch": "*", + "@types/node": "*", + "@types/tapable": "*", + "@types/uglify-js": "*", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", diff --git a/package.json b/package.json index 9594404..aa06886 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "build:types": "tsc --emitDeclarationOnly", "watch:types": "npm run build:types -- -w >/dev/null", "test": "jest", + "test:types": "tsc -p tsconfig.test.json --emitDeclarationOnly", "test:coverage": "npm run test -- --coverage", "semantic-release": "semantic-release", "docs": "typedoc", @@ -51,24 +52,25 @@ "@babel/preset-typescript": "^7.1.0", "@types/jest": "24.0.9", "@types/node": "^11.10.0", + "@types/webpack": "^4.4.25", "babel-loader": "^8.0.5", "fork-ts-checker-webpack-plugin": "^0.5.2", "jest": "24.1.0", "nodemon": "^1.18.9", "nodemon-webpack-plugin": "^4.0.7", - "npm-run-all": "^4.1.5", - "ts-node": "^8.0.2", - "typescript": "^3.3.3333", - "webpack": "4.29.6", - "webpack-cli": "^3.1.0", "now": "^14.0.2", + "npm-run-all": "^4.1.5", "semantic-release": "^15.8.1", "source-map-loader": "^0.2.1", + "ts-node": "^8.0.2", "tslint": "^5.11.0", "tslint-loader": "^3.6.0", "typedoc": "^0.14.2", "typedoc-plugin-external-module-name": "^2.0.0", - "typedoc-plugin-internal-external": "^2.0.1" + "typedoc-plugin-internal-external": "^2.0.1", + "typescript": "^3.3.3333", + "webpack": "4.29.6", + "webpack-cli": "^3.1.0" }, "eslintConfig": { "extends": "xo-space", diff --git a/tsconfig.json b/tsconfig.json index 3f17486..862d225 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,5 +17,5 @@ "downlevelIteration": true }, "include": ["src"], - "exclude": ["dist"] + "exclude": ["dist", "**/*/*.spec.ts"] } diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..dabdce5 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "temp" + }, + "include": ["**/*/*.spec.ts"] +} From 6a77f355f75b602137907c2fa99b5fdbcccaae59 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Fri, 1 Mar 2019 20:29:24 -0500 Subject: [PATCH 07/30] test: include test files --- tsconfig.test.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.test.json b/tsconfig.test.json index dabdce5..21716e3 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -3,5 +3,6 @@ "compilerOptions": { "outDir": "temp" }, - "include": ["**/*/*.spec.ts"] + "include": ["src/**/*.spec.ts"], + "exclude": ["dist"] } From 21c0a96bfdb399038db2afdd36d798dba781ab37 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Thu, 7 Mar 2019 00:54:37 -0500 Subject: [PATCH 08/30] chore: update ts configs --- tsconfig.json | 6 +++--- tsconfig.prod.json | 21 +++++++++++++++++++++ tsconfig.test.json | 8 -------- 3 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 tsconfig.prod.json delete mode 100644 tsconfig.test.json diff --git a/tsconfig.json b/tsconfig.json index 862d225..edf1cfb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "es5", - "module": "commonjs", + "module": "esnext", "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, @@ -16,6 +16,6 @@ "strictPropertyInitialization": false, "downlevelIteration": true }, - "include": ["src"], - "exclude": ["dist", "**/*/*.spec.ts"] + "include": ["src/**/*.ts"], + "exclude": ["dist"] } diff --git a/tsconfig.prod.json b/tsconfig.prod.json new file mode 100644 index 0000000..46a6f95 --- /dev/null +++ b/tsconfig.prod.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "esnext", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "lib": ["es2017", "dom"], + "strict": true, + "esModuleInterop": true, + "declarationDir": "dist/types", + "declaration": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strictPropertyInitialization": false, + "downlevelIteration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "**/*/*.spec.ts"] +} diff --git a/tsconfig.test.json b/tsconfig.test.json deleted file mode 100644 index 21716e3..0000000 --- a/tsconfig.test.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "temp" - }, - "include": ["src/**/*.spec.ts"], - "exclude": ["dist"] -} From eb762c2f84de10bbbba66109067d94e676e730e9 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Thu, 7 Mar 2019 00:55:27 -0500 Subject: [PATCH 09/30] ci: update build system --- .babelrc | 3 ++- .circleci/config.yml | 2 +- package.json | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.babelrc b/.babelrc index aae57ff..bccdc67 100644 --- a/.babelrc +++ b/.babelrc @@ -4,7 +4,8 @@ "@babel/preset-env", { "targets": { - "node": "current" + "node": "current", + "esmodules": true } } ], diff --git a/.circleci/config.yml b/.circleci/config.yml index fdbdf18..658b91d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,7 +24,7 @@ jobs: # run tests! - run: name: Run Tests - command: npm test -- --coverage + command: npm run test:coverage - run: name: Build Tests Types command: npm run test:types diff --git a/package.json b/package.json index aa06886..966b949 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "start": "run-p watch:*", "build:js": "TS_NODE_PROJECT=\"tsconfig.webpack.json\" webpack --mode=production", "watch:js": "npm run build:js -- -w --display \"errors-only\"", - "build:types": "tsc --emitDeclarationOnly", + "build:types": "tsc -p tsconfig.prod.json --emitDeclarationOnly", "watch:types": "npm run build:types -- -w >/dev/null", "test": "jest", - "test:types": "tsc -p tsconfig.test.json --emitDeclarationOnly", + "test:types": "tsc --emitDeclarationOnly", "test:coverage": "npm run test -- --coverage", "semantic-release": "semantic-release", "docs": "typedoc", From 9539dadea90225577686caef4b22d1ae8461f5b6 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Thu, 7 Mar 2019 00:56:39 -0500 Subject: [PATCH 10/30] fix: remove partial from data type in mapper --- src/morphism.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/morphism.ts b/src/morphism.ts index 03149d2..77b78cd 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -393,8 +393,8 @@ type DestinationFromSchema = T extends StrictSchema | Schema = { [P in keyof TSchema]: DestinationFromSchema[P] }; export interface Mapper> { - (data: Partial>[]): TResult[]; - (data: Partial>): TResult; + (data: SourceFromSchema[]): TResult[]; + (data: SourceFromSchema): TResult; } /** From 5c4489e6e295b02f406253d158a5096d9b4e4381 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Thu, 7 Mar 2019 00:58:24 -0500 Subject: [PATCH 11/30] fix: fix register types --- src/morphism.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/morphism.ts b/src/morphism.ts index 77b78cd..a18a224 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -438,7 +438,7 @@ export function morphism< export function morphism( schema: TSchema, - items: null, + items: SourceFromSchema | null, type: Constructable ): Mapper; // morphism({}, null, T) => mapper(S) => T @@ -450,7 +450,7 @@ export function morphism( export function morphism>( schema: TSchema, - items?: SourceFromSchema | null | undefined, + items?: SourceFromSchema | null, type?: Constructable ) { if (items === undefined && type === undefined) { @@ -479,7 +479,9 @@ export interface IMorphismRegistry { * @param schema Structure-preserving object from a source data towards a target data. * */ + register(type: Constructable): Mapper, Target>; register(type: Constructable, schema?: TSchema): Mapper; + /** * Transform any input in the specified Class * @@ -548,13 +550,18 @@ export class MorphismRegistry implements IMorphismRegistry { * @param schema Structure-preserving object from a source data towards a target data. * */ - register(type: Constructable, schema: TSchema | {} = {}) { + register(type: Constructable, schema?: TSchema) { if (!type && !schema) { - throw new Error('type paramater is required when register a mapping'); + throw new Error('type paramater is required when you register a mapping'); } else if (this.exists(type)) { throw new Error(`A mapper for ${type.name} has already been registered`); } - const mapper = morphism(schema, null, type); + let mapper; + if (schema) { + mapper = morphism(schema, null, type); + } else { + mapper = morphism({}, null, type); + } this._registry.cache.set(type, mapper); return mapper; } From c19360ecced4b3d996429b712c02694906b7d164 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Thu, 7 Mar 2019 00:58:52 -0500 Subject: [PATCH 12/30] test: update UT accordingly to strictNullCheck --- src/morphism.spec.ts | 3 ++- src/typescript.spec.ts | 34 +++++++++++++++++++++++++++++----- src/typings.spec.ts | 6 +++++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/morphism.spec.ts b/src/morphism.spec.ts index 1e50a11..93e76f6 100644 --- a/src/morphism.spec.ts +++ b/src/morphism.spec.ts @@ -514,11 +514,12 @@ describe('Morphism', () => { }); it('should override the default value if source value is defined', function() { - let sourceData: any = { + let sourceData = { phoneNumber: null }; let mapper = Morphism.register(User, {}); + let result = mapper([sourceData])[0]; expect(new User().phoneNumber).toEqual(undefined); expect(result.phoneNumber).toEqual(null); diff --git a/src/typescript.spec.ts b/src/typescript.spec.ts index b342da7..5eb578d 100644 --- a/src/typescript.spec.ts +++ b/src/typescript.spec.ts @@ -49,9 +49,20 @@ describe('Morphism', () => { const schema: StrictSchema = { d1: 's1' }; - morphism(schema)([{ s1: 'test' }]).shift().d1; + const a = morphism(schema)([{ s1: 'test' }]); + const itemA = a.shift(); + expect(itemA).toBeDefined(); + if (itemA) { + itemA.d1; + } + morphism(schema, { s1: 'teest' }).d1.toString(); - morphism(schema, [{ s1: 'teest' }]).shift().d1; + const b = morphism(schema, [{ s1: 'teest' }]); + const itemB = b.shift(); + expect(itemB).toBeDefined(); + if (itemB) { + itemB.d1; + } morphism(schema, [{ s1: 'teest' }]); morphism(schema, [{ s1: 'test' }]); morphism(schema, [{}]); @@ -78,20 +89,33 @@ describe('Morphism', () => { } const a = morphism({ namingIsHard: 'boring_api_field' }, [{ boring_api_field: 2 }]); - a.pop().namingIsHard; + const itemA = a.pop(); + expect(itemA).toBeDefined(); + if (itemA) { + itemA.namingIsHard; + } const b = morphism({ namingIsHard: 'boring_api_field' }, { boring_api_field: 2 }); b.namingIsHard; const c = morphism({ namingIsHard: 'boring_api_field' }, [{ boring_api_field: 2 }]); - c.pop().namingIsHard; + const itemC = c.pop(); + expect(itemC).toBeDefined(); + if (itemC) { + itemC.namingIsHard; + } const d = morphism({ namingIsHard: 'boring_api_field' }, { boring_api_field: 2 }); d.namingIsHard; morphism({ namingIsHard: 'boring_api_field' }); morphism({ namingIsHard: 'boring_api_field' })({ boring_api_field: 2 }); - morphism({ namingIsHard: 'boring_api_field' })([{ boring_api_field: 2 }]).pop().namingIsHard; + const e = morphism({ namingIsHard: 'boring_api_field' })([{ boring_api_field: 2 }]); + const itemE = e.pop(); + expect(itemE).toBeDefined(); + if (itemE) { + itemE.namingIsHard; + } }); it('', () => { diff --git a/src/typings.spec.ts b/src/typings.spec.ts index 48e7efb..eeffe76 100644 --- a/src/typings.spec.ts +++ b/src/typings.spec.ts @@ -74,9 +74,13 @@ describe('Morphism', () => { const target = morphism(schema, source); const targets = morphism(schema, [source]); + const singleTarget = targets.shift(); expect(target.foo).toEqual('value'); - expect(targets.shift().foo).toEqual('value'); + expect(singleTarget).toBeDefined(); + if (singleTarget) { + expect(singleTarget.foo).toEqual('value'); + } }); }); From 3a7c6b225bff36207abf2a52962e4fcc3e0546bd Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 16 Mar 2019 05:09:44 -0400 Subject: [PATCH 13/30] chore: move types around --- src/morphism.ts | 327 ++---------------------------------------------- src/types.ts | 162 ++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 314 deletions(-) create mode 100644 src/types.ts diff --git a/src/morphism.ts b/src/morphism.ts index a18a224..0c3d1ee 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -1,314 +1,10 @@ /** * @module morphism */ -import { isString, get, isFunction, zipObject, isUndefined, assignInWith, aggregator, isObject } from './helpers'; +import { zipObject, isUndefined, assignInWith, set, get } from './helpers'; +import { Schema, StrictSchema } from './types'; +import { MophismSchemaTree, parseSchema } from './MorphismTree'; -/** - * A Function invoked per iteration - * @param {} iteratee The current element to transform - * @param source The source input to transform - * @param target The current element transformed - * @example - * ```typescript - * - * const source = { - * foo: { - * bar: 'bar' - * } - * }; - * let schema = { - * bar: iteratee => { - * // Apply a function over the source propery - * return iteratee.foo.bar; - * } - * }; - * - * morphism(schema, source); - * //=> { bar: 'bar' } - * ``` - * - */ -export interface ActionFunction { - (iteratee: S, source: S[], target: D): R; -} -/** - * A String path that indicates where to find the property in the source input - * - * @example - * ```typescript - * - * const source = { - * foo: 'baz', - * bar: ['bar', 'foo'], - * baz: { - * qux: 'bazqux' - * } - * }; - * const schema = { - * foo: 'foo', // Simple Projection - * bazqux: 'baz.qux' // Grab a value from a nested property - * }; - * - * morphism(schema, source); - * //=> { foo: 'baz', bazqux: 'bazqux' } - * ``` - * - */ -export type ActionString = keyof T; -/** - * An Array of String that allows to perform a function over source property - * - * @example - * ```typescript - * - * const source = { - * foo: 'foo', - * bar: 'bar' - * }; - * let schema = { - * fooAndBar: ['foo', 'bar'] // Grab these properties into fooAndBar - * }; - * - * morphism(schema, source); - * //=> { fooAndBar: { foo: 'foo', bar: 'bar' } } - * ``` - */ -export type ActionAggregator = string[]; -/** - * An Object that allows to perform a function over a source property's value - * - * @example - * ```typescript - * - * const source = { - * foo: { - * bar: 'bar' - * } - * }; - * let schema = { - * barqux: { - * path: 'foo.bar', - * fn: value => `${value}qux` // Apply a function over the source property's value - * } - * }; - * - * morphism(schema, source); - * //=> { barqux: 'barqux' } - *``` - * - */ -export type ActionSelector = { - path: string | string[]; - fn: (fieldValue: any, object: Source, items: Source, objectToCompute: R) => R; -}; - -/** - * A structure-preserving object from a source data towards a target data. - * - * The keys of the schema match the desired destination structure. - * Each value corresponds to an Action applied by Morphism when iterating over the input data - * @example - * ```typescript - * - * const input = { - * foo: { - * baz: 'value1' - * } - * }; - * - * const schema: Schema = { - * bar: 'foo', // ActionString - * qux: ['foo', 'foo.baz'], // ActionAggregator - * quux: (iteratee, source, destination) => { // ActionFunction - * return iteratee.foo; - * }, - * corge: { // ActionSelector - * path: 'foo.baz', - * fn: (propertyValue, source) => { - * return propertyValue; - * } - * } - * }; - * - * morphism(schema, input); - * ``` - */ - -export type StrictSchema = { - /** `destinationProperty` is the name of the property of the target object you want to produce */ - [destinationProperty in keyof Target]: - | ActionString - | ActionFunction - | ActionAggregator - | ActionSelector -}; -export type Schema = { - /** `destinationProperty` is the name of the property of the target object you want to produce */ - [destinationProperty in keyof Target]?: - | ActionString - | ActionFunction - | ActionAggregator - | ActionSelector -}; -export function isActionSelector(value: any): value is ActionSelector { - return isObject(value) && value.hasOwnProperty('fn') && value.hasOwnProperty('path'); -} -function isActionAggregator(value: any): value is ActionAggregator { - return Array.isArray(value); -} -function isActionString(value: any): value is string { - return isString(value); -} -function isActionFunction(value: any): value is ActionFunction { - return isFunction(value); -} - -enum Action { - ActionFunction = 'ActionFunction', - ActionAggregator = 'ActionAggregator', - ActionString = 'ActionString', - ActionSelector = 'ActionSelector' -} - -enum NodeDataKind { - Root = 'Root' -} - -type Actions = ActionFunction | ActionAggregator | ActionString | ActionSelector; -type SchemaNodeDataKind = Action | NodeDataKind; - -type PreparedAction = (params: { object: any; items: any; objectToCompute: any }) => any; -interface SchemaNodeData { - propertyName: string; - action: Actions | null; - preparedAction?: PreparedAction; - kind: SchemaNodeDataKind; - targetPropertyPath: string; -} -interface SchemaNode { - data: SchemaNodeData; - parent: SchemaNode | null; - children: SchemaNode[]; -} - -type Overwrite = { [P in Exclude]: T1[P] } & T2; - -type AddNode = Overwrite< - SchemaNodeData, - { - kind?: Action; - targetPropertyPath?: string; - preparedAction?: (...args: any) => any; - } ->; - -class MophismSchemaTree { - private root: SchemaNode; - constructor() { - this.root = { - data: { targetPropertyPath: '', propertyName: 'MorphismTreeRoot', action: null, kind: NodeDataKind.Root }, - parent: null, - children: [] - }; - } - *traverseBFS() { - const queue = []; - queue.push(this.root); - let node = queue.shift(); - while (node) { - for (let i = 0, length = node.children.length; i < length; i++) { - queue.push(node.children[i]); - } - if (node.data.kind !== NodeDataKind.Root) { - yield node; - } - node = queue.shift(); - } - } - - add(data: AddNode) { - if (!data.action) throw new Error(`Action of ${data.propertyName} can't be ${data.action}.`); - - const kind = this.getActionKind(data.action); - if (!kind) throw new Error(`The action specified for ${data.propertyName} is not supported.`); - const nodeToAdd: SchemaNode = { data: { ...data, kind, targetPropertyPath: '' }, parent: null, children: [] }; - nodeToAdd.data.preparedAction = this.getPreparedAction(nodeToAdd.data); - - nodeToAdd.parent = this.root; - nodeToAdd.data.targetPropertyPath = nodeToAdd.data.propertyName; - this.root.children.push(nodeToAdd); - } - - getActionKind(action: Actions) { - if (isActionString(action)) return Action.ActionString; - if (isFunction(action)) return Action.ActionFunction; - if (isActionSelector(action)) return Action.ActionSelector; - if (isActionAggregator(action)) return Action.ActionAggregator; - } - - getPreparedAction(nodeData: SchemaNodeData): PreparedAction { - const { propertyName: targetProperty, action } = nodeData; - // iterate on every action of the schema - if (isActionString(action)) { - // Action: string path => [ target: 'source' ] - return ({ object }) => ({ [targetProperty]: get(object, action) }); - } else if (isActionFunction(action)) { - // Action: Free Computin - a callback called with the current object and collection [ destination: (object) => {...} ] - return ({ object, items, objectToCompute }) => ({ - [targetProperty]: action.call(undefined, object, items, objectToCompute) - }); - } else if (isActionAggregator(action)) { - // Action: Aggregator - string paths => : [ destination: ['source1', 'source2', 'source3'] ] - return ({ object }) => ({ [targetProperty]: aggregator(action, object) }); - } else if (isActionSelector(action)) { - // Action: a path and a function: [ destination : { path: 'source', fn:(fieldValue, items) }] - return ({ object, items, objectToCompute }) => { - let result; - try { - let value; - if (Array.isArray(action.path)) { - value = aggregator(action.path, object); - } else if (isString(action.path)) { - value = get(object, action.path); - } - result = action.fn.call(undefined, value, object, items, objectToCompute); - } catch (e) { - e.message = `Unable to set target property [${targetProperty}]. - \n An error occured when applying [${action.fn.name}] on property [${ - action.path - }] - \n Internal error: ${e.message}`; - throw e; - } - return { [targetProperty]: result }; - }; - } else { - throw new Error(`The action specified for ${targetProperty} is not supported.`); - } - } -} -function isValidAction(action: any) { - return isString(action) || isFunction(action) || isActionSelector(action) || isActionAggregator(action); -} - -function parseSchema(schema: Schema | StrictSchema | string | number) { - const tree = new MophismSchemaTree(); - seedTreeSchema(tree, schema); - return tree; -} -function seedTreeSchema( - tree: MophismSchemaTree, - partialSchema: Partial | string | number, - actionKey?: string -): void { - if (isValidAction(partialSchema) && actionKey) { - tree.add({ propertyName: actionKey, action: partialSchema as Actions }); - return; - } - Object.keys(partialSchema).forEach(key => { - seedTreeSchema(tree, (partialSchema as any)[key], key); - }); -} /** * Low Level transformer function. * Take a plain object as input and transform its values using a specified schema. @@ -317,19 +13,20 @@ function seedTreeSchema( * @param {Array} items Items to be forwarded to Actions * @param {} objectToCompute Created tranformed object of a given type */ -function transformValuesFromObject( +function transformValuesFromObject( object: Source, - tree: MophismSchemaTree, + tree: MophismSchemaTree, items: Source[], - objectToCompute: TDestination + objectToCompute: Target ) { const transformChunks = []; for (const node of tree.traverseBFS()) { - const { preparedAction } = node.data; - if (preparedAction) transformChunks.push(preparedAction({ object, objectToCompute, items })); + const { preparedAction, kind, targetPropertyPath } = node.data; + if (preparedAction) + transformChunks.push({ targetPropertyPath, preparedAction: preparedAction({ object, objectToCompute, items }) }); } - return transformChunks.reduce((finalObject, keyValue) => { + return transformChunks.reduce((finalObject, chunk) => { const undefinedValueCheck = (destination: any, source: any) => { // Take the Object class value property if the incoming property is undefined if (isUndefined(source)) { @@ -342,7 +39,9 @@ function transformValuesFromObject( return source; } }; - return assignInWith(finalObject, keyValue, undefinedValueCheck); + + const finalValue = undefinedValueCheck(get(finalObject, chunk.targetPropertyPath), chunk.preparedAction); + return set(finalObject, chunk.targetPropertyPath, finalValue); }, objectToCompute); } interface Constructable { diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..ecf74aa --- /dev/null +++ b/src/types.ts @@ -0,0 +1,162 @@ +/** + * A structure-preserving object from a source data towards a target data. + * + * The keys of the schema match the desired destination structure. + * Each value corresponds to an Action applied by Morphism when iterating over the input data + * @example + * ```typescript + * + * const input = { + * foo: { + * baz: 'value1' + * } + * }; + * + * const schema: Schema = { + * bar: 'foo', // ActionString + * qux: ['foo', 'foo.baz'], // ActionAggregator + * quux: (iteratee, source, destination) => { // ActionFunction + * return iteratee.foo; + * }, + * corge: { // ActionSelector + * path: 'foo.baz', + * fn: (propertyValue, source) => { + * return propertyValue; + * } + * } + * }; + * + * morphism(schema, input); + * ``` + */ +export type StrictSchema = { + /** `destinationProperty` is the name of the property of the target object you want to produce */ + [destinationProperty in keyof Target]: + | ActionString + | ActionFunction + | ActionAggregator + | ActionSelector + | StrictSchema +}; +export type Schema = { + /** `destinationProperty` is the name of the property of the target object you want to produce */ + [destinationProperty in keyof Target]?: + | ActionString + | ActionFunction + | ActionAggregator + | ActionSelector + | Schema +}; + +// export enum Action { +// ActionFunction = 'ActionFunction', +// ActionAggregator = 'ActionAggregator', +// ActionString = 'ActionString', +// ActionSelector = 'ActionSelector' +// } + +export type Actions = + | ActionFunction + | ActionAggregator + | ActionString + | ActionSelector; + +/** + * A Function invoked per iteration + * @param {} iteratee The current element to transform + * @param source The source input to transform + * @param target The current element transformed + * @example + * ```typescript + * + * const source = { + * foo: { + * bar: 'bar' + * } + * }; + * let schema = { + * bar: iteratee => { + * // Apply a function over the source propery + * return iteratee.foo.bar; + * } + * }; + * + * morphism(schema, source); + * //=> { bar: 'bar' } + * ``` + * + */ +export interface ActionFunction { + (iteratee: S, source: S[], target: D): R; +} + +/** + * A String path that indicates where to find the property in the source input + * + * @example + * ```typescript + * + * const source = { + * foo: 'baz', + * bar: ['bar', 'foo'], + * baz: { + * qux: 'bazqux' + * } + * }; + * const schema = { + * foo: 'foo', // Simple Projection + * bazqux: 'baz.qux' // Grab a value from a nested property + * }; + * + * morphism(schema, source); + * //=> { foo: 'baz', bazqux: 'bazqux' } + * ``` + * + */ +export type ActionString = keyof T; +/** + * An Array of String that allows to perform a function over source property + * + * @example + * ```typescript + * + * const source = { + * foo: 'foo', + * bar: 'bar' + * }; + * let schema = { + * fooAndBar: ['foo', 'bar'] // Grab these properties into fooAndBar + * }; + * + * morphism(schema, source); + * //=> { fooAndBar: { foo: 'foo', bar: 'bar' } } + * ``` + */ +export type ActionAggregator = string[]; +/** + * An Object that allows to perform a function over a source property's value + * + * @example + * ```typescript + * + * const source = { + * foo: { + * bar: 'bar' + * } + * }; + * let schema = { + * barqux: { + * path: 'foo.bar', + * fn: value => `${value}qux` // Apply a function over the source property's value + * } + * }; + * + * morphism(schema, source); + * //=> { barqux: 'barqux' } + *``` + * + */ +export type ActionSelector = { + path: string | string[]; + fn: (fieldValue: any, object: Source, items: Source, objectToCompute: R) => R; +}; From 4046edf6a1754306b532ee6d3417c69cba6e07c9 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 16 Mar 2019 05:15:41 -0400 Subject: [PATCH 14/30] feat: add MorphismTree created from a schema - a parser transforms a schema to a MorphismTree - the tree is then used to rebuild the final object --- src/MorphismTree.spec.ts | 67 +++++++++++++++ src/MorphismTree.ts | 178 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 src/MorphismTree.spec.ts create mode 100644 src/MorphismTree.ts diff --git a/src/MorphismTree.spec.ts b/src/MorphismTree.spec.ts new file mode 100644 index 0000000..87faaf6 --- /dev/null +++ b/src/MorphismTree.spec.ts @@ -0,0 +1,67 @@ +import { MophismSchemaTree, SchemaNode, NodeKind } from './MorphismTree'; + +describe('Morphism Tree', () => { + it('should add a node under the root', () => { + interface Target { + keyA: string; + } + const tree = new MophismSchemaTree(); + tree.add({ action: 'keyA', propertyName: 'keyA' }); + + const root: SchemaNode = { + data: { targetPropertyPath: '', propertyName: 'MorphismTreeRoot', action: null, kind: NodeKind.Root }, + parent: null, + children: [] + }; + const result: SchemaNode = { + data: { targetPropertyPath: 'keyA', propertyName: 'keyA', action: 'keyA', kind: NodeKind.ActionString }, + parent: root, + children: [] + }; + + expect(tree.root.data).toEqual(root.data); + expect(tree.root.parent).toEqual(root.parent); + + expect(tree.root.children[0].data.action).toEqual(result.data.action); + expect(tree.root.children[0].data.targetPropertyPath).toEqual(result.data.targetPropertyPath); + expect(tree.root.children[0].data.propertyName).toEqual(result.data.propertyName); + expect(tree.root.children[0].data.kind).toEqual(result.data.kind); + + expect(tree.root.children[0].parent!.data.propertyName).toEqual(root.data.propertyName); + }); + + it('should add a node under another node', () => { + interface Target { + keyA: string; + } + const tree = new MophismSchemaTree(); + const parentTargetPropertyPath = 'keyA'; + tree.add({ action: null, propertyName: 'keyA', targetPropertyPath: parentTargetPropertyPath }); + tree.add({ action: 'keyA', propertyName: 'keyA1' }, parentTargetPropertyPath); + + const nodeKeyA: SchemaNode = { + data: { targetPropertyPath: 'keyA', propertyName: 'keyA', action: null, kind: NodeKind.Property }, + parent: null, + children: [] + }; + + const nodeKeyA1: SchemaNode = { + data: { targetPropertyPath: 'keyA.keyA1', propertyName: 'keyA1', action: 'keyA', kind: NodeKind.ActionString }, + parent: null, + children: [] + }; + + // KeyA + expect(tree.root.children[0].data.action).toEqual(nodeKeyA.data.action); + expect(tree.root.children[0].data.targetPropertyPath).toEqual(nodeKeyA.data.targetPropertyPath); + expect(tree.root.children[0].data.propertyName).toEqual(nodeKeyA.data.propertyName); + expect(tree.root.children[0].data.kind).toEqual(nodeKeyA.data.kind); + + // KeyA1 + const keyA1 = tree.root.children[0].children[0]; + expect(keyA1.data.action).toEqual(nodeKeyA1.data.action); + expect(keyA1.data.targetPropertyPath).toEqual(nodeKeyA1.data.targetPropertyPath); + expect(keyA1.data.propertyName).toEqual(nodeKeyA1.data.propertyName); + expect(keyA1.data.kind).toEqual(nodeKeyA1.data.kind); + }); +}); diff --git a/src/MorphismTree.ts b/src/MorphismTree.ts new file mode 100644 index 0000000..87e6208 --- /dev/null +++ b/src/MorphismTree.ts @@ -0,0 +1,178 @@ +import { Actions, Schema, StrictSchema } from './types'; +import { isFunction, isString } from 'util'; +import { + aggregator, + get, + isValidAction, + isActionString, + isActionSelector, + isActionAggregator, + isActionFunction +} from './helpers'; + +export enum NodeKind { + Root = 'Root', + Property = 'Property', + ActionFunction = 'ActionFunction', + ActionAggregator = 'ActionAggregator', + ActionString = 'ActionString', + ActionSelector = 'ActionSelector' +} + +type PreparedAction = (params: { object: any; items: any; objectToCompute: any }) => any; +interface SchemaNodeData { + propertyName: string; + action: Actions | null; + preparedAction?: PreparedAction | null; + kind: NodeKind; + targetPropertyPath: string; +} +export interface SchemaNode { + data: SchemaNodeData; + parent: SchemaNode | null; + children: SchemaNode[]; +} + +type Overwrite = { [P in Exclude]: T1[P] } & T2; + +type AddNode = Overwrite< + SchemaNodeData, + { + kind?: NodeKind; + targetPropertyPath?: string; + preparedAction?: (...args: any) => any; + } +>; + +export function parseSchema(schema: Schema | StrictSchema | string | number) { + const tree = new MophismSchemaTree(); + seedTreeSchema(tree, schema); + return tree; +} + +function seedTreeSchema( + tree: MophismSchemaTree, + partialSchema: Partial | string | number, + actionKey?: string, + parentkey?: string +): void { + if (isValidAction(partialSchema) && actionKey) { + tree.add({ propertyName: actionKey, action: partialSchema as Actions }, parentkey); + return; + } else if (actionKey && !Array.isArray(partialSchema)) { + // check if actionKey exists to verify if not root node + tree.add({ propertyName: actionKey, action: null }, parentkey); + } + + Object.keys(partialSchema).forEach(key => { + seedTreeSchema(tree, (partialSchema as any)[key], key, actionKey); + }); +} + +export class MophismSchemaTree { + root: SchemaNode; + constructor() { + this.root = { + data: { targetPropertyPath: '', propertyName: 'MorphismTreeRoot', action: null, kind: NodeKind.Root }, + parent: null, + children: [] + }; + } + *traverseBFS() { + const queue: SchemaNode[] = []; + queue.push(this.root); + while (queue.length > 0) { + let node = queue.shift(); + + if (node) { + for (let i = 0, length = node.children.length; i < length; i++) { + queue.push(node.children[i]); + } + if (node.data.kind !== NodeKind.Root) { + yield node; + } + } else { + return; + } + } + } + + add(data: AddNode, targetPropertyPath?: string) { + const kind = this.getActionKind(data.action); + if (!kind) throw new Error(`The action specified for ${data.propertyName} is not supported.`); + + const nodeToAdd: SchemaNode = { + data: { ...data, kind, targetPropertyPath: '' }, + parent: null, + children: [] + }; + nodeToAdd.data.preparedAction = this.getPreparedAction(nodeToAdd.data); + // console.log('add', nodeToAdd.data.propertyName, nodeToAdd.data.kind, nodeToAdd.data.action, targetPropertyPath); + + if (!targetPropertyPath) { + nodeToAdd.parent = this.root; + nodeToAdd.data.targetPropertyPath = nodeToAdd.data.propertyName; + this.root.children.push(nodeToAdd); + } else { + for (const node of this.traverseBFS()) { + if (node.data.targetPropertyPath === targetPropertyPath) { + // console.log('found', node); + + nodeToAdd.parent = node; + nodeToAdd.data.targetPropertyPath = `${node.data.targetPropertyPath}.${nodeToAdd.data.propertyName}`; + + node.children.push(nodeToAdd); + } + } + } + } + + getActionKind(action: Actions | null) { + if (isActionString(action)) return NodeKind.ActionString; + if (isFunction(action)) return NodeKind.ActionFunction; + if (isActionSelector(action)) return NodeKind.ActionSelector; + if (isActionAggregator(action)) return NodeKind.ActionAggregator; + if (action === null) return NodeKind.Property; + } + + getPreparedAction(nodeData: SchemaNodeData): PreparedAction | null { + const { propertyName: targetProperty, action, kind } = nodeData; + // iterate on every action of the schema + if (isActionString(action)) { + // Action: string path => [ target: 'source' ] + return ({ object }) => get(object, action); + } else if (isActionFunction(action)) { + // Action: Free Computin - a callback called with the current object and collection [ destination: (object) => {...} ] + return ({ object, items, objectToCompute }) => action.call(undefined, object, items, objectToCompute); + } else if (isActionAggregator(action)) { + // Action: Aggregator - string paths => : [ destination: ['source1', 'source2', 'source3'] ] + return ({ object }) => aggregator(action, object); + } else if (isActionSelector(action)) { + // Action: a path and a function: [ destination : { path: 'source', fn:(fieldValue, items) }] + return ({ object, items, objectToCompute }) => { + let result; + try { + let value; + if (Array.isArray(action.path)) { + value = aggregator(action.path, object); + } else if (isString(action.path)) { + value = get(object, action.path); + } + result = action.fn.call(undefined, value, object, items, objectToCompute); + } catch (e) { + e.message = `Unable to set target property [${targetProperty}]. + \n An error occured when applying [${action.fn.name}] on property [${ + action.path + }] + \n Internal error: ${e.message}`; + throw e; + } + return result; + }; + } else if (kind === NodeKind.Property) { + return null; + } else { + throw new Error(`The action specified for ${targetProperty} is not supported.`); + } + } +} From aa99c14c9d6ba1e9e4faf0370ac63118a88517b6 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 16 Mar 2019 05:20:20 -0400 Subject: [PATCH 15/30] tests: update UTs --- src/morphism.spec.ts | 32 ++++++++++++++++++++++++++++++-- src/morphism.ts | 1 + src/typescript.spec.ts | 3 +-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/morphism.spec.ts b/src/morphism.spec.ts index 93e76f6..aa49f13 100644 --- a/src/morphism.spec.ts +++ b/src/morphism.spec.ts @@ -1,4 +1,4 @@ -import Morphism, { StrictSchema } from './morphism'; +import Morphism, { morphism, StrictSchema } from './morphism'; class User { firstName?: string; @@ -338,7 +338,17 @@ describe('Morphism', () => { describe('Flattening and Projection', function() { it('should flatten data from specified path', function() { - let schema = { + interface Source { + firstName: string; + lastName: string; + address: { city: string }; + } + interface Target { + firstName: string; + lastName: string; + city: string; + } + let schema: StrictSchema = { firstName: 'firstName', lastName: 'lastName', city: 'address.city' @@ -631,6 +641,24 @@ describe('Morphism', () => { expect(res).toEqual(new Target('value')); }); }); + + it('should accept deep nested actions', () => { + interface Source { + keyA: string; + } + const sample: Source = { + keyA: 'value' + }; + + interface Target { + keyA: { keyA1: string }; + } + + const schema: StrictSchema = { keyA: { keyA1: source => source.keyA } }; + + const target = morphism(schema, sample); + expect(target).toEqual({ keyA: { keyA1: 'value' } }); + }); }); }); }); diff --git a/src/morphism.ts b/src/morphism.ts index 0c3d1ee..b463d7a 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -405,4 +405,5 @@ morphismMixin.mappers = morphismRegistry.mappers; const Morphism: typeof morphism & IMorphismRegistry = morphismMixin; +export { Schema, StrictSchema }; export default Morphism; diff --git a/src/typescript.spec.ts b/src/typescript.spec.ts index 5eb578d..d52b95b 100644 --- a/src/typescript.spec.ts +++ b/src/typescript.spec.ts @@ -1,4 +1,4 @@ -import { StrictSchema, Schema, morphism } from './morphism'; +import { morphism, StrictSchema, Schema } from './morphism'; describe('Morphism', () => { describe('Typescript', () => { @@ -55,7 +55,6 @@ describe('Morphism', () => { if (itemA) { itemA.d1; } - morphism(schema, { s1: 'teest' }).d1.toString(); const b = morphism(schema, [{ s1: 'teest' }]); const itemB = b.shift(); From a7998e073dc5a002a1ea7ad9fd9ec831cfa132f4 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 16 Mar 2019 05:21:25 -0400 Subject: [PATCH 16/30] chore: move action function helpers --- src/helpers.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index 7ceeae0..ceb06c3 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,3 +1,22 @@ +import { ActionSelector, ActionAggregator, ActionFunction } from './types'; + +export function isActionSelector(value: any): value is ActionSelector { + return isObject(value) && value.hasOwnProperty('fn') && value.hasOwnProperty('path'); +} +export function isActionString(value: any): value is string { + return isString(value); +} +export function isActionAggregator(value: any): value is ActionAggregator { + return Array.isArray(value); +} +export function isActionFunction(value: any): value is ActionFunction { + return isFunction(value); +} + +export function isValidAction(action: any) { + return isString(action) || isFunction(action) || isActionSelector(action) || isActionAggregator(action); +} + export const aggregator = (paths: string[], object: any) => { return paths.reduce((delta, path) => { return set(delta, path, get(object, path)); @@ -31,7 +50,8 @@ export function isString(value: any): value is string { export function isFunction(value: any): value is (...args: any[]) => any { return typeof value === 'function'; } -export function set(object: object, path: string, value: any) { + +export function set(object: any, path: string, value: any) { path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties path = path.replace(/^\./, ''); // strip a leading dot const paths = path.split('.'); @@ -43,7 +63,7 @@ export function set(object: object, path: string, value: any) { { [lastProperty]: value } ); - return { ...object, ...finalValue }; + return Object.assign(object, finalValue); } export function get(object: any, path: string) { path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties From 3be31d5b80b2be5756ce1892eb48ba0929f696ab Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 16 Mar 2019 05:34:37 -0400 Subject: [PATCH 17/30] chore: cleanup vars --- src/morphism.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/morphism.ts b/src/morphism.ts index b463d7a..7d5f056 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -1,7 +1,7 @@ /** * @module morphism */ -import { zipObject, isUndefined, assignInWith, set, get } from './helpers'; +import { zipObject, isUndefined, set, get } from './helpers'; import { Schema, StrictSchema } from './types'; import { MophismSchemaTree, parseSchema } from './MorphismTree'; @@ -21,7 +21,7 @@ function transformValuesFromObject( ) { const transformChunks = []; for (const node of tree.traverseBFS()) { - const { preparedAction, kind, targetPropertyPath } = node.data; + const { preparedAction, targetPropertyPath } = node.data; if (preparedAction) transformChunks.push({ targetPropertyPath, preparedAction: preparedAction({ object, objectToCompute, items }) }); } @@ -170,7 +170,7 @@ export function morphism> } } -export interface IMorphismRegistry { +interface IMorphismRegistry { /** * Register a mapping schema for a Class. * @@ -340,7 +340,7 @@ export class MorphismRegistry implements IMorphismRegistry { } function decorator(mapper: Mapper) { - return (target: any, name: string, descriptor: PropertyDescriptor) => { + return (_target: any, _name: string, descriptor: PropertyDescriptor) => { const fn = descriptor.value; if (typeof fn === 'function') { descriptor.value = function(...args: any[]) { From 159161fdd4e659bd4c44ca12711b6473e4ab778b Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 16 Mar 2019 06:03:02 -0400 Subject: [PATCH 18/30] chore: cleanup --- src/MorphismDecorator.ts | 19 ++++ src/MorphismRegistry.ts | 171 +++++++++++++++++++++++++++++++++ src/MorphismTree.ts | 8 +- src/helpers.ts | 20 ++-- src/morphism.ts | 203 +-------------------------------------- src/types.ts | 11 +-- 6 files changed, 211 insertions(+), 221 deletions(-) create mode 100644 src/MorphismDecorator.ts create mode 100644 src/MorphismRegistry.ts diff --git a/src/MorphismDecorator.ts b/src/MorphismDecorator.ts new file mode 100644 index 0000000..cd9b1af --- /dev/null +++ b/src/MorphismDecorator.ts @@ -0,0 +1,19 @@ +import { Mapper } from './morphism'; +import { isPromise } from './helpers'; + +export function decorator(mapper: Mapper) { + return (_target: any, _name: string, descriptor: PropertyDescriptor) => { + const fn = descriptor.value; + if (typeof fn === 'function') { + descriptor.value = function(...args: any[]) { + const output = fn.apply(this, args); + if (isPromise(output)) { + return Promise.resolve(output).then(res => mapper(res)); + } + return mapper(output); + }; + } + + return descriptor; + }; +} diff --git a/src/MorphismRegistry.ts b/src/MorphismRegistry.ts new file mode 100644 index 0000000..7f6143b --- /dev/null +++ b/src/MorphismRegistry.ts @@ -0,0 +1,171 @@ +import { Constructable, Schema } from './types'; +import { Mapper, morphism } from './morphism'; + +export interface IMorphismRegistry { + /** + * Register a mapping schema for a Class. + * + * @param type Class Type to be registered + * @param schema Structure-preserving object from a source data towards a target data. + * + */ + register(type: Constructable): Mapper, Target>; + register(type: Constructable, schema?: TSchema): Mapper; + + /** + * Transform any input in the specified Class + * + * @param {Type} type Class Type of the ouput Data + * @param {Object} data Input data to transform + * + */ + map(type: Target): Mapper; + map(type: Constructable, data: Source[]): Target[]; + map(type: Constructable, data: Source): Target; + /** + * Get a specific mapping function for the provided Class + * + * @param {Type} type Class Type of the ouput Data + * + */ + getMapper(type: Constructable): Mapper; + /** + * Set a schema for a specific Class Type + * + * @param {Type} type Class Type of the ouput Data + * @param {Schema} schema Class Type of the ouput Data + * + */ + setMapper>(type: Constructable, schema: TSchema): Mapper; + /** + * Delete a registered schema associated to a Class + * + * @param type ES6 Class Type of the ouput Data + * + */ + deleteMapper(type: Constructable): any; + /** + * Check if a schema has already been registered for this type + * + * @param {*} type + */ + exists(type: Target): boolean; + /** + * Get the list of the mapping functions registered + * + * @param {Type} type Class Type of the ouput Data + * + */ + mappers: Map; +} + +export class MorphismRegistry implements IMorphismRegistry { + private _registry: any = null; + /** + *Creates an instance of MorphismRegistry. + * @param {Map} cache Cache implementation to store the mapping functions. + */ + constructor(cache?: Map | WeakMap) { + if (!cache) { + this._registry = { cache: new Map() }; + } else { + this._registry = cache; + } + } + + /** + * Register a mapping schema for a Class. + * + * @param type Class Type to be registered + * @param schema Structure-preserving object from a source data towards a target data. + * + */ + register(type: Constructable, schema?: TSchema) { + if (!type && !schema) { + throw new Error('type paramater is required when you register a mapping'); + } else if (this.exists(type)) { + throw new Error(`A mapper for ${type.name} has already been registered`); + } + let mapper; + if (schema) { + mapper = morphism(schema, null, type); + } else { + mapper = morphism({}, null, type); + } + this._registry.cache.set(type, mapper); + return mapper; + } + /** + * Transform any input in the specified Class + * + * @param {Type} type Class Type of the ouput Data + * @param {Object} data Input data to transform + * + */ + map(type: any, data?: any) { + if (!this.exists(type)) { + const mapper = this.register(type); + if (data === undefined) { + return mapper; + } + } + return this.getMapper(type)(data); + } + /** + * Get a specific mapping function for the provided Class + * + * @param {Type} type Class Type of the ouput Data + * + */ + getMapper(type: Constructable) { + return this._registry.cache.get(type); + } + /** + * Set a schema for a specific Class Type + * + * @param {Type} type Class Type of the ouput Data + * @param {Schema} schema Class Type of the ouput Data + * + */ + setMapper(type: Constructable, schema: Schema) { + if (!schema) { + throw new Error(`The schema must be an Object. Found ${schema}`); + } else if (!this.exists(type)) { + throw new Error( + `The type ${type.name} is not registered. Register it using \`Mophism.register(${type.name}, schema)\`` + ); + } else { + let fn = morphism(schema, null, type); + this._registry.cache.set(type, fn); + return fn; + } + } + + /** + * Delete a registered schema associated to a Class + * + * @param type ES6 Class Type of the ouput Data + * + */ + deleteMapper(type: any) { + return this._registry.cache.delete(type); + } + + /** + * Check if a schema has already been registered for this type + * + * @param {*} type + */ + exists(type: any) { + return this._registry.cache.has(type); + } + /** + * Get the list of the mapping functions registered + * + * @param {Type} type Class Type of the ouput Data + * + */ + get mappers() { + return this._registry.cache as Map; + } +} diff --git a/src/MorphismTree.ts b/src/MorphismTree.ts index 87e6208..aed8989 100644 --- a/src/MorphismTree.ts +++ b/src/MorphismTree.ts @@ -1,5 +1,4 @@ import { Actions, Schema, StrictSchema } from './types'; -import { isFunction, isString } from 'util'; import { aggregator, get, @@ -7,7 +6,9 @@ import { isActionString, isActionSelector, isActionAggregator, - isActionFunction + isActionFunction, + isFunction, + isString } from './helpers'; export enum NodeKind { @@ -107,7 +108,6 @@ export class MophismSchemaTree { children: [] }; nodeToAdd.data.preparedAction = this.getPreparedAction(nodeToAdd.data); - // console.log('add', nodeToAdd.data.propertyName, nodeToAdd.data.kind, nodeToAdd.data.action, targetPropertyPath); if (!targetPropertyPath) { nodeToAdd.parent = this.root; @@ -116,8 +116,6 @@ export class MophismSchemaTree { } else { for (const node of this.traverseBFS()) { if (node.data.targetPropertyPath === targetPropertyPath) { - // console.log('found', node); - nodeToAdd.parent = node; nodeToAdd.data.targetPropertyPath = `${node.data.targetPropertyPath}.${nodeToAdd.data.propertyName}`; diff --git a/src/helpers.ts b/src/helpers.ts index ceb06c3..7de5bbf 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -23,17 +23,6 @@ export const aggregator = (paths: string[], object: any) => { }, {}); }; -export function assignInWith(target: any, source: any, customizer?: (targetValue: any, sourceValue: any) => any) { - Object.entries(source).forEach(([field, value]) => { - if (customizer) { - target[field] = customizer(target[field], value); - } else { - target[field] = value; - } - }); - return target; -} - export function isUndefined(value: any) { return value === undefined; } @@ -51,6 +40,15 @@ export function isFunction(value: any): value is (...args: any[]) => any { return typeof value === 'function'; } +export function isPromise(object: any) { + if (Promise && Promise.resolve) { + // tslint:disable-next-line:triple-equals + return Promise.resolve(object) == object; + } else { + throw 'Promise not supported in your environment'; + } +} + export function set(object: any, path: string, value: any) { path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties path = path.replace(/^\./, ''); // strip a leading dot diff --git a/src/morphism.ts b/src/morphism.ts index 7d5f056..1b89be1 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -2,8 +2,10 @@ * @module morphism */ import { zipObject, isUndefined, set, get } from './helpers'; -import { Schema, StrictSchema } from './types'; +import { Schema, StrictSchema, Constructable } from './types'; import { MophismSchemaTree, parseSchema } from './MorphismTree'; +import { MorphismRegistry, IMorphismRegistry } from './MorphismRegistry'; +import { decorator } from './MorphismDecorator'; /** * Low Level transformer function. @@ -44,9 +46,6 @@ function transformValuesFromObject( return set(finalObject, chunk.targetPropertyPath, finalValue); }, objectToCompute); } -interface Constructable { - new (...args: any[]): T; -} function transformItems>(schema: TSchema, type?: Constructable) { const tree = parseSchema(schema); @@ -170,200 +169,7 @@ export function morphism> } } -interface IMorphismRegistry { - /** - * Register a mapping schema for a Class. - * - * @param type Class Type to be registered - * @param schema Structure-preserving object from a source data towards a target data. - * - */ - register(type: Constructable): Mapper, Target>; - register(type: Constructable, schema?: TSchema): Mapper; - - /** - * Transform any input in the specified Class - * - * @param {Type} type Class Type of the ouput Data - * @param {Object} data Input data to transform - * - */ - map(type: Target): Mapper; - map(type: Constructable, data: Source[]): Target[]; - map(type: Constructable, data: Source): Target; - /** - * Get a specific mapping function for the provided Class - * - * @param {Type} type Class Type of the ouput Data - * - */ - getMapper(type: Constructable): Mapper; - /** - * Set a schema for a specific Class Type - * - * @param {Type} type Class Type of the ouput Data - * @param {Schema} schema Class Type of the ouput Data - * - */ - setMapper>(type: Constructable, schema: TSchema): Mapper; - /** - * Delete a registered schema associated to a Class - * - * @param type ES6 Class Type of the ouput Data - * - */ - deleteMapper(type: Constructable): any; - /** - * Check if a schema has already been registered for this type - * - * @param {*} type - */ - exists(type: Target): boolean; - /** - * Get the list of the mapping functions registered - * - * @param {Type} type Class Type of the ouput Data - * - */ - mappers: Map; -} - -export class MorphismRegistry implements IMorphismRegistry { - private _registry: any = null; - /** - *Creates an instance of MorphismRegistry. - * @param {Map} cache Cache implementation to store the mapping functions. - */ - constructor(cache?: Map | WeakMap) { - if (!cache) { - this._registry = { cache: new Map() }; - } else { - this._registry = cache; - } - } - - /** - * Register a mapping schema for a Class. - * - * @param type Class Type to be registered - * @param schema Structure-preserving object from a source data towards a target data. - * - */ - register(type: Constructable, schema?: TSchema) { - if (!type && !schema) { - throw new Error('type paramater is required when you register a mapping'); - } else if (this.exists(type)) { - throw new Error(`A mapper for ${type.name} has already been registered`); - } - let mapper; - if (schema) { - mapper = morphism(schema, null, type); - } else { - mapper = morphism({}, null, type); - } - this._registry.cache.set(type, mapper); - return mapper; - } - /** - * Transform any input in the specified Class - * - * @param {Type} type Class Type of the ouput Data - * @param {Object} data Input data to transform - * - */ - map(type: any, data?: any) { - if (!this.exists(type)) { - const mapper = this.register(type); - if (data === undefined) { - return mapper; - } - } - return this.getMapper(type)(data); - } - /** - * Get a specific mapping function for the provided Class - * - * @param {Type} type Class Type of the ouput Data - * - */ - getMapper(type: Constructable) { - return this._registry.cache.get(type); - } - /** - * Set a schema for a specific Class Type - * - * @param {Type} type Class Type of the ouput Data - * @param {Schema} schema Class Type of the ouput Data - * - */ - setMapper(type: Constructable, schema: Schema) { - if (!schema) { - throw new Error(`The schema must be an Object. Found ${schema}`); - } else if (!this.exists(type)) { - throw new Error( - `The type ${type.name} is not registered. Register it using \`Mophism.register(${type.name}, schema)\`` - ); - } else { - let fn = morphism(schema, null, type); - this._registry.cache.set(type, fn); - return fn; - } - } - - /** - * Delete a registered schema associated to a Class - * - * @param type ES6 Class Type of the ouput Data - * - */ - deleteMapper(type: any) { - return this._registry.cache.delete(type); - } - - /** - * Check if a schema has already been registered for this type - * - * @param {*} type - */ - exists(type: any) { - return this._registry.cache.has(type); - } - /** - * Get the list of the mapping functions registered - * - * @param {Type} type Class Type of the ouput Data - * - */ - get mappers() { - return this._registry.cache as Map; - } -} - -function decorator(mapper: Mapper) { - return (_target: any, _name: string, descriptor: PropertyDescriptor) => { - const fn = descriptor.value; - if (typeof fn === 'function') { - descriptor.value = function(...args: any[]) { - const output = fn.apply(this, args); - if (isPromise(output)) { - return Promise.resolve(output).then(res => mapper(res)); - } - return mapper(output); - }; - } - - return descriptor; - }; -} -function isPromise(object: any) { - if (Promise && Promise.resolve) { - // tslint:disable-next-line:triple-equals - return Promise.resolve(object) == object; - } else { - throw 'Promise not supported in your environment'; - } -} - +// Decorators /** * Function Decorator transforming the return value of the targeted Function using the provided Schema and/or Type * @@ -394,6 +200,7 @@ export function toClassObject(schema: Schema, type: Constructabl return decorator(mapper); } +// Registry const morphismRegistry = new MorphismRegistry(); const morphismMixin: typeof morphism & any = morphism; morphismMixin.register = (t: any, s: any) => morphismRegistry.register(t, s); diff --git a/src/types.ts b/src/types.ts index ecf74aa..8e95b4b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -48,13 +48,6 @@ export type Schema = { | Schema }; -// export enum Action { -// ActionFunction = 'ActionFunction', -// ActionAggregator = 'ActionAggregator', -// ActionString = 'ActionString', -// ActionSelector = 'ActionSelector' -// } - export type Actions = | ActionFunction | ActionAggregator @@ -160,3 +153,7 @@ export type ActionSelector = { path: string | string[]; fn: (fieldValue: any, object: Source, items: Source, objectToCompute: R) => R; }; + +export interface Constructable { + new (...args: any[]): T; +} From d2d1af54b2438192cbd2308d2850e6b9dd81a475 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sat, 16 Mar 2019 06:04:36 -0400 Subject: [PATCH 19/30] chore: update devDeps --- package-lock.json | 1761 +++++++++++++++++++++++++++------------------ package.json | 6 +- 2 files changed, 1074 insertions(+), 693 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0971c0..583af9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1105,6 +1105,374 @@ } } }, + "@cnakazawa/watch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", + "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "@jest/console": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.3.0.tgz", + "integrity": "sha512-NaCty/OOei6rSDcbPdMiCbYCI0KGFGPgGO6B09lwWt5QTxnkuhKYET9El5u5z1GAcSxkQmSMtM63e24YabCWqA==", + "dev": true, + "requires": { + "@jest/source-map": "^24.3.0", + "@types/node": "*", + "chalk": "^2.0.1", + "slash": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "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": { + "has-flag": "^3.0.0" + } + } + } + }, + "@jest/core": { + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.5.0.tgz", + "integrity": "sha512-RDZArRzAs51YS7dXG1pbXbWGxK53rvUu8mCDYsgqqqQ6uSOaTjcVyBl2Jce0exT2rSLk38ca7az7t2f3b0/oYQ==", + "dev": true, + "requires": { + "@jest/console": "^24.3.0", + "@jest/reporters": "^24.5.0", + "@jest/test-result": "^24.5.0", + "@jest/transform": "^24.5.0", + "@jest/types": "^24.5.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-changed-files": "^24.5.0", + "jest-config": "^24.5.0", + "jest-haste-map": "^24.5.0", + "jest-message-util": "^24.5.0", + "jest-regex-util": "^24.3.0", + "jest-resolve-dependencies": "^24.5.0", + "jest-runner": "^24.5.0", + "jest-runtime": "^24.5.0", + "jest-snapshot": "^24.5.0", + "jest-util": "^24.5.0", + "jest-validate": "^24.5.0", + "jest-watcher": "^24.5.0", + "micromatch": "^3.1.10", + "p-each-series": "^1.0.0", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "rimraf": "^2.5.4", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "strip-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.1.0.tgz", + "integrity": "sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "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": { + "has-flag": "^3.0.0" + } + } + } + }, + "@jest/environment": { + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.5.0.tgz", + "integrity": "sha512-tzUHR9SHjMXwM8QmfHb/EJNbF0fjbH4ieefJBvtwO8YErLTrecc1ROj0uo2VnIT6SlpEGZnvdCK6VgKYBo8LsA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^24.5.0", + "@jest/transform": "^24.5.0", + "@jest/types": "^24.5.0", + "@types/node": "*", + "jest-mock": "^24.5.0" + } + }, + "@jest/fake-timers": { + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.5.0.tgz", + "integrity": "sha512-i59KVt3QBz9d+4Qr4QxsKgsIg+NjfuCjSOWj3RQhjF5JNy+eVJDhANQ4WzulzNCHd72srMAykwtRn5NYDGVraw==", + "dev": true, + "requires": { + "@jest/types": "^24.5.0", + "@types/node": "*", + "jest-message-util": "^24.5.0", + "jest-mock": "^24.5.0" + } + }, + "@jest/reporters": { + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.5.0.tgz", + "integrity": "sha512-vfpceiaKtGgnuC3ss5czWOihKOUSyjJA4M4udm6nH8xgqsuQYcyDCi4nMMcBKsHXWgz9/V5G7iisnZGfOh1w6Q==", + "dev": true, + "requires": { + "@jest/environment": "^24.5.0", + "@jest/test-result": "^24.5.0", + "@jest/transform": "^24.5.0", + "@jest/types": "^24.5.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "istanbul-api": "^2.1.1", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-source-maps": "^3.0.1", + "jest-haste-map": "^24.5.0", + "jest-resolve": "^24.5.0", + "jest-runtime": "^24.5.0", + "jest-util": "^24.5.0", + "jest-worker": "^24.4.0", + "node-notifier": "^5.2.1", + "slash": "^2.0.0", + "source-map": "^0.6.0", + "string-length": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "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": { + "has-flag": "^3.0.0" + } + } + } + }, + "@jest/source-map": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.3.0.tgz", + "integrity": "sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/test-result": { + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.5.0.tgz", + "integrity": "sha512-u66j2vBfa8Bli1+o3rCaVnVYa9O8CAFZeqiqLVhnarXtreSXG33YQ6vNYBogT7+nYiFNOohTU21BKiHlgmxD5A==", + "dev": true, + "requires": { + "@jest/console": "^24.3.0", + "@jest/types": "^24.5.0", + "@types/istanbul-lib-coverage": "^1.1.0" + } + }, + "@jest/transform": { + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.5.0.tgz", + "integrity": "sha512-XSsDz1gdR/QMmB8UCKlweAReQsZrD/DK7FuDlNo/pE8EcKMrfi2kqLRk8h8Gy/PDzgqJj64jNEzOce9pR8oj1w==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.5.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.5.0", + "jest-regex-util": "^24.3.0", + "jest-util": "^24.5.0", + "micromatch": "^3.1.10", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "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": { + "has-flag": "^3.0.0" + } + } + } + }, + "@jest/types": { + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.5.0.tgz", + "integrity": "sha512-kN7RFzNMf2R8UDadPOl6ReyI+MT8xfqRuAnuVL+i4gwjv/zubdDK+EDeLHYwq1j0CSSR2W/MmgaRlMZJzXdmVA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^1.1.0", + "@types/yargs": "^12.0.9" + } + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -1291,12 +1659,72 @@ } } }, - "@types/anymatch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", - "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", - "dev": true - }, + "@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.0.tgz", + "integrity": "sha512-wJTeJRt7BToFx3USrCDs2BhEi4ijBInTQjOIukj6a/5tEkwpFMVZ+1ppgmE+Q/FQyc5P/VWUbx7I9NELrKruHA==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz", + "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.6.tgz", + "integrity": "sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } + } + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -1335,10 +1763,16 @@ "integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==", "dev": true }, + "@types/istanbul-lib-coverage": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.0.tgz", + "integrity": "sha512-ohkhb9LehJy+PA40rDtGAji61NCgdtKLAlFoYp4cnuuQEswwdK3vz9SOIkkyc3wrk8dzjphQApNs56yyXLStaQ==", + "dev": true + }, "@types/jest": { - "version": "24.0.9", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.9.tgz", - "integrity": "sha512-k3OOeevcBYLR5pdsOv5g3OP94h3mrJmLPHFEPWgbbVy2tGv0TZ/TlygiC848ogXhK8NL0I5up7YYtwpCp8xCJA==", + "version": "24.0.11", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.11.tgz", + "integrity": "sha512-2kLuPC5FDnWIDvaJBzsGTBQaBbnDweznicvK7UGYzlIJP4RJR2a4A/ByLUXEyEgag6jz8eHdlWExGDtH3EYUXQ==", "dev": true, "requires": { "@types/jest-diff": "*" @@ -1384,6 +1818,12 @@ "@types/node": "*" } }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, "@types/tapable": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz", @@ -1428,6 +1868,12 @@ } } }, + "@types/yargs": { + "version": "12.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.9.tgz", + "integrity": "sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -1924,10 +2370,13 @@ "dev": true }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } }, "asn1.js": { "version": "4.10.1", @@ -2023,6 +2472,12 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -2035,13 +2490,16 @@ } }, "babel-jest": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.1.0.tgz", - "integrity": "sha512-MLcagnVrO9ybQGLEfZUqnOzv36iQzU7Bj4elm39vCukumLVSfoX+tRy3/jW7lUKc7XdpRmB/jech6L/UCsSZjw==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.5.0.tgz", + "integrity": "sha512-0fKCXyRwxFTJL0UXDJiT2xYxO9Lu2vBd9n+cC+eDjESzcVG3s2DRGAxbzJX21fceB1WYoBjAh8pQ83dKcl003g==", "dev": true, "requires": { + "@jest/transform": "^24.5.0", + "@jest/types": "^24.5.0", + "@types/babel__core": "^7.1.0", "babel-plugin-istanbul": "^5.1.0", - "babel-preset-jest": "^24.1.0", + "babel-preset-jest": "^24.3.0", "chalk": "^2.4.2", "slash": "^2.0.0" }, @@ -2115,27 +2573,6 @@ "locate-path": "^3.0.0" } }, - "istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", - "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", - "dev": true, - "requires": { - "@babel/generator": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "istanbul-lib-coverage": "^2.0.3", - "semver": "^5.5.0" - } - }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -2147,9 +2584,9 @@ } }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -2169,29 +2606,26 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", "dev": true - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true } } }, "babel-plugin-jest-hoist": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.1.0.tgz", - "integrity": "sha512-gljYrZz8w1b6fJzKcsfKsipSru2DU2DmQ39aB6nV3xQ0DDv3zpIzKGortA5gknrhNnPN8DweaEgrnZdmbGmhnw==", - "dev": true + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.3.0.tgz", + "integrity": "sha512-nWh4N1mVH55Tzhx2isvUN5ebM5CDUvIpXPZYMRazQughie/EqGnbR+czzoQlhUmJG9pPJmYDRhvocotb2THl1w==", + "dev": true, + "requires": { + "@types/babel__traverse": "^7.0.6" + } }, "babel-preset-jest": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.1.0.tgz", - "integrity": "sha512-FfNLDxFWsNX9lUmtwY7NheGlANnagvxq8LZdl5PKnVG3umP+S/g0XbVBfwtA4Ai3Ri/IMkWabBz3Tyk9wdspcw==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.3.0.tgz", + "integrity": "sha512-VGTV2QYBa/Kn3WCOKdfS31j9qomaXSgJqi65B6o05/1GsJyj9LVhSljM9ro4S+IBGj/ENhNBuH9bpqzztKAQSw==", "dev": true, "requires": { "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "babel-plugin-jest-hoist": "^24.1.0" + "babel-plugin-jest-hoist": "^24.3.0" } }, "balanced-match": { @@ -2266,7 +2700,6 @@ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, - "optional": true, "requires": { "tweetnacl": "^0.14.3" } @@ -2694,12 +3127,12 @@ "dev": true }, "capture-exit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", - "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", "dev": true, "requires": { - "rsvp": "^3.3.3" + "rsvp": "^4.8.4" } }, "capture-stack-trace": { @@ -2919,9 +3352,9 @@ "dev": true }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "dev": true, "requires": { "delayed-stream": "~1.0.0" @@ -3434,9 +3867,9 @@ "dev": true }, "diff-sequences": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.0.0.tgz", - "integrity": "sha512-46OkIuVGBBnrC0soO/4LHu5LHGHx0uhP65OVz8XOrAJpqiCB2aVIuESvjI1F9oqebuvY8lekS1pt6TN7vt7qsw==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz", + "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==", "dev": true }, "diffie-hellman": { @@ -3512,13 +3945,13 @@ } }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, - "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "electron-to-chromium": { @@ -3732,13 +4165,10 @@ } }, "exec-sh": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", - "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", - "dev": true, - "requires": { - "merge": "^1.2.0" - } + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", + "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==", + "dev": true }, "execa": { "version": "0.10.0", @@ -3827,16 +4257,17 @@ } }, "expect": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-24.1.0.tgz", - "integrity": "sha512-lVcAPhaYkQcIyMS+F8RVwzbm1jro20IG8OkvxQ6f1JfqhVZyyudCwYogQ7wnktlf14iF3ii7ArIUO/mqvrW9Gw==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.5.0.tgz", + "integrity": "sha512-p2Gmc0CLxOgkyA93ySWmHFYHUPFIHG6XZ06l7WArWAsrqYVaVEkOU5NtT5i68KUyGKbkQgDCkiT65bWmdoL6Bw==", "dev": true, "requires": { + "@jest/types": "^24.5.0", "ansi-styles": "^3.2.0", - "jest-get-type": "^24.0.0", - "jest-matcher-utils": "^24.0.0", - "jest-message-util": "^24.0.0", - "jest-regex-util": "^24.0.0" + "jest-get-type": "^24.3.0", + "jest-matcher-utils": "^24.5.0", + "jest-message-util": "^24.5.0", + "jest-regex-util": "^24.3.0" }, "dependencies": { "ansi-styles": { @@ -4186,9 +4617,9 @@ "dev": true }, "fork-ts-checker-webpack-plugin": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-0.5.2.tgz", - "integrity": "sha512-a5IG+xXyKnpruI0CP/anyRLAoxWtp3lzdG6flxicANnoSzz64b12dJ7ASAVRrI2OaWwZR2JyBaMHFQqInhWhIw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.0.0.tgz", + "integrity": "sha512-Kc7LI0OlnWB0FRbDQO+nnDCfZr+LhFSJIP8kZppDXvuXI/opeMg3IrlMedBX/EGgOUK0ma5Hafgkdp3DuxgYdg==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -4196,6 +4627,7 @@ "chokidar": "^2.0.4", "micromatch": "^3.1.10", "minimatch": "^3.0.4", + "semver": "^5.6.0", "tapable": "^1.0.0" }, "dependencies": { @@ -4219,6 +4651,12 @@ "supports-color": "^5.3.0" } }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -4231,13 +4669,13 @@ } }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "1.0.6", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, @@ -5056,6 +5494,16 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", "dev": true }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -5244,6 +5692,15 @@ } } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "ieee754": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", @@ -5772,6 +6229,187 @@ "lodash.isstring": "^4.0.1" } }, + "istanbul-api": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.1.tgz", + "integrity": "sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw==", + "dev": true, + "requires": { + "async": "^2.6.1", + "compare-versions": "^3.2.1", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.3", + "istanbul-lib-hook": "^2.0.3", + "istanbul-lib-instrument": "^3.1.0", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.2", + "istanbul-reports": "^2.1.1", + "js-yaml": "^3.12.0", + "make-dir": "^1.3.0", + "minimatch": "^3.0.4", + "once": "^1.4.0" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } + } + }, + "istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz", + "integrity": "sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz", + "integrity": "sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "supports-color": "^6.0.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz", + "integrity": "sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.1.1.tgz", + "integrity": "sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==", + "dev": true, + "requires": { + "handlebars": "^4.1.0" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true, + "optional": true + }, + "handlebars": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + } + } + } + }, "java-properties": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-0.2.10.tgz", @@ -5779,19 +6417,19 @@ "dev": true }, "jest": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-24.1.0.tgz", - "integrity": "sha512-+q91L65kypqklvlRFfXfdzUKyngQLOcwGhXQaLmVHv+d09LkNXuBuGxlofTFW42XMzu3giIcChchTsCNUjQ78A==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.5.0.tgz", + "integrity": "sha512-lxL+Fq5/RH7inxxmfS2aZLCf8MsS+YCUBfeiNO6BWz/MmjhDGaIEA/2bzEf9q4Q0X+mtFHiinHFvQ0u+RvW/qQ==", "dev": true, "requires": { "import-local": "^2.0.0", - "jest-cli": "^24.1.0" + "jest-cli": "^24.5.0" }, "dependencies": { "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "ansi-styles": { @@ -5803,21 +6441,6 @@ "color-convert": "^1.9.0" } }, - "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", - "dev": true - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -5838,52 +6461,19 @@ "string-width": "^2.1.1", "strip-ansi": "^4.0.0", "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } } }, - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true, - "optional": true - }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "execa": { @@ -5919,168 +6509,30 @@ "pump": "^3.0.0" } }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "handlebars": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", - "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", - "dev": true, - "requires": { - "async": "^2.5.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - } - }, "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, - "istanbul-api": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.1.tgz", - "integrity": "sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw==", - "dev": true, - "requires": { - "async": "^2.6.1", - "compare-versions": "^3.2.1", - "fileset": "^2.0.3", - "istanbul-lib-coverage": "^2.0.3", - "istanbul-lib-hook": "^2.0.3", - "istanbul-lib-instrument": "^3.1.0", - "istanbul-lib-report": "^2.0.4", - "istanbul-lib-source-maps": "^3.0.2", - "istanbul-reports": "^2.1.1", - "js-yaml": "^3.12.0", - "make-dir": "^1.3.0", - "minimatch": "^3.0.4", - "once": "^1.4.0" - } - }, - "istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz", - "integrity": "sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==", - "dev": true, - "requires": { - "append-transform": "^1.0.0" - } - }, - "istanbul-lib-instrument": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", - "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", - "dev": true, - "requires": { - "@babel/generator": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "istanbul-lib-coverage": "^2.0.3", - "semver": "^5.5.0" - } - }, - "istanbul-lib-report": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz", - "integrity": "sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^2.0.3", - "make-dir": "^1.3.0", - "supports-color": "^6.0.0" - }, - "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz", - "integrity": "sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.3", - "make-dir": "^1.3.0", - "rimraf": "^2.6.2", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.1.1.tgz", - "integrity": "sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==", - "dev": true, - "requires": { - "handlebars": "^4.1.0" - } - }, "jest-cli": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.1.0.tgz", - "integrity": "sha512-U/iyWPwOI0T1CIxVLtk/2uviOTJ/OiSWJSe8qt6X1VkbbgP+nrtLJlmT9lPBe4lK78VNFJtrJ7pttcNv/s7yCw==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.5.0.tgz", + "integrity": "sha512-P+Jp0SLO4KWN0cGlNtC7JV0dW1eSFR7eRpoOucP2UM0sqlzp/bVHeo71Omonvigrj9AvCKy7NtQANtqJ7FXz8g==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", + "@jest/core": "^24.5.0", + "@jest/test-result": "^24.5.0", + "@jest/types": "^24.5.0", "chalk": "^2.0.1", "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.1.15", "import-local": "^2.0.0", "is-ci": "^2.0.0", - "istanbul-api": "^2.0.8", - "istanbul-lib-coverage": "^2.0.2", - "istanbul-lib-instrument": "^3.0.1", - "istanbul-lib-source-maps": "^3.0.1", - "jest-changed-files": "^24.0.0", - "jest-config": "^24.1.0", - "jest-environment-jsdom": "^24.0.0", - "jest-get-type": "^24.0.0", - "jest-haste-map": "^24.0.0", - "jest-message-util": "^24.0.0", - "jest-regex-util": "^24.0.0", - "jest-resolve-dependencies": "^24.1.0", - "jest-runner": "^24.1.0", - "jest-runtime": "^24.1.0", - "jest-snapshot": "^24.1.0", - "jest-util": "^24.0.0", - "jest-validate": "^24.0.0", - "jest-watcher": "^24.0.0", - "jest-worker": "^24.0.0", - "micromatch": "^3.1.10", - "node-notifier": "^5.2.1", - "p-each-series": "^1.0.0", - "pirates": "^4.0.0", + "jest-config": "^24.5.0", + "jest-util": "^24.5.0", + "jest-validate": "^24.5.0", "prompts": "^2.0.1", - "realpath-native": "^1.0.0", - "rimraf": "^2.5.4", - "slash": "^2.0.0", - "string-length": "^2.0.0", - "strip-ansi": "^5.0.0", - "which": "^1.2.12", + "realpath-native": "^1.1.0", "yargs": "^12.0.2" } }, @@ -6103,27 +6555,21 @@ "path-exists": "^3.0.0" } }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, "mem": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", - "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.2.0.tgz", + "integrity": "sha512-5fJxa68urlY0Ir8ijatKa3eRz5lwXnRCTvo9+TbTGAuTFJOwpGcY0X05moBd0nW45965Njt4CDI2GFQoG8DvqA==", "dev": true, "requires": { "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", + "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" } }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "mimic-fn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.0.0.tgz", + "integrity": "sha512-jbex9Yd/3lmICXwYT6gA/j2mNQGU48wCh/VzRd+/Y/PjYQtlg1gLMdZqvu9s/xH7qKvngxRObl56XZR609IMbA==", "dev": true }, "os-locale": { @@ -6144,9 +6590,9 @@ "dev": true }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -6173,25 +6619,13 @@ "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "strip-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", - "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^4.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { @@ -6203,17 +6637,6 @@ "has-flag": "^3.0.0" } }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - } - }, "yargs": { "version": "12.0.5", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", @@ -6233,25 +6656,16 @@ "y18n": "^3.2.1 || ^4.0.0", "yargs-parser": "^11.1.1" } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, "jest-changed-files": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.0.0.tgz", - "integrity": "sha512-nnuU510R9U+UX0WNb5XFEcsrMqriSiRLeO9KWDFgPrpToaQm60prfQYpxsXigdClpvNot5bekDY440x9dNGnsQ==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.5.0.tgz", + "integrity": "sha512-Ikl29dosYnTsH9pYa1Tv9POkILBhN/TLZ37xbzgNsZ1D2+2n+8oEZS2yP1BrHn/T4Rs4Ggwwbp/x8CKOS5YJOg==", "dev": true, "requires": { + "@jest/types": "^24.5.0", "execa": "^1.0.0", "throat": "^4.0.0" }, @@ -6302,26 +6716,27 @@ } }, "jest-config": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.1.0.tgz", - "integrity": "sha512-FbbRzRqtFC6eGjG5VwsbW4E5dW3zqJKLWYiZWhB0/4E5fgsMw8GODLbGSrY5t17kKOtCWb/Z7nsIThRoDpuVyg==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.5.0.tgz", + "integrity": "sha512-t2UTh0Z2uZhGBNVseF8wA2DS2SuBiLOL6qpLq18+OZGfFUxTM7BzUVKyHFN/vuN+s/aslY1COW95j1Rw81huOQ==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "babel-jest": "^24.1.0", + "@jest/types": "^24.5.0", + "babel-jest": "^24.5.0", "chalk": "^2.0.1", "glob": "^7.1.1", - "jest-environment-jsdom": "^24.0.0", - "jest-environment-node": "^24.0.0", - "jest-get-type": "^24.0.0", - "jest-jasmine2": "^24.1.0", - "jest-regex-util": "^24.0.0", - "jest-resolve": "^24.1.0", - "jest-util": "^24.0.0", - "jest-validate": "^24.0.0", + "jest-environment-jsdom": "^24.5.0", + "jest-environment-node": "^24.5.0", + "jest-get-type": "^24.3.0", + "jest-jasmine2": "^24.5.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.5.0", + "jest-util": "^24.5.0", + "jest-validate": "^24.5.0", "micromatch": "^3.1.10", - "pretty-format": "^24.0.0", - "realpath-native": "^1.0.2" + "pretty-format": "^24.5.0", + "realpath-native": "^1.1.0" }, "dependencies": { "ansi-styles": { @@ -6356,15 +6771,15 @@ } }, "jest-diff": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.0.0.tgz", - "integrity": "sha512-XY5wMpRaTsuMoU+1/B2zQSKQ9RdE9gsLkGydx3nvApeyPijLA8GtEvIcPwISRCer+VDf9W1mStTYYq6fPt8ryA==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.5.0.tgz", + "integrity": "sha512-mCILZd9r7zqL9Uh6yNoXjwGQx0/J43OD2vvWVKwOEOLZliQOsojXwqboubAQ+Tszrb6DHGmNU7m4whGeB9YOqw==", "dev": true, "requires": { "chalk": "^2.0.1", - "diff-sequences": "^24.0.0", - "jest-get-type": "^24.0.0", - "pretty-format": "^24.0.0" + "diff-sequences": "^24.3.0", + "jest-get-type": "^24.3.0", + "pretty-format": "^24.5.0" }, "dependencies": { "ansi-styles": { @@ -6399,24 +6814,25 @@ } }, "jest-docblock": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.0.0.tgz", - "integrity": "sha512-KfAKZ4SN7CFOZpWg4i7g7MSlY0M+mq7K0aMqENaG2vHuhC9fc3vkpU/iNN9sOus7v3h3Y48uEjqz3+Gdn2iptA==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.3.0.tgz", + "integrity": "sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==", "dev": true, "requires": { "detect-newline": "^2.1.0" } }, "jest-each": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.0.0.tgz", - "integrity": "sha512-gFcbY4Cu55yxExXMkjrnLXov3bWO3dbPAW7HXb31h/DNWdNc/6X8MtxGff8nh3/MjkF9DpVqnj0KsPKuPK0cpA==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.5.0.tgz", + "integrity": "sha512-6gy3Kh37PwIT5sNvNY2VchtIFOOBh8UCYnBlxXMb5sr5wpJUDPTUATX2Axq1Vfk+HWTMpsYPeVYp4TXx5uqUBw==", "dev": true, "requires": { + "@jest/types": "^24.5.0", "chalk": "^2.0.1", - "jest-get-type": "^24.0.0", - "jest-util": "^24.0.0", - "pretty-format": "^24.0.0" + "jest-get-type": "^24.3.0", + "jest-util": "^24.5.0", + "pretty-format": "^24.5.0" }, "dependencies": { "ansi-styles": { @@ -6451,46 +6867,53 @@ } }, "jest-environment-jsdom": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.0.0.tgz", - "integrity": "sha512-1YNp7xtxajTRaxbylDc2pWvFnfDTH5BJJGyVzyGAKNt/lEULohwEV9zFqTgG4bXRcq7xzdd+sGFws+LxThXXOw==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.5.0.tgz", + "integrity": "sha512-62Ih5HbdAWcsqBx2ktUnor/mABBo1U111AvZWcLKeWN/n/gc5ZvDBKe4Og44fQdHKiXClrNGC6G0mBo6wrPeGQ==", "dev": true, "requires": { - "jest-mock": "^24.0.0", - "jest-util": "^24.0.0", + "@jest/environment": "^24.5.0", + "@jest/fake-timers": "^24.5.0", + "@jest/types": "^24.5.0", + "jest-mock": "^24.5.0", + "jest-util": "^24.5.0", "jsdom": "^11.5.1" } }, "jest-environment-node": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.0.0.tgz", - "integrity": "sha512-62fOFcaEdU0VLaq8JL90TqwI7hLn0cOKOl8vY2n477vRkCJRojiRRtJVRzzCcgFvs6gqU97DNqX5R0BrBP6Rxg==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.5.0.tgz", + "integrity": "sha512-du6FuyWr/GbKLsmAbzNF9mpr2Iu2zWSaq/BNHzX+vgOcts9f2ayXBweS7RAhr+6bLp6qRpMB6utAMF5Ygktxnw==", "dev": true, "requires": { - "jest-mock": "^24.0.0", - "jest-util": "^24.0.0" + "@jest/environment": "^24.5.0", + "@jest/fake-timers": "^24.5.0", + "@jest/types": "^24.5.0", + "jest-mock": "^24.5.0", + "jest-util": "^24.5.0" } }, "jest-get-type": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.0.0.tgz", - "integrity": "sha512-z6/Eyf6s9ZDGz7eOvl+fzpuJmN9i0KyTt1no37/dHu8galssxz5ZEgnc1KaV8R31q1khxyhB4ui/X5ZjjPk77w==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", + "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", "dev": true }, "jest-haste-map": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.0.0.tgz", - "integrity": "sha512-CcViJyUo41IQqttLxXVdI41YErkzBKbE6cS6dRAploCeutePYfUimWd3C9rQEWhX0YBOQzvNsC0O9nYxK2nnxQ==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.5.0.tgz", + "integrity": "sha512-mb4Yrcjw9vBgSvobDwH8QUovxApdimGcOkp+V1ucGGw4Uvr3VzZQBJhNm1UY3dXYm4XXyTW2G7IBEZ9pM2ggRQ==", "dev": true, "requires": { + "@jest/types": "^24.5.0", "fb-watchman": "^2.0.0", "graceful-fs": "^4.1.15", "invariant": "^2.2.4", - "jest-serializer": "^24.0.0", - "jest-util": "^24.0.0", - "jest-worker": "^24.0.0", + "jest-serializer": "^24.4.0", + "jest-util": "^24.5.0", + "jest-worker": "^24.4.0", "micromatch": "^3.1.10", - "sane": "^3.0.0" + "sane": "^4.0.3" }, "dependencies": { "graceful-fs": { @@ -6511,22 +6934,26 @@ } }, "jest-jasmine2": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.1.0.tgz", - "integrity": "sha512-H+o76SdSNyCh9fM5K8upK45YTo/DiFx5w2YAzblQebSQmukDcoVBVeXynyr7DDnxh+0NTHYRCLwJVf3tC518wg==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.5.0.tgz", + "integrity": "sha512-sfVrxVcx1rNUbBeyIyhkqZ4q+seNKyAG6iM0S2TYBdQsXjoFDdqWFfsUxb6uXSsbimbXX/NMkJIwUZ1uT9+/Aw==", "dev": true, "requires": { "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.5.0", + "@jest/test-result": "^24.5.0", + "@jest/types": "^24.5.0", "chalk": "^2.0.1", "co": "^4.6.0", - "expect": "^24.1.0", + "expect": "^24.5.0", "is-generator-fn": "^2.0.0", - "jest-each": "^24.0.0", - "jest-matcher-utils": "^24.0.0", - "jest-message-util": "^24.0.0", - "jest-snapshot": "^24.1.0", - "jest-util": "^24.0.0", - "pretty-format": "^24.0.0", + "jest-each": "^24.5.0", + "jest-matcher-utils": "^24.5.0", + "jest-message-util": "^24.5.0", + "jest-runtime": "^24.5.0", + "jest-snapshot": "^24.5.0", + "jest-util": "^24.5.0", + "pretty-format": "^24.5.0", "throat": "^4.0.0" }, "dependencies": { @@ -6562,24 +6989,24 @@ } }, "jest-leak-detector": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.0.0.tgz", - "integrity": "sha512-ZYHJYFeibxfsDSKowjDP332pStuiFT2xfc5R67Rjm/l+HFJWJgNIOCOlQGeXLCtyUn3A23+VVDdiCcnB6dTTrg==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.5.0.tgz", + "integrity": "sha512-LZKBjGovFRx3cRBkqmIg+BZnxbrLqhQl09IziMk3oeh1OV81Hg30RUIx885mq8qBv1PA0comB9bjKcuyNO1bCQ==", "dev": true, "requires": { - "pretty-format": "^24.0.0" + "pretty-format": "^24.5.0" } }, "jest-matcher-utils": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.0.0.tgz", - "integrity": "sha512-LQTDmO+aWRz1Tf9HJg+HlPHhDh1E1c65kVwRFo5mwCVp5aQDzlkz4+vCvXhOKFjitV2f0kMdHxnODrXVoi+rlA==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.5.0.tgz", + "integrity": "sha512-QM1nmLROjLj8GMGzg5VBra3I9hLpjMPtF1YqzQS3rvWn2ltGZLrGAO1KQ9zUCVi5aCvrkbS5Ndm2evIP9yZg1Q==", "dev": true, "requires": { "chalk": "^2.0.1", - "jest-diff": "^24.0.0", - "jest-get-type": "^24.0.0", - "pretty-format": "^24.0.0" + "jest-diff": "^24.5.0", + "jest-get-type": "^24.3.0", + "pretty-format": "^24.5.0" }, "dependencies": { "ansi-styles": { @@ -6614,12 +7041,15 @@ } }, "jest-message-util": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.0.0.tgz", - "integrity": "sha512-J9ROJIwz/IeC+eV1XSwnRK4oAwPuhmxEyYx1+K5UI+pIYwFZDSrfZaiWTdq0d2xYFw4Xiu+0KQWsdsQpgJMf3Q==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.5.0.tgz", + "integrity": "sha512-6ZYgdOojowCGiV0D8WdgctZEAe+EcFU+KrVds+0ZjvpZurUW2/oKJGltJ6FWY2joZwYXN5VL36GPV6pNVRqRnQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.5.0", + "@jest/types": "^24.5.0", + "@types/stack-utils": "^1.0.1", "chalk": "^2.0.1", "micromatch": "^3.1.10", "slash": "^2.0.0", @@ -6664,26 +7094,37 @@ } }, "jest-mock": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.0.0.tgz", - "integrity": "sha512-sQp0Hu5fcf5NZEh1U9eIW2qD0BwJZjb63Yqd98PQJFvf/zzUTBoUAwv/Dc/HFeNHIw1f3hl/48vNn+j3STaI7A==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.5.0.tgz", + "integrity": "sha512-ZnAtkWrKf48eERgAOiUxVoFavVBziO2pAi2MfZ1+bGXVkDfxWLxU0//oJBkgwbsv6OAmuLBz4XFFqvCFMqnGUw==", + "dev": true, + "requires": { + "@jest/types": "^24.5.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", + "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", "dev": true }, "jest-regex-util": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.0.0.tgz", - "integrity": "sha512-Jv/uOTCuC+PY7WpJl2mpoI+WbY2ut73qwwO9ByJJNwOCwr1qWhEW2Lyi2S9ZewUdJqeVpEBisdEVZSI+Zxo58Q==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", + "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", "dev": true }, "jest-resolve": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.1.0.tgz", - "integrity": "sha512-TPiAIVp3TG6zAxH28u/6eogbwrvZjBMWroSLBDkwkHKrqxB/RIdwkWDye4uqPlZIXWIaHtifY3L0/eO5Z0f2wg==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.5.0.tgz", + "integrity": "sha512-ZIfGqLX1Rg8xJpQqNjdoO8MuxHV1q/i2OO1hLXjgCWFWs5bsedS8UrOdgjUqqNae6DXA+pCyRmdcB7lQEEbXew==", "dev": true, "requires": { + "@jest/types": "^24.5.0", "browser-resolve": "^1.11.3", "chalk": "^2.0.1", - "realpath-native": "^1.0.0" + "jest-pnp-resolver": "^1.2.1", + "realpath-native": "^1.1.0" }, "dependencies": { "ansi-styles": { @@ -6718,33 +7159,39 @@ } }, "jest-resolve-dependencies": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.1.0.tgz", - "integrity": "sha512-2VwPsjd3kRPu7qe2cpytAgowCObk5AKeizfXuuiwgm1a9sijJDZe8Kh1sFj6FKvSaNEfCPlBVkZEJa2482m/Uw==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.5.0.tgz", + "integrity": "sha512-dRVM1D+gWrFfrq2vlL5P9P/i8kB4BOYqYf3S7xczZ+A6PC3SgXYSErX/ScW/469pWMboM1uAhgLF+39nXlirCQ==", "dev": true, "requires": { - "jest-regex-util": "^24.0.0", - "jest-snapshot": "^24.1.0" + "@jest/types": "^24.5.0", + "jest-regex-util": "^24.3.0", + "jest-snapshot": "^24.5.0" } }, "jest-runner": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.1.0.tgz", - "integrity": "sha512-CDGOkT3AIFl16BLL/OdbtYgYvbAprwJ+ExKuLZmGSCSldwsuU2dEGauqkpvd9nphVdAnJUcP12e/EIlnTX0QXg==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.5.0.tgz", + "integrity": "sha512-oqsiS9TkIZV5dVkD+GmbNfWBRPIvxqmlTQ+AQUJUQ07n+4xTSDc40r+aKBynHw9/tLzafC00DIbJjB2cOZdvMA==", "dev": true, "requires": { + "@jest/console": "^24.3.0", + "@jest/environment": "^24.5.0", + "@jest/test-result": "^24.5.0", + "@jest/types": "^24.5.0", "chalk": "^2.4.2", "exit": "^0.1.2", "graceful-fs": "^4.1.15", - "jest-config": "^24.1.0", - "jest-docblock": "^24.0.0", - "jest-haste-map": "^24.0.0", - "jest-jasmine2": "^24.1.0", - "jest-leak-detector": "^24.0.0", - "jest-message-util": "^24.0.0", - "jest-runtime": "^24.1.0", - "jest-util": "^24.0.0", - "jest-worker": "^24.0.0", + "jest-config": "^24.5.0", + "jest-docblock": "^24.3.0", + "jest-haste-map": "^24.5.0", + "jest-jasmine2": "^24.5.0", + "jest-leak-detector": "^24.5.0", + "jest-message-util": "^24.5.0", + "jest-resolve": "^24.5.0", + "jest-runtime": "^24.5.0", + "jest-util": "^24.5.0", + "jest-worker": "^24.4.0", "source-map-support": "^0.5.6", "throat": "^4.0.0" }, @@ -6787,32 +7234,33 @@ } }, "jest-runtime": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.1.0.tgz", - "integrity": "sha512-59/BY6OCuTXxGeDhEMU7+N33dpMQyXq7MLK07cNSIY/QYt2QZgJ7Tjx+rykBI0skAoigFl0A5tmT8UdwX92YuQ==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.5.0.tgz", + "integrity": "sha512-GTFHzfLdwpaeoDPilNpBrorlPoNZuZrwKKzKJs09vWwHo+9TOsIIuszK8cWOuKC7ss07aN1922Ge8fsGdsqCuw==", "dev": true, "requires": { - "@babel/core": "^7.1.0", - "babel-plugin-istanbul": "^5.1.0", + "@jest/console": "^24.3.0", + "@jest/environment": "^24.5.0", + "@jest/source-map": "^24.3.0", + "@jest/transform": "^24.5.0", + "@jest/types": "^24.5.0", + "@types/yargs": "^12.0.2", "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", "exit": "^0.1.2", - "fast-json-stable-stringify": "^2.0.0", "glob": "^7.1.3", "graceful-fs": "^4.1.15", - "jest-config": "^24.1.0", - "jest-haste-map": "^24.0.0", - "jest-message-util": "^24.0.0", - "jest-regex-util": "^24.0.0", - "jest-resolve": "^24.1.0", - "jest-snapshot": "^24.1.0", - "jest-util": "^24.0.0", - "jest-validate": "^24.0.0", - "micromatch": "^3.1.10", - "realpath-native": "^1.0.0", + "jest-config": "^24.5.0", + "jest-haste-map": "^24.5.0", + "jest-message-util": "^24.5.0", + "jest-mock": "^24.5.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.5.0", + "jest-snapshot": "^24.5.0", + "jest-util": "^24.5.0", + "jest-validate": "^24.5.0", + "realpath-native": "^1.1.0", "slash": "^2.0.0", "strip-bom": "^3.0.0", - "write-file-atomic": "2.4.1", "yargs": "^12.0.2" }, "dependencies": { @@ -6831,12 +7279,6 @@ "color-convert": "^1.9.0" } }, - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", - "dev": true - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -6951,16 +7393,22 @@ } }, "mem": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", - "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.2.0.tgz", + "integrity": "sha512-5fJxa68urlY0Ir8ijatKa3eRz5lwXnRCTvo9+TbTGAuTFJOwpGcY0X05moBd0nW45965Njt4CDI2GFQoG8DvqA==", "dev": true, "requires": { "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", + "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" } }, + "mimic-fn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.0.0.tgz", + "integrity": "sha512-jbex9Yd/3lmICXwYT6gA/j2mNQGU48wCh/VzRd+/Y/PjYQtlg1gLMdZqvu9s/xH7qKvngxRObl56XZR609IMbA==", + "dev": true + }, "os-locale": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", @@ -6979,9 +7427,9 @@ "dev": true }, "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -7051,40 +7499,32 @@ "y18n": "^3.2.1 || ^4.0.0", "yargs-parser": "^11.1.1" } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, "jest-serializer": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.0.0.tgz", - "integrity": "sha512-9FKxQyrFgHtx3ozU+1a8v938ILBE7S8Ko3uiAVjT8Yfi2o91j/fj81jacCQZ/Ihjiff/VsUCXVgQ+iF1XdImOw==", + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.4.0.tgz", + "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==", "dev": true }, "jest-snapshot": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.1.0.tgz", - "integrity": "sha512-th6TDfFqEmXvuViacU1ikD7xFb7lQsPn2rJl7OEmnfIVpnrx3QNY2t3PE88meeg0u/mQ0nkyvmC05PBqO4USFA==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.5.0.tgz", + "integrity": "sha512-eBEeJb5ROk0NcpodmSKnCVgMOo+Qsu5z9EDl3tGffwPzK1yV37mjGWF2YeIz1NkntgTzP+fUL4s09a0+0dpVWA==", "dev": true, "requires": { "@babel/types": "^7.0.0", + "@jest/types": "^24.5.0", "chalk": "^2.0.1", - "jest-diff": "^24.0.0", - "jest-matcher-utils": "^24.0.0", - "jest-message-util": "^24.0.0", - "jest-resolve": "^24.1.0", + "expect": "^24.5.0", + "jest-diff": "^24.5.0", + "jest-matcher-utils": "^24.5.0", + "jest-message-util": "^24.5.0", + "jest-resolve": "^24.5.0", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "pretty-format": "^24.0.0", + "pretty-format": "^24.5.0", "semver": "^5.5.0" }, "dependencies": { @@ -7126,16 +7566,21 @@ } }, "jest-util": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.0.0.tgz", - "integrity": "sha512-QxsALc4wguYS7cfjdQSOr5HTkmjzkHgmZvIDkcmPfl1ib8PNV8QUWLwbKefCudWS0PRKioV+VbQ0oCUPC691fQ==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.5.0.tgz", + "integrity": "sha512-Xy8JsD0jvBz85K7VsTIQDuY44s+hYJyppAhcsHsOsGisVtdhar6fajf2UOf2mEVEgh15ZSdA0zkCuheN8cbr1Q==", "dev": true, "requires": { + "@jest/console": "^24.3.0", + "@jest/fake-timers": "^24.5.0", + "@jest/source-map": "^24.3.0", + "@jest/test-result": "^24.5.0", + "@jest/types": "^24.5.0", + "@types/node": "*", "callsites": "^3.0.0", "chalk": "^2.0.1", "graceful-fs": "^4.1.15", "is-ci": "^2.0.0", - "jest-message-util": "^24.0.0", "mkdirp": "^0.5.1", "slash": "^2.0.0", "source-map": "^0.6.0" @@ -7191,16 +7636,17 @@ } }, "jest-validate": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.0.0.tgz", - "integrity": "sha512-vMrKrTOP4BBFIeOWsjpsDgVXATxCspC9S1gqvbJ3Tnn/b9ACsJmteYeVx9830UMV28Cob1RX55x96Qq3Tfad4g==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.5.0.tgz", + "integrity": "sha512-gg0dYszxjgK2o11unSIJhkOFZqNRQbWOAB2/LOUdsd2LfD9oXiMeuee8XsT0iRy5EvSccBgB4h/9HRbIo3MHgQ==", "dev": true, "requires": { + "@jest/types": "^24.5.0", "camelcase": "^5.0.0", "chalk": "^2.0.1", - "jest-get-type": "^24.0.0", + "jest-get-type": "^24.3.0", "leven": "^2.1.0", - "pretty-format": "^24.0.0" + "pretty-format": "^24.5.0" }, "dependencies": { "ansi-styles": { @@ -7213,9 +7659,9 @@ } }, "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.2.0.tgz", + "integrity": "sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ==", "dev": true }, "chalk": { @@ -7241,14 +7687,18 @@ } }, "jest-watcher": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.0.0.tgz", - "integrity": "sha512-GxkW2QrZ4YxmW1GUWER05McjVDunBlKMFfExu+VsGmXJmpej1saTEKvONdx5RJBlVdpPI5x6E3+EDQSIGgl53g==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.5.0.tgz", + "integrity": "sha512-/hCpgR6bg0nKvD3nv4KasdTxuhwfViVMHUATJlnGCD0r1QrmIssimPbmc5KfAQblAVxkD8xrzuij9vfPUk1/rA==", "dev": true, "requires": { + "@jest/test-result": "^24.5.0", + "@jest/types": "^24.5.0", + "@types/node": "*", + "@types/yargs": "^12.0.9", "ansi-escapes": "^3.0.0", "chalk": "^2.0.1", - "jest-util": "^24.0.0", + "jest-util": "^24.5.0", "string-length": "^2.0.0" }, "dependencies": { @@ -7284,11 +7734,12 @@ } }, "jest-worker": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.0.0.tgz", - "integrity": "sha512-s64/OThpfQvoCeHG963MiEZOAAxu8kHsaL/rCMF7lpdzo7vgF0CtPml9hfguOMgykgH/eOm4jFP4ibfHLruytg==", + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.4.0.tgz", + "integrity": "sha512-BH9X/klG9vxwoO99ZBUbZFfV8qO0XNZ5SIiCyYK2zOuJBl6YJVAeNIQjcoOVNu4HGEHeYEKsUWws8kSlSbZ9YQ==", "dev": true, "requires": { + "@types/node": "*", "merge-stream": "^1.0.1", "supports-color": "^6.1.0" }, @@ -7330,8 +7781,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true + "dev": true }, "jsdom": { "version": "11.12.0", @@ -7365,17 +7815,6 @@ "whatwg-url": "^6.4.1", "ws": "^5.2.0", "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - } } }, "json-parse-better-errors": { @@ -7814,12 +8253,6 @@ } } }, - "merge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", - "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", - "dev": true - }, "merge-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", @@ -7867,18 +8300,18 @@ } }, "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", "dev": true }, "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", "dev": true, "requires": { - "mime-db": "~1.30.0" + "mime-db": "~1.38.0" } }, "mimic-fn": { @@ -8395,6 +8828,12 @@ "integrity": "sha512-T5GaA1J/d34AC8mkrFD2O0DR17kwJ702ZOtJOsS8RpbsQZVOC2/xYFb1i/cw+xdM54JIlMuojjDOYct8GIWtwg==", "dev": true }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -8879,19 +9318,21 @@ "dev": true }, "pretty-format": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.0.0.tgz", - "integrity": "sha512-LszZaKG665djUcqg5ZQq+XzezHLKrxsA86ZABTozp+oNhkdqa+tG2dX4qa6ERl5c/sRDrAa3lHmwnvKoP+OG/g==", + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.5.0.tgz", + "integrity": "sha512-/3RuSghukCf8Riu5Ncve0iI+BzVkbRU5EeUoArKARZobREycuH5O4waxvaNIloEXdb0qwgmEAed5vTpX1HNROQ==", "dev": true, "requires": { + "@jest/types": "^24.5.0", "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0" + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" }, "dependencies": { "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "ansi-styles": { @@ -9036,6 +9477,12 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -9099,6 +9546,12 @@ } } }, + "react-is": { + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.4.tgz", + "integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==", + "dev": true + }, "read-pkg": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", @@ -9395,79 +9848,12 @@ "uuid": "^3.3.2" }, "dependencies": { - "ajv": { - "version": "6.9.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.2.tgz", - "integrity": "sha512-4UFy0/LgDo7Oa/+wOAlj44tp9K78u38E5/359eSrqEp1Z5PdVfimCcs7SluXMP755RUQu6d2b4AvF0R1C9RZjg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "mime-db": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", - "dev": true - }, - "mime-types": { - "version": "2.1.22", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", - "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", - "dev": true, - "requires": { - "mime-db": "~1.38.0" - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -9483,12 +9869,6 @@ "psl": "^1.1.24", "punycode": "^1.4.1" } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true } } }, @@ -9622,9 +10002,9 @@ } }, "rsvp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", - "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.4.tgz", + "integrity": "sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==", "dev": true }, "run-queue": { @@ -9658,21 +10038,20 @@ "dev": true }, "sane": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-3.1.0.tgz", - "integrity": "sha512-G5GClRRxT1cELXfdAq7UKtUsv8q/ZC5k8lQGmjEm4HcAl3HzBy68iglyNCmw4+0tiXPCBZntslHlRhbnsSws+Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", "dev": true, "requires": { + "@cnakazawa/watch": "^1.0.3", "anymatch": "^2.0.0", - "capture-exit": "^1.2.0", - "exec-sh": "^0.2.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", "execa": "^1.0.0", "fb-watchman": "^2.0.0", - "fsevents": "^1.2.3", "micromatch": "^3.1.4", "minimist": "^1.1.1", - "walker": "~1.0.5", - "watch": "~0.18.0" + "walker": "~1.0.5" }, "dependencies": { "cross-spawn": { @@ -10341,9 +10720,9 @@ "dev": true }, "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "dev": true, "requires": { "asn1": "~0.2.3", @@ -10757,6 +11136,12 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "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 + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -10809,20 +11194,13 @@ } }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, "requires": { - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "tr46": { @@ -10969,8 +11347,7 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true + "dev": true }, "type-check": { "version": "0.3.2", @@ -11366,6 +11743,12 @@ "object.getownpropertydescriptors": "^2.0.3" } }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, "v8-compile-cache": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz", @@ -11420,24 +11803,6 @@ "makeerror": "1.0.x" } }, - "watch": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", - "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", - "dev": true, - "requires": { - "exec-sh": "^0.2.0", - "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", @@ -11763,17 +12128,6 @@ "dev": true, "requires": { "iconv-lite": "0.4.24" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } } }, "whatwg-mimetype": { @@ -11899,6 +12253,15 @@ "signal-exit": "^3.0.2" } }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", @@ -11942,6 +12305,24 @@ "window-size": "0.1.0" } }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.2.0.tgz", + "integrity": "sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ==", + "dev": true + } + } + }, "yn": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/yn/-/yn-3.0.0.tgz", diff --git a/package.json b/package.json index 966b949..d036adb 100644 --- a/package.json +++ b/package.json @@ -50,12 +50,12 @@ "@babel/plugin-proposal-object-rest-spread": "^7.2.0", "@babel/preset-env": "7.3.4", "@babel/preset-typescript": "^7.1.0", - "@types/jest": "24.0.9", + "@types/jest": "24.0.11", "@types/node": "^11.10.0", "@types/webpack": "^4.4.25", "babel-loader": "^8.0.5", - "fork-ts-checker-webpack-plugin": "^0.5.2", - "jest": "24.1.0", + "fork-ts-checker-webpack-plugin": "^1.0.0", + "jest": "24.5.0", "nodemon": "^1.18.9", "nodemon-webpack-plugin": "^4.0.7", "now": "^14.0.2", From fb13c4e609fdfe348197057c1a25dc394d73b828 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sun, 17 Mar 2019 05:25:36 -0400 Subject: [PATCH 20/30] fix: wrong return type when using Morphism.map(type) --- src/MorphismRegistry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MorphismRegistry.ts b/src/MorphismRegistry.ts index 7f6143b..6832f02 100644 --- a/src/MorphismRegistry.ts +++ b/src/MorphismRegistry.ts @@ -19,7 +19,7 @@ export interface IMorphismRegistry { * @param {Object} data Input data to transform * */ - map(type: Target): Mapper; + map(type: Constructable): Mapper; map(type: Constructable, data: Source[]): Target[]; map(type: Constructable, data: Source): Target; /** From 28e02c6f6935e8f6a0b3db2d517a895aaae7a796 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sun, 17 Mar 2019 05:26:56 -0400 Subject: [PATCH 21/30] tests: add parser UTs --- src/MorphismTree.spec.ts | 169 ++++++++++++++++++++++++++++++++++++++- src/MorphismTree.ts | 33 +++++--- 2 files changed, 189 insertions(+), 13 deletions(-) diff --git a/src/MorphismTree.spec.ts b/src/MorphismTree.spec.ts index 87faaf6..b87179a 100644 --- a/src/MorphismTree.spec.ts +++ b/src/MorphismTree.spec.ts @@ -1,4 +1,6 @@ -import { MophismSchemaTree, SchemaNode, NodeKind } from './MorphismTree'; +import { MophismSchemaTree, SchemaNode, NodeKind, parseSchema } from './MorphismTree'; +import { StrictSchema, morphism } from './morphism'; +import { set } from './helpers'; describe('Morphism Tree', () => { it('should add a node under the root', () => { @@ -64,4 +66,169 @@ describe('Morphism Tree', () => { expect(keyA1.data.propertyName).toEqual(nodeKeyA1.data.propertyName); expect(keyA1.data.kind).toEqual(nodeKeyA1.data.kind); }); + + describe('Parser', () => { + it('should parse a simple morphism schema to a MorphismTree', () => { + interface Target { + keyA: string; + } + const schema: StrictSchema = { keyA: 'test' }; + const tree = parseSchema(schema); + + const expected = { + propertyName: 'keyA', + targetPropertyPath: 'keyA', + kind: NodeKind.ActionString, + action: 'test' + }; + + let result; + for (const node of tree.traverseBFS()) { + const { + data: { propertyName, targetPropertyPath, kind, action } + } = node; + result = { propertyName, targetPropertyPath, kind, action }; + } + + expect(result).toEqual(expected); + }); + + it('should parse a complex morphism schema to a MorphismTree', () => { + interface Target { + keyA: { + keyA1: string; + }; + keyB: { + keyB1: { + keyB11: string; + }; + }; + } + const mockAction = 'action-string'; + const schema: StrictSchema = { + keyA: { keyA1: mockAction }, + keyB: { keyB1: { keyB11: mockAction } } + }; + const tree = parseSchema(schema); + + const expected = [ + { + propertyName: 'keyA', + targetPropertyPath: 'keyA', + kind: NodeKind.Property, + action: null + }, + { + propertyName: 'keyB', + targetPropertyPath: 'keyB', + kind: NodeKind.Property, + action: null + }, + { + propertyName: 'keyA1', + targetPropertyPath: 'keyA.keyA1', + kind: NodeKind.ActionString, + action: mockAction + }, + { + propertyName: 'keyB1', + targetPropertyPath: 'keyB.keyB1', + kind: NodeKind.Property, + action: null + }, + { + propertyName: 'keyB11', + targetPropertyPath: 'keyB.keyB1.keyB11', + kind: NodeKind.ActionString, + action: mockAction + } + ]; + + let results = []; + for (const node of tree.traverseBFS()) { + const { + data: { propertyName, targetPropertyPath, kind, action } + } = node; + results.push({ propertyName, targetPropertyPath, kind, action }); + } + + expect(results).toEqual(expected); + }); + + it('should parse an array of morphism actions in schema to a MorphismTree', () => { + interface Target { + keyA: [ + { + keyA1: string; + keyA2: string; + }, + { + keyB1: string; + keyB2: string; + } + ]; + } + + const mockAction = 'action-string'; + const schema: StrictSchema = { + keyA: [{ keyA1: mockAction, keyA2: mockAction }, { keyB1: mockAction, keyB2: mockAction }] + }; + const tree = parseSchema(schema); + + const expected = [ + { + action: null, + kind: 'Property', + propertyName: 'keyA', + targetPropertyPath: 'keyA' + }, + { + action: null, + kind: 'Property', + propertyName: '0', + targetPropertyPath: 'keyA.0' + }, + { + action: null, + kind: 'Property', + propertyName: '1', + targetPropertyPath: 'keyA.1' + }, + { + action: 'action-string', + kind: 'ActionString', + propertyName: 'keyA1', + targetPropertyPath: 'keyA.0.keyA1' + }, + { + action: 'action-string', + kind: 'ActionString', + propertyName: 'keyA2', + targetPropertyPath: 'keyA.0.keyA2' + }, + { + action: 'action-string', + kind: 'ActionString', + propertyName: 'keyB1', + targetPropertyPath: 'keyA.1.keyB1' + }, + { + action: 'action-string', + kind: 'ActionString', + propertyName: 'keyB2', + targetPropertyPath: 'keyA.1.keyB2' + } + ]; + + let results = []; + for (const node of tree.traverseBFS()) { + const { + data: { propertyName, targetPropertyPath, kind, action } + } = node; + results.push({ propertyName, targetPropertyPath, kind, action }); + } + + expect(results).toEqual(expected); + }); + }); }); diff --git a/src/MorphismTree.ts b/src/MorphismTree.ts index aed8989..cfbbe0d 100644 --- a/src/MorphismTree.ts +++ b/src/MorphismTree.ts @@ -8,7 +8,8 @@ import { isActionAggregator, isActionFunction, isFunction, - isString + isString, + isObject } from './helpers'; export enum NodeKind { @@ -55,19 +56,28 @@ function seedTreeSchema( tree: MophismSchemaTree, partialSchema: Partial | string | number, actionKey?: string, - parentkey?: string + parentKeyPath?: string ): void { if (isValidAction(partialSchema) && actionKey) { - tree.add({ propertyName: actionKey, action: partialSchema as Actions }, parentkey); - return; - } else if (actionKey && !Array.isArray(partialSchema)) { - // check if actionKey exists to verify if not root node - tree.add({ propertyName: actionKey, action: null }, parentkey); - } + tree.add({ propertyName: actionKey, action: partialSchema as Actions }, parentKeyPath); + parentKeyPath = parentKeyPath ? `${parentKeyPath}.${actionKey}` : actionKey; + } else { + if (actionKey) { + // check if actionKey exists to verify if not root node + tree.add({ propertyName: actionKey, action: null }, parentKeyPath); + parentKeyPath = parentKeyPath ? `${parentKeyPath}.${actionKey}` : actionKey; + } - Object.keys(partialSchema).forEach(key => { - seedTreeSchema(tree, (partialSchema as any)[key], key, actionKey); - }); + if (Array.isArray(partialSchema)) { + partialSchema.forEach((subSchema, index) => { + seedTreeSchema(tree, subSchema, index.toString(), parentKeyPath); + }); + } else if (isObject(partialSchema)) { + Object.keys(partialSchema).forEach(key => { + seedTreeSchema(tree, (partialSchema as any)[key], key, parentKeyPath); + }); + } + } } export class MophismSchemaTree { @@ -118,7 +128,6 @@ export class MophismSchemaTree { if (node.data.targetPropertyPath === targetPropertyPath) { nodeToAdd.parent = node; nodeToAdd.data.targetPropertyPath = `${node.data.targetPropertyPath}.${nodeToAdd.data.propertyName}`; - node.children.push(nodeToAdd); } } From e83e8040640fe3def70dfdb35342b2dea4840036 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sun, 17 Mar 2019 05:34:25 -0400 Subject: [PATCH 22/30] test: enforce type checking in UTs --- src/morphism.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/morphism.spec.ts b/src/morphism.spec.ts index aa49f13..185ac75 100644 --- a/src/morphism.spec.ts +++ b/src/morphism.spec.ts @@ -231,6 +231,7 @@ describe('Morphism', () => { let result = Morphism(schema, mock); expect(result).toEqual(expected); expect(result.target).toEqual(expected.target); + expect(result.target.replace).toBeDefined(); }); it('should support nesting mapping', function() { @@ -512,9 +513,12 @@ describe('Morphism', () => { } }; let user = Morphism(schema, dataSource, User); + let expectedUser = new User(); expectedUser.groups = dataSource.groups; expect(user).toEqual(expectedUser); + expect(user.firstName).toEqual(expectedUser.firstName); + expect(triggered).toEqual(true); }); @@ -639,6 +643,7 @@ describe('Morphism', () => { const results = mocks.map(mapper); results.forEach(res => { expect(res).toEqual(new Target('value')); + expect(res.field).toBeDefined(); }); }); From d4005e003d9f9bc4f2646acd5ef0b2cbd1b2b7bb Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sun, 17 Mar 2019 05:35:14 -0400 Subject: [PATCH 23/30] refactor: aggregator action is identified as an array with only strings --- src/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers.ts b/src/helpers.ts index 7de5bbf..e40d596 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -7,7 +7,7 @@ export function isActionString(value: any): value is string { return isString(value); } export function isActionAggregator(value: any): value is ActionAggregator { - return Array.isArray(value); + return Array.isArray(value) && value.every(isActionString); } export function isActionFunction(value: any): value is ActionFunction { return isFunction(value); From 7fa1c8c921ba6ebbdcf03cd2538a9b2b1467c552 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sun, 17 Mar 2019 05:35:22 -0400 Subject: [PATCH 24/30] chore: cleanup --- src/typings.spec.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/typings.spec.ts b/src/typings.spec.ts index eeffe76..0ac5608 100644 --- a/src/typings.spec.ts +++ b/src/typings.spec.ts @@ -53,24 +53,6 @@ describe('Morphism', () => { qux: elem => elem.bar }; const source = { bar: 'value' }; - // const target2 = morphism( - // { - // foo: 'bar', - // bar: elem => { - // elem; - // } - // }, - // [source] - // ); - // const targe3 = morphism( - // { - // foo: 'bar', - // bar: elem => { - // elem; - // } - // }, - // source - // ); const target = morphism(schema, source); const targets = morphism(schema, [source]); From 2151b1abf9999dad5a979d66aab29f248536415f Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sun, 17 Mar 2019 05:43:51 -0400 Subject: [PATCH 25/30] refactor: use new set function allowing to set arrays --- src/helpers.ts | 73 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index e40d596..82f0bd5 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -19,7 +19,8 @@ export function isValidAction(action: any) { export const aggregator = (paths: string[], object: any) => { return paths.reduce((delta, path) => { - return set(delta, path, get(object, path)); + set(delta, path, get(object, path)); // TODO: ensure set will return the mutated object + return delta; }, {}); }; @@ -48,21 +49,6 @@ export function isPromise(object: any) { throw 'Promise not supported in your environment'; } } - -export function set(object: any, path: string, value: any) { - path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties - path = path.replace(/^\./, ''); // strip a leading dot - const paths = path.split('.'); - const lastProperty = paths.pop() as string; - const finalValue = paths.reduceRight( - (finalObject, prop) => { - return { [prop]: finalObject }; - }, - { [lastProperty]: value } - ); - - return Object.assign(object, finalValue); -} export function get(object: any, path: string) { path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties path = path.replace(/^\./, ''); // strip a leading dot @@ -83,3 +69,58 @@ export function zipObject(props: string[], values: any[]) { return { ...prev, [prop]: values[i] }; }, {}); } + +// https://github.com/mariocasciaro/object-path/blob/master/index.js +function hasOwnProperty(obj: any, prop: any) { + if (obj == null) { + return false; + } + // to handle objects with null prototypes (too edge case?) + return Object.prototype.hasOwnProperty.call(obj, prop); +} +function hasShallowProperty(obj: any, prop: any) { + return (typeof prop === 'number' && Array.isArray(obj)) || hasOwnProperty(obj, prop); +} +function getShallowProperty(obj: any, prop: any) { + if (hasShallowProperty(obj, prop)) { + return obj[prop]; + } +} +export function set(obj: any, path: any, value: any, doNotReplace?: boolean): any { + if (typeof path === 'number') { + path = [path]; + } + if (!path || path.length === 0) { + return obj; + } + if (typeof path === 'string') { + return set(obj, path.split('.').map(getKey), value, doNotReplace); + } + const currentPath = path[0]; + const currentValue = getShallowProperty(obj, currentPath); + if (path.length === 1) { + if (currentValue === void 0 || !doNotReplace) { + obj[currentPath] = value; + } + return currentValue; + } + + if (currentValue === void 0) { + // check if we assume an array + if (typeof path[1] === 'number') { + obj[currentPath] = []; + } else { + obj[currentPath] = {}; + } + } + + return set(obj[currentPath], path.slice(1), value, doNotReplace); +} + +function getKey(key: any) { + const intKey = parseInt(key); + if (intKey.toString() === key) { + return intKey; + } + return key; +} From 05a1a30e920553a7e497d7dfc9fa051734303474 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sun, 17 Mar 2019 05:44:38 -0400 Subject: [PATCH 26/30] refactor: use new set function --- src/morphism.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/morphism.ts b/src/morphism.ts index 1b89be1..cc62dfa 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -1,7 +1,7 @@ /** * @module morphism */ -import { zipObject, isUndefined, set, get } from './helpers'; +import { zipObject, isUndefined, get, set } from './helpers'; import { Schema, StrictSchema, Constructable } from './types'; import { MophismSchemaTree, parseSchema } from './MorphismTree'; import { MorphismRegistry, IMorphismRegistry } from './MorphismRegistry'; @@ -43,7 +43,8 @@ function transformValuesFromObject( }; const finalValue = undefinedValueCheck(get(finalObject, chunk.targetPropertyPath), chunk.preparedAction); - return set(finalObject, chunk.targetPropertyPath, finalValue); + set(finalObject, chunk.targetPropertyPath, finalValue); + return finalObject; }, objectToCompute); } From 44b8ca239fe05d5e45658265ca24fcad63064b33 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sun, 17 Mar 2019 05:45:20 -0400 Subject: [PATCH 27/30] fix: signature when items are null with type to get a mapper --- src/morphism.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/morphism.ts b/src/morphism.ts index cc62dfa..bc92f70 100644 --- a/src/morphism.ts +++ b/src/morphism.ts @@ -90,7 +90,8 @@ function getSchemaForType(type: Constructable, baseSchema: Schema): Sch type SourceFromSchema = T extends StrictSchema | Schema ? U : never; type DestinationFromSchema = T extends StrictSchema | Schema ? U : never; -type ResultItem = { [P in keyof TSchema]: DestinationFromSchema[P] }; +type ResultItem = DestinationFromSchema; + export interface Mapper> { (data: SourceFromSchema[]): TResult[]; (data: SourceFromSchema): TResult; @@ -137,7 +138,7 @@ export function morphism< export function morphism( schema: TSchema, - items: SourceFromSchema | null, + items: null, type: Constructable ): Mapper; // morphism({}, null, T) => mapper(S) => T From 9d41242bcb419b9f1e3dcad006708faaaca0f1fa Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sun, 17 Mar 2019 05:45:32 -0400 Subject: [PATCH 28/30] test: deep nested actions --- src/morphism.spec.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/morphism.spec.ts b/src/morphism.spec.ts index 185ac75..92b3c71 100644 --- a/src/morphism.spec.ts +++ b/src/morphism.spec.ts @@ -664,6 +664,37 @@ describe('Morphism', () => { const target = morphism(schema, sample); expect(target).toEqual({ keyA: { keyA1: 'value' } }); }); + + it('should accept deep nested actions into array', () => { + interface Source { + keySource: string; + } + const sample: Source = { + keySource: 'value' + }; + + interface Target { + keyA: { + keyA1: [ + { + keyA11: string; + keyA12: number; + } + ]; + keyA2: string; + }; + } + const schema: StrictSchema = { + keyA: { + keyA1: [{ keyA11: 'keySource', keyA12: 'keySource' }], + keyA2: 'keySource' + } + }; + + const target = morphism(schema, sample); + + expect(target).toEqual({ keyA: { keyA1: [{ keyA11: 'value', keyA12: 'value' }], keyA2: 'value' } }); + }); }); }); }); From 179e9f02d44a7ece766cc92f15f8ce9dc37bf921 Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Sun, 17 Mar 2019 06:36:46 -0400 Subject: [PATCH 29/30] test: reorganize UTs --- src/MorphismRegistry.spec.ts | 83 +++++++ src/MorphismTree.spec.ts | 131 +++++------ src/classObjects.spec.ts | 340 +++++++++++++++++++++++----- src/jsObjects.spec.ts | 108 ++++----- src/morphism.spec.ts | 415 ++++++----------------------------- src/typescript.spec.ts | 54 ++++- src/typings.spec.ts | 109 --------- src/utils-test.ts | 48 ++++ 8 files changed, 652 insertions(+), 636 deletions(-) create mode 100644 src/MorphismRegistry.spec.ts delete mode 100644 src/typings.spec.ts create mode 100644 src/utils-test.ts diff --git a/src/MorphismRegistry.spec.ts b/src/MorphismRegistry.spec.ts new file mode 100644 index 0000000..90653be --- /dev/null +++ b/src/MorphismRegistry.spec.ts @@ -0,0 +1,83 @@ +import Morphism from './morphism'; +import { User, MockData } from './utils-test'; + +describe('Mappers Registry', function() { + const dataToCrunch: MockData[] = [ + { + firstName: 'John', + lastName: 'Smith', + age: 25, + address: { + streetAddress: '21 2nd Street', + city: 'New York', + state: 'NY', + postalCode: '10021' + }, + phoneNumber: [ + { + type: 'home', + number: '212 555-1234' + }, + { + type: 'fax', + number: '646 555-4567' + } + ] + } + ]; + beforeEach(() => { + Morphism.deleteMapper(User); + Morphism.register(User); + }); + it('should throw an exception when using Registration function without parameters', function() { + expect(() => Morphism.register(null as any, null)).toThrow(); + }); + + it('should throw an exception when trying to register a mapper type more than once', function() { + expect(() => { + Morphism.register(User, {}); + }).toThrow(); + }); + + it('should return the stored mapper after a registration', function() { + let schema = { + phoneNumber: 'phoneNumber[0].number' + }; + let mapper = Morphism.setMapper(User, schema); + let mapperSaved = Morphism.getMapper(User); + expect(typeof mapper).toEqual('function'); + expect(typeof mapperSaved).toEqual('function'); + expect(mapperSaved).toEqual(mapper); + }); + + it('should get a stored mapper after a registration', function() { + Morphism.setMapper(User, {}); + expect(typeof Morphism.getMapper(User)).toEqual('function'); + }); + + it('should allow to map data using a registered mapper', function() { + let schema = { + phoneNumber: 'phoneNumber[0].number' + }; + Morphism.setMapper(User, schema); + let desiredResult = new User('John', 'Smith', '212 555-1234'); + expect(Morphism.map(User, dataToCrunch)).toBeTruthy(); + expect(Morphism.map(User, dataToCrunch)[0]).toEqual(desiredResult); + }); + + it('should allow to map data using a mapper updated schema', function() { + let schema = { + phoneNumber: 'phoneNumber[0].number' + }; + let mapper = Morphism.setMapper(User, schema); + let desiredResult = new User('John', 'Smith', '212 555-1234'); + expect(mapper(dataToCrunch)[0]).toEqual(desiredResult); + }); + + it('should throw an exception when trying to set an non-registered type', function() { + Morphism.deleteMapper(User); + expect(() => { + Morphism.setMapper(User, {}); + }).toThrow(); + }); +}); diff --git a/src/MorphismTree.spec.ts b/src/MorphismTree.spec.ts index b87179a..0c9a8b8 100644 --- a/src/MorphismTree.spec.ts +++ b/src/MorphismTree.spec.ts @@ -1,70 +1,71 @@ import { MophismSchemaTree, SchemaNode, NodeKind, parseSchema } from './MorphismTree'; -import { StrictSchema, morphism } from './morphism'; -import { set } from './helpers'; - -describe('Morphism Tree', () => { - it('should add a node under the root', () => { - interface Target { - keyA: string; - } - const tree = new MophismSchemaTree(); - tree.add({ action: 'keyA', propertyName: 'keyA' }); - - const root: SchemaNode = { - data: { targetPropertyPath: '', propertyName: 'MorphismTreeRoot', action: null, kind: NodeKind.Root }, - parent: null, - children: [] - }; - const result: SchemaNode = { - data: { targetPropertyPath: 'keyA', propertyName: 'keyA', action: 'keyA', kind: NodeKind.ActionString }, - parent: root, - children: [] - }; - - expect(tree.root.data).toEqual(root.data); - expect(tree.root.parent).toEqual(root.parent); - - expect(tree.root.children[0].data.action).toEqual(result.data.action); - expect(tree.root.children[0].data.targetPropertyPath).toEqual(result.data.targetPropertyPath); - expect(tree.root.children[0].data.propertyName).toEqual(result.data.propertyName); - expect(tree.root.children[0].data.kind).toEqual(result.data.kind); - - expect(tree.root.children[0].parent!.data.propertyName).toEqual(root.data.propertyName); - }); +import { StrictSchema } from './morphism'; + +describe('Tree', () => { + describe('Add', () => { + it('should add a node under the root', () => { + interface Target { + keyA: string; + } + const tree = new MophismSchemaTree(); + tree.add({ action: 'keyA', propertyName: 'keyA' }); + + const root: SchemaNode = { + data: { targetPropertyPath: '', propertyName: 'MorphismTreeRoot', action: null, kind: NodeKind.Root }, + parent: null, + children: [] + }; + const result: SchemaNode = { + data: { targetPropertyPath: 'keyA', propertyName: 'keyA', action: 'keyA', kind: NodeKind.ActionString }, + parent: root, + children: [] + }; + + expect(tree.root.data).toEqual(root.data); + expect(tree.root.parent).toEqual(root.parent); + + expect(tree.root.children[0].data.action).toEqual(result.data.action); + expect(tree.root.children[0].data.targetPropertyPath).toEqual(result.data.targetPropertyPath); + expect(tree.root.children[0].data.propertyName).toEqual(result.data.propertyName); + expect(tree.root.children[0].data.kind).toEqual(result.data.kind); - it('should add a node under another node', () => { - interface Target { - keyA: string; - } - const tree = new MophismSchemaTree(); - const parentTargetPropertyPath = 'keyA'; - tree.add({ action: null, propertyName: 'keyA', targetPropertyPath: parentTargetPropertyPath }); - tree.add({ action: 'keyA', propertyName: 'keyA1' }, parentTargetPropertyPath); - - const nodeKeyA: SchemaNode = { - data: { targetPropertyPath: 'keyA', propertyName: 'keyA', action: null, kind: NodeKind.Property }, - parent: null, - children: [] - }; - - const nodeKeyA1: SchemaNode = { - data: { targetPropertyPath: 'keyA.keyA1', propertyName: 'keyA1', action: 'keyA', kind: NodeKind.ActionString }, - parent: null, - children: [] - }; - - // KeyA - expect(tree.root.children[0].data.action).toEqual(nodeKeyA.data.action); - expect(tree.root.children[0].data.targetPropertyPath).toEqual(nodeKeyA.data.targetPropertyPath); - expect(tree.root.children[0].data.propertyName).toEqual(nodeKeyA.data.propertyName); - expect(tree.root.children[0].data.kind).toEqual(nodeKeyA.data.kind); - - // KeyA1 - const keyA1 = tree.root.children[0].children[0]; - expect(keyA1.data.action).toEqual(nodeKeyA1.data.action); - expect(keyA1.data.targetPropertyPath).toEqual(nodeKeyA1.data.targetPropertyPath); - expect(keyA1.data.propertyName).toEqual(nodeKeyA1.data.propertyName); - expect(keyA1.data.kind).toEqual(nodeKeyA1.data.kind); + expect(tree.root.children[0].parent!.data.propertyName).toEqual(root.data.propertyName); + }); + + it('should add a node under another node', () => { + interface Target { + keyA: string; + } + const tree = new MophismSchemaTree(); + const parentTargetPropertyPath = 'keyA'; + tree.add({ action: null, propertyName: 'keyA', targetPropertyPath: parentTargetPropertyPath }); + tree.add({ action: 'keyA', propertyName: 'keyA1' }, parentTargetPropertyPath); + + const nodeKeyA: SchemaNode = { + data: { targetPropertyPath: 'keyA', propertyName: 'keyA', action: null, kind: NodeKind.Property }, + parent: null, + children: [] + }; + + const nodeKeyA1: SchemaNode = { + data: { targetPropertyPath: 'keyA.keyA1', propertyName: 'keyA1', action: 'keyA', kind: NodeKind.ActionString }, + parent: null, + children: [] + }; + + // KeyA + expect(tree.root.children[0].data.action).toEqual(nodeKeyA.data.action); + expect(tree.root.children[0].data.targetPropertyPath).toEqual(nodeKeyA.data.targetPropertyPath); + expect(tree.root.children[0].data.propertyName).toEqual(nodeKeyA.data.propertyName); + expect(tree.root.children[0].data.kind).toEqual(nodeKeyA.data.kind); + + // KeyA1 + const keyA1 = tree.root.children[0].children[0]; + expect(keyA1.data.action).toEqual(nodeKeyA1.data.action); + expect(keyA1.data.targetPropertyPath).toEqual(nodeKeyA1.data.targetPropertyPath); + expect(keyA1.data.propertyName).toEqual(nodeKeyA1.data.propertyName); + expect(keyA1.data.kind).toEqual(nodeKeyA1.data.kind); + }); }); describe('Parser', () => { diff --git a/src/classObjects.spec.ts b/src/classObjects.spec.ts index 64a56e3..4d3fe8c 100644 --- a/src/classObjects.spec.ts +++ b/src/classObjects.spec.ts @@ -1,64 +1,306 @@ -import { toClassObject, morph } from './morphism'; - -describe('Morphism', () => { - describe('Class Objects', () => { - describe('Decorators - Function Decorator', () => { - const schema = { foo: 'bar' }; - interface ITarget { - foo: string | null; - } - class Target implements ITarget { - foo = null; - } +import Morphism, { toClassObject, morph, StrictSchema, morphism } from './morphism'; +import { User } from './utils-test'; + +describe('Class Objects', () => { + describe('Class Type Mapping', function() { + beforeEach(() => { + Morphism.deleteMapper(User); + }); + + it('should throw an exception when setting a mapper with a falsy schema', function() { + expect(() => { + Morphism.setMapper(User, null as any); + }).toThrow(); + }); + + it('should use the constructor default value if source value is undefined', function() { + let sourceData: any = { + firstName: 'John', + lastName: 'Smith', + type: undefined // <== this field should fallback to the type constructor default value + }; + let desiredResult = new User('John', 'Smith'); + let mapper = Morphism.register(User); + expect(desiredResult.type).toEqual('User'); + expect(mapper([sourceData])[0]).toEqual(desiredResult); + }); + + it('should allow straight mapping from a Type without a schema', () => { + let userName = 'user-name'; + let user = Morphism(null as any, { firstName: userName }, User); + expect(user).toEqual(new User(userName)); + }); + + it('should allow straight mapping from a Type with a schema', () => { + let dataSource = { + userName: 'a-user-name' + }; + let schema = { + firstName: 'userName' + }; + let user = Morphism(schema, dataSource, User); + expect(user).toEqual(new User(dataSource.userName)); + }); + + it('should pass created object context for complex interractions within object', function() { + let dataSource = { + groups: ['main', 'test'] + }; + + let triggered = false; + let trigger = (_user: User, _group: any) => { + triggered = true; + }; - class Service { - @toClassObject(schema, Target) - fetch(source: any) { - return Promise.resolve(source); + let schema = { + groups: (object: any, _items: any, constructed: User) => { + if (object.groups) { + for (let group of object.groups) { + constructed.addToGroup(group, trigger); + } + } } - @toClassObject(schema, Target) - fetch2(source: any) { - return source; + }; + let user = Morphism(schema, dataSource, User); + + let expectedUser = new User(); + expectedUser.groups = dataSource.groups; + expect(user).toEqual(expectedUser); + expect(user.firstName).toEqual(expectedUser.firstName); + + expect(triggered).toEqual(true); + }); + + it('should return undefined if undefined is given to map without doing any processing', function() { + Morphism.register(User, { a: 'firstName' }); + expect(Morphism.map(User, undefined)).toEqual(undefined); + }); + + it('should override the default value if source value is defined', function() { + let sourceData = { + phoneNumber: null + }; + + let mapper = Morphism.register(User, {}); + + let result = mapper([sourceData])[0]; + expect(new User().phoneNumber).toEqual(undefined); + expect(result.phoneNumber).toEqual(null); + }); + + it('should provide an Object as result when Morphism is applied on a typed Object', function() { + let mock = { + number: '12345' + }; + + let mapper = Morphism.register(User, { phoneNumber: 'number' }); + let result = mapper(mock); + expect(result.phoneNumber).toEqual(mock.number); + expect(result instanceof User).toEqual(true); + }); + + it('should provide an Object as result when Morphism is applied on a typed Object usin .map', function() { + let mock = { + number: '12345' + }; + + Morphism.register(User, { phoneNumber: 'number' }); + let result = Morphism.map(User, mock); + expect(result.phoneNumber).toEqual(mock.number); + expect(result instanceof User).toEqual(true); + }); + + it('should provide a List of Objects as result when Morphism is applied on a list', function() { + let mock = { + number: '12345' + }; + + Morphism.register(User, { phoneNumber: 'number' }); + let result = Morphism.map(User, [mock]); + expect(result[0].phoneNumber).toEqual(mock.number); + expect(result[0] instanceof User).toBe(true); + }); + + it('should fallback to constructor default value and ignore function when path value is undefined', function() { + let mock = { + lastname: 'user-lastname' + }; + let schema = { + type: { + path: 'unreachable.path', + fn: (value: any) => value } - @toClassObject(schema, Target) - async fetch3(source: any) { - return await (async () => source)(); + }; + + Morphism.register(User, schema); + expect(new User().type).toEqual('User'); + + let result = Morphism.map(User, mock); + expect(result.type).toEqual('User'); + }); + }); + describe('Projection', () => { + it('should allow to map property one to one when using `Morphism.map(Type,object)` without registration', function() { + let mock = { field: 'value' }; + class Target { + field: any; + constructor(field: any) { + this.field = field; } - @toClassObject(schema, Target) - fetchMultiple(source: any) { - return [source, source]; + } + const result = Morphism.map(Target, mock); + expect(result).toEqual(new Target('value')); + }); + + it('should allow to map property one to one when using `Morphism.map(Type,data)` without registration', function() { + let mocks = [{ field: 'value' }, { field: 'value' }, { field: 'value' }]; + class Target { + field: any; + constructor(field: any) { + this.field = field; } - @morph(schema, Target) - withMorphDecorator(source: any) { - return source; + } + const results = Morphism.map(Target, mocks); + results.forEach((res: any) => { + expect(res).toEqual(new Target('value')); + }); + }); + + it('should allow to use Morphism.map as an iteratee first function', function() { + let mocks = [{ field: 'value' }, { field: 'value' }, { field: 'value' }]; + class Target { + field: any; + constructor(field: any) { + this.field = field; } } + const results = mocks.map(Morphism.map(Target)); + results.forEach(res => { + expect(res).toEqual(new Target('value')); + }); + }); - const service = new Service(); - interface ISource { - bar: string; + it('should allow to use mapper from `Morphism.map(Type, undefined)` as an iteratee first function', function() { + let mocks = [{ field: 'value' }, { field: 'value' }, { field: 'value' }]; + class Target { + field: any; + constructor(field: any) { + this.field = field; + } } + const mapper = Morphism.map(Target); + const results = mocks.map(mapper); + results.forEach(res => { + expect(res).toEqual(new Target('value')); + expect(res.field).toBeDefined(); + }); + }); - const source: ISource = { - bar: 'value' + it('should accept deep nested actions', () => { + interface Source { + keyA: string; + } + const sample: Source = { + keyA: 'value' }; - it('should create a Class Object when a Promise is used', function() { - service.fetch(source).then((result: any) => expect(result instanceof Target).toBe(true)); - }); - it('should support a function returning an array of Class Object or a Class Object', () => { - expect(service.fetch2(source) instanceof Target).toBe(true); - }); - it('should support an aync function', async function() { - const res = await service.fetch3(source); - expect(res instanceof Target).toBe(true); - }); - it('should support a function returning an array of Class Objects', function() { - service.fetchMultiple(source).forEach(item => expect(item instanceof Target).toBe(true)); - }); - it('should allow to morph with Morph decorator to Class Object', function() { - expect(service.withMorphDecorator(source) instanceof Target).toBe(true); - }); + interface Target { + keyA: { keyA1: string }; + } + + const schema: StrictSchema = { keyA: { keyA1: source => source.keyA } }; + + const target = morphism(schema, sample); + expect(target).toEqual({ keyA: { keyA1: 'value' } }); + }); + + it('should accept deep nested actions into array', () => { + interface Source { + keySource: string; + } + const sample: Source = { + keySource: 'value' + }; + + interface Target { + keyA: { + keyA1: [ + { + keyA11: string; + keyA12: number; + } + ]; + keyA2: string; + }; + } + const schema: StrictSchema = { + keyA: { + keyA1: [{ keyA11: 'keySource', keyA12: 'keySource' }], + keyA2: 'keySource' + } + }; + + const target = morphism(schema, sample); + + expect(target).toEqual({ keyA: { keyA1: [{ keyA11: 'value', keyA12: 'value' }], keyA2: 'value' } }); + }); + }); + describe('Class Decorators', () => { + const schema = { foo: 'bar' }; + interface ITarget { + foo: string | null; + } + class Target implements ITarget { + foo = null; + } + + class Service { + @toClassObject(schema, Target) + fetch(source: any) { + return Promise.resolve(source); + } + @toClassObject(schema, Target) + fetch2(source: any) { + return source; + } + @toClassObject(schema, Target) + async fetch3(source: any) { + return await (async () => source)(); + } + @toClassObject(schema, Target) + fetchMultiple(source: any) { + return [source, source]; + } + @morph(schema, Target) + withMorphDecorator(source: any) { + return source; + } + } + + const service = new Service(); + interface ISource { + bar: string; + } + + const source: ISource = { + bar: 'value' + }; + + it('should create a Class Object when a Promise is used', function() { + service.fetch(source).then((result: any) => expect(result instanceof Target).toBe(true)); + }); + it('should support a function returning an array of Class Object or a Class Object', () => { + expect(service.fetch2(source) instanceof Target).toBe(true); + }); + it('should support an aync function', async function() { + const res = await service.fetch3(source); + expect(res instanceof Target).toBe(true); + }); + it('should support a function returning an array of Class Objects', function() { + service.fetchMultiple(source).forEach(item => expect(item instanceof Target).toBe(true)); + }); + it('should allow to morph with Morph decorator to Class Object', function() { + expect(service.withMorphDecorator(source) instanceof Target).toBe(true); }); }); }); diff --git a/src/jsObjects.spec.ts b/src/jsObjects.spec.ts index 98aa72e..c40155f 100644 --- a/src/jsObjects.spec.ts +++ b/src/jsObjects.spec.ts @@ -1,7 +1,7 @@ import Morphism, { toJSObject, morph } from './morphism'; -describe('Morphism', () => { - describe('Javascript Objects', () => { +describe('Javascript Objects', () => { + describe('Base', () => { it('should morph an empty Object to an empty Object || m({}, {}) => {}', function() { expect(Morphism({}, {})).toEqual({}); }); @@ -30,64 +30,64 @@ describe('Morphism', () => { expect(res).toEqual({ target: 'value' }); }); }); + }); - describe('Decorators - Function Decorator', () => { - const schema = { foo: 'bar' }; + describe('Decorators - Function Decorator', () => { + const schema = { foo: 'bar' }; - class Service { - @toJSObject(schema) - fetch(source: any) { - return Promise.resolve(source); - } - @toJSObject(schema) - fetch2(source: any) { - return source; - } - @toJSObject(schema) - async fetch3(source: any) { - return await (async () => source)(); - } - @morph(schema) - withMorphDecorator(source: any) { - return source; - } - @toJSObject(schema) - fetchFail(source: any) { - return Promise.reject(source); - } + class Service { + @toJSObject(schema) + fetch(source: any) { + return Promise.resolve(source); } - - const service = new Service(); - interface ISource { - bar: string; + @toJSObject(schema) + fetch2(source: any) { + return source; } - interface ITarget { - foo: string; + @toJSObject(schema) + async fetch3(source: any) { + return await (async () => source)(); } - const source: ISource = { - bar: 'value' - }; - const expected: ITarget = { - foo: 'value' - }; + @morph(schema) + withMorphDecorator(source: any) { + return source; + } + @toJSObject(schema) + fetchFail(source: any) { + return Promise.reject(source); + } + } - it('should support a function returning a promise', function() { - service.fetch(source).then((result: any) => expect(result).toEqual(expected)); - }); - it('should support a function returning an array or an object', () => { - expect(service.fetch2(source)).toEqual(expected); - }); - it('should support an aync function', async function() { - const res = await service.fetch3(source); - expect(res).toEqual(expected); - }); - it('should allow to morph with Morph decorator to Class Object', function() { - expect(service.withMorphDecorator(source)).toEqual(expected); - }); - it('should not swallow error when promise fails', function() { - service.fetchFail(source).catch(data => { - expect(data).toEqual(source); - }); + const service = new Service(); + interface ISource { + bar: string; + } + interface ITarget { + foo: string; + } + const source: ISource = { + bar: 'value' + }; + const expected: ITarget = { + foo: 'value' + }; + + it('should support a function returning a promise', () => { + return service.fetch(source).then((result: any) => expect(result).toEqual(expected)); + }); + it('should support a function returning an array or an object', () => { + expect(service.fetch2(source)).toEqual(expected); + }); + it('should support an aync function', async function() { + const res = await service.fetch3(source); + expect(res).toEqual(expected); + }); + it('should allow to morph with Morph decorator to JS Object', function() { + expect(service.withMorphDecorator(source)).toEqual(expected); + }); + it('should not swallow error when promise fails', () => { + return service.fetchFail(source).catch(data => { + expect(data).toEqual(source); }); }); }); diff --git a/src/morphism.spec.ts b/src/morphism.spec.ts index 92b3c71..36f8c1d 100644 --- a/src/morphism.spec.ts +++ b/src/morphism.spec.ts @@ -1,54 +1,7 @@ -import Morphism, { morphism, StrictSchema } from './morphism'; - -class User { - firstName?: string; - lastName?: string; - phoneNumber?: string; - type?: string; - - groups: Array = new Array(); - - constructor(firstName?: string, lastName?: string, phoneNumber?: string) { - this.firstName = firstName; - this.lastName = lastName; - this.phoneNumber = phoneNumber; - - this.type = 'User'; // Use to test default value scenario - } - - /** - * Use to test runtime access to the created object context - * @param {} group - * @param {} externalTrigger - */ - addToGroup(group: any, externalTrigger: any) { - this.groups.push(group); - externalTrigger(this, group); - } -} +import Morphism, { StrictSchema, morphism, Schema } from './morphism'; +import { User, MockData } from './utils-test'; describe('Morphism', () => { - interface MockData { - firstName: string; - lastName: string; - age: number; - address: { - streetAddress: string; - city: string; - state: string; - postalCode: string; - }; - phoneNumber: [ - { - type: string; - number: string; - }, - { - type: string; - number: string; - } - ]; - } const dataToCrunch: MockData[] = [ { firstName: 'John', @@ -77,6 +30,71 @@ describe('Morphism', () => { Morphism.register(User); }); + describe('Currying Function overload', () => { + it('Should return a collection of objects when an array is provided as source', () => { + const schema = { foo: 'bar' }; + const res = morphism(schema, [{ bar: 'test' }]); + expect(res.map).toBeDefined(); + expect(res[0].foo).toEqual('test'); + }); + it('Should return a single object matching the schema structure when an object is provided as source', () => { + const schema = { foo: 'bar' }; + const res = morphism(schema, { bar: 'test' }); + + expect(res.foo).toEqual('test'); + }); + + it('Should return a Mapper which outputs a Class Object when a Class Type is specified and no items', () => { + class Foo { + foo: string; + } + const schema = { foo: 'bar' }; + const source = { bar: 'value' }; + const mapper = morphism(schema, null, Foo); + expect(mapper(source).foo).toEqual('value'); + expect(mapper([source][0]).foo).toEqual('value'); + }); + + it('Should return a Mapper which outputs a Typed Object from the generic provided', () => { + interface IFoo { + foo: string; + } + const schema: Schema = { foo: 'bar' }; + const source = { bar: 'value' }; + const mapper = morphism(schema); + + expect(mapper(source).foo).toEqual('value'); + expect(mapper([source][0]).foo).toEqual('value'); + }); + + it('Should do a straight mapping with an Interface provided', () => { + interface Destination { + foo: string; + bar: string; + qux: string; + } + interface Source { + bar: string; + } + const schema: StrictSchema = { + foo: 'bar', + bar: 'bar', + qux: elem => elem.bar + }; + const source = { bar: 'value' }; + + const target = morphism(schema, source); + const targets = morphism(schema, [source]); + const singleTarget = targets.shift(); + + expect(target.foo).toEqual('value'); + expect(singleTarget).toBeDefined(); + if (singleTarget) { + expect(singleTarget.foo).toEqual('value'); + } + }); + }); + describe('Plain Objects', function() { it('should export Morphism function curried function', function() { expect(typeof Morphism).toEqual('function'); @@ -91,12 +109,6 @@ describe('Morphism', () => { expect(Morphism({}, {})).toEqual({}); }); - it('should throw an exception when setting a mapper with a falsy schema', function() { - expect(() => { - Morphism.setMapper(User, null as any); - }).toThrow(); - }); - it('should throw an exception when trying to access a path from an undefined object', function() { Morphism.setMapper(User, { fieldWillThrow: { @@ -404,297 +416,4 @@ describe('Morphism', () => { expect(results[0]).toEqual(desiredResult); }); }); - - describe('Mappers Registry', function() { - it('should throw an exception when using Registration function without parameters', function() { - expect(() => Morphism.register(null as any, null)).toThrow(); - }); - - it('should throw an exception when trying to register a mapper type more than once', function() { - expect(() => { - Morphism.register(User, {}); - }).toThrow(); - }); - - it('should return the stored mapper after a registration', function() { - let schema = { - phoneNumber: 'phoneNumber[0].number' - }; - let mapper = Morphism.setMapper(User, schema); - let mapperSaved = Morphism.getMapper(User); - expect(typeof mapper).toEqual('function'); - expect(typeof mapperSaved).toEqual('function'); - expect(mapperSaved).toEqual(mapper); - }); - - it('should get a stored mapper after a registration', function() { - Morphism.setMapper(User, {}); - expect(typeof Morphism.getMapper(User)).toEqual('function'); - }); - - it('should allow to map data using a registered mapper', function() { - let schema = { - phoneNumber: 'phoneNumber[0].number' - }; - Morphism.setMapper(User, schema); - let desiredResult = new User('John', 'Smith', '212 555-1234'); - expect(Morphism.map(User, dataToCrunch)).toBeTruthy(); - expect(Morphism.map(User, dataToCrunch)[0]).toEqual(desiredResult); - }); - - it('should allow to map data using a mapper updated schema', function() { - let schema = { - phoneNumber: 'phoneNumber[0].number' - }; - let mapper = Morphism.setMapper(User, schema); - let desiredResult = new User('John', 'Smith', '212 555-1234'); - expect(mapper(dataToCrunch)[0]).toEqual(desiredResult); - }); - - it('should throw an exception when trying to set an non-registered type', function() { - Morphism.deleteMapper(User); - expect(() => { - Morphism.setMapper(User, {}); - }).toThrow(); - }); - }); - - describe('Class Type Mapping', function() { - beforeEach(() => { - Morphism.deleteMapper(User); - }); - - it('should use the constructor default value if source value is undefined', function() { - let sourceData: any = { - firstName: 'John', - lastName: 'Smith', - type: undefined // <== this field should fallback to the type constructor default value - }; - let desiredResult = new User('John', 'Smith'); - let mapper = Morphism.register(User); - expect(desiredResult.type).toEqual('User'); - expect(mapper([sourceData])[0]).toEqual(desiredResult); - }); - - it('should allow straight mapping from a Type without a schema', () => { - let userName = 'user-name'; - let user = Morphism(null as any, { firstName: userName }, User); - expect(user).toEqual(new User(userName)); - }); - - it('should allow straight mapping from a Type with a schema', () => { - let dataSource = { - userName: 'a-user-name' - }; - let schema = { - firstName: 'userName' - }; - let user = Morphism(schema, dataSource, User); - expect(user).toEqual(new User(dataSource.userName)); - }); - - it('should pass created object context for complex interractions within object', function() { - let dataSource = { - groups: ['main', 'test'] - }; - - let triggered = false; - let trigger = (_user: User, _group: any) => { - triggered = true; - }; - - let schema = { - groups: (object: any, _items: any, constructed: User) => { - if (object.groups) { - for (let group of object.groups) { - constructed.addToGroup(group, trigger); - } - } - } - }; - let user = Morphism(schema, dataSource, User); - - let expectedUser = new User(); - expectedUser.groups = dataSource.groups; - expect(user).toEqual(expectedUser); - expect(user.firstName).toEqual(expectedUser.firstName); - - expect(triggered).toEqual(true); - }); - - it('should return undefined if undefined is given to map without doing any processing', function() { - Morphism.register(User, { a: 'firstName' }); - expect(Morphism.map(User, undefined)).toEqual(undefined); - }); - - it('should override the default value if source value is defined', function() { - let sourceData = { - phoneNumber: null - }; - - let mapper = Morphism.register(User, {}); - - let result = mapper([sourceData])[0]; - expect(new User().phoneNumber).toEqual(undefined); - expect(result.phoneNumber).toEqual(null); - }); - - it('should provide an Object as result when Morphism is applied on a typed Object', function() { - let mock = { - number: '12345' - }; - - let mapper = Morphism.register(User, { phoneNumber: 'number' }); - let result = mapper(mock); - expect(result.phoneNumber).toEqual(mock.number); - expect(result instanceof User).toEqual(true); - }); - - it('should provide an Object as result when Morphism is applied on a typed Object usin .map', function() { - let mock = { - number: '12345' - }; - - Morphism.register(User, { phoneNumber: 'number' }); - let result = Morphism.map(User, mock); - expect(result.phoneNumber).toEqual(mock.number); - expect(result instanceof User).toEqual(true); - }); - - it('should provide a List of Objects as result when Morphism is applied on a list', function() { - let mock = { - number: '12345' - }; - - Morphism.register(User, { phoneNumber: 'number' }); - let result = Morphism.map(User, [mock]); - expect(result[0].phoneNumber).toEqual(mock.number); - expect(result[0] instanceof User).toBe(true); - }); - - it('should fallback to constructor default value and ignore function when path value is undefined', function() { - let mock = { - lastname: 'user-lastname' - }; - let schema = { - type: { - path: 'unreachable.path', - fn: (value: any) => value - } - }; - - Morphism.register(User, schema); - expect(new User().type).toEqual('User'); - - let result = Morphism.map(User, mock); - expect(result.type).toEqual('User'); - }); - - describe('Projection', () => { - it('should allow to map property one to one when using `Morphism.map(Type,object)` without registration', function() { - let mock = { field: 'value' }; - class Target { - field: any; - constructor(field: any) { - this.field = field; - } - } - const result = Morphism.map(Target, mock); - expect(result).toEqual(new Target('value')); - }); - - it('should allow to map property one to one when using `Morphism.map(Type,data)` without registration', function() { - let mocks = [{ field: 'value' }, { field: 'value' }, { field: 'value' }]; - class Target { - field: any; - constructor(field: any) { - this.field = field; - } - } - const results = Morphism.map(Target, mocks); - results.forEach((res: any) => { - expect(res).toEqual(new Target('value')); - }); - }); - - it('should allow to use Morphism.map as an iteratee first function', function() { - let mocks = [{ field: 'value' }, { field: 'value' }, { field: 'value' }]; - class Target { - field: any; - constructor(field: any) { - this.field = field; - } - } - const results = mocks.map(Morphism.map(Target)); - results.forEach(res => { - expect(res).toEqual(new Target('value')); - }); - }); - - it('should allow to use mapper from `Morphism.map(Type, undefined)` as an iteratee first function', function() { - let mocks = [{ field: 'value' }, { field: 'value' }, { field: 'value' }]; - class Target { - field: any; - constructor(field: any) { - this.field = field; - } - } - const mapper = Morphism.map(Target); - const results = mocks.map(mapper); - results.forEach(res => { - expect(res).toEqual(new Target('value')); - expect(res.field).toBeDefined(); - }); - }); - - it('should accept deep nested actions', () => { - interface Source { - keyA: string; - } - const sample: Source = { - keyA: 'value' - }; - - interface Target { - keyA: { keyA1: string }; - } - - const schema: StrictSchema = { keyA: { keyA1: source => source.keyA } }; - - const target = morphism(schema, sample); - expect(target).toEqual({ keyA: { keyA1: 'value' } }); - }); - - it('should accept deep nested actions into array', () => { - interface Source { - keySource: string; - } - const sample: Source = { - keySource: 'value' - }; - - interface Target { - keyA: { - keyA1: [ - { - keyA11: string; - keyA12: number; - } - ]; - keyA2: string; - }; - } - const schema: StrictSchema = { - keyA: { - keyA1: [{ keyA11: 'keySource', keyA12: 'keySource' }], - keyA2: 'keySource' - } - }; - - const target = morphism(schema, sample); - - expect(target).toEqual({ keyA: { keyA1: [{ keyA11: 'value', keyA12: 'value' }], keyA2: 'value' } }); - }); - }); - }); }); diff --git a/src/typescript.spec.ts b/src/typescript.spec.ts index d52b95b..6a1e59b 100644 --- a/src/typescript.spec.ts +++ b/src/typescript.spec.ts @@ -1,7 +1,46 @@ -import { morphism, StrictSchema, Schema } from './morphism'; +import Morphism, { morphism, StrictSchema, Schema } from './morphism'; + +describe('Typescript', () => { + describe('Registry Type Checking', () => { + it('Should return a Mapper when using Register', () => { + class Foo { + foo: string; + } + const schema = { foo: 'bar' }; + const source = { bar: 'value' }; + const mapper = Morphism.register(Foo, schema); + + expect(mapper(source).foo).toEqual('value'); + expect(mapper([source][0]).foo).toEqual('value'); + }); + }); + + describe('Schema Type Checking', () => { + it('Should allow to type the Schema', () => { + interface IFoo { + foo: string; + bar: number; + } + const schema: Schema = { foo: 'qux' }; + const source = { qux: 'foo' }; + const target = morphism(schema, source); + + expect(target.foo).toEqual(source.qux); + }); + + it('Should allow to use a strict Schema', () => { + interface IFoo { + foo: string; + bar: number; + } + const schema: StrictSchema = { foo: 'qux', bar: () => 1 }; + const source = { qux: 'foo' }; + const target = morphism(schema, source); + + expect(target.foo).toEqual(source.qux); + expect(target.bar).toEqual(1); + }); -describe('Morphism', () => { - describe('Typescript', () => { it('should accept 2 generic parameters on StrictSchema', () => { interface Source { inputA: string; @@ -67,16 +106,13 @@ describe('Morphism', () => { morphism(schema, [{}]); }); - xit('should fail with typescript', () => { + it('should not fail with typescript', () => { interface S { s1: string; } interface D { d1: string; } - const schema: StrictSchema = { - d1: 's1' - }; interface Source { boring_api_field: number; @@ -115,9 +151,7 @@ describe('Morphism', () => { if (itemE) { itemE.namingIsHard; } - }); - it('', () => { interface S { _a: string; } @@ -127,8 +161,6 @@ describe('Morphism', () => { morphism({ a: ({ _a }) => _a.toString() }); morphism({ a: ({ _a }) => _a.toString() }); - // morphism({ a: ({ b }) => b }, { _a: 'value' }); - // morphism({ a: ({ b }) => b }); }); }); }); diff --git a/src/typings.spec.ts b/src/typings.spec.ts deleted file mode 100644 index 0ac5608..0000000 --- a/src/typings.spec.ts +++ /dev/null @@ -1,109 +0,0 @@ -import Morphism, { morphism, Schema, StrictSchema } from './morphism'; - -describe('Morphism', () => { - describe('Currying Function overload', () => { - it('Should return a collection of objects when an array is provided as source', () => { - const schema = { foo: 'bar' }; - const res = morphism(schema, [{ bar: 'test' }]); - expect(res.map).toBeDefined(); - expect(res[0].foo).toEqual('test'); - }); - it('Should return a single object matching the schema structure when an object is provided as source', () => { - const schema = { foo: 'bar' }; - const res = morphism(schema, { bar: 'test' }); - - expect(res.foo).toEqual('test'); - }); - - it('Should return a Mapper which outputs a Class Object when a Class Type is specified and no items', () => { - class Foo { - foo: string; - } - const schema = { foo: 'bar' }; - const source = { bar: 'value' }; - const mapper = morphism(schema, null, Foo); - expect(mapper(source).foo).toEqual('value'); - expect(mapper([source][0]).foo).toEqual('value'); - }); - - it('Should return a Mapper which outputs a Typed Object from the generic provided', () => { - interface IFoo { - foo: string; - } - const schema: Schema = { foo: 'bar' }; - const source = { bar: 'value' }; - const mapper = morphism(schema); - - expect(mapper(source).foo).toEqual('value'); - expect(mapper([source][0]).foo).toEqual('value'); - }); - - it('Should do a straight mapping with an Interface provided', () => { - interface Destination { - foo: string; - bar: string; - qux: string; - } - interface Source { - bar: string; - } - const schema: StrictSchema = { - foo: 'bar', - bar: 'bar', - qux: elem => elem.bar - }; - const source = { bar: 'value' }; - - const target = morphism(schema, source); - const targets = morphism(schema, [source]); - const singleTarget = targets.shift(); - - expect(target.foo).toEqual('value'); - expect(singleTarget).toBeDefined(); - if (singleTarget) { - expect(singleTarget.foo).toEqual('value'); - } - }); - }); - - describe('Registry Type Checking', () => { - it('Should return a Mapper when using Register', () => { - class Foo { - foo: string; - } - const schema = { foo: 'bar' }; - const source = { bar: 'value' }; - const mapper = Morphism.register(Foo, schema); - - expect(mapper(source).foo).toEqual('value'); - expect(mapper([source][0]).foo).toEqual('value'); - }); - }); - - describe('Schema Type Checking', () => { - it('Should allow to type the Schema', () => { - interface IFoo { - foo: string; - bar: number; - } - const schema: Schema = { foo: 'qux' }; - const source = { qux: 'foo' }; - const target = morphism(schema, source); - - expect(target.foo).toEqual(source.qux); - }); - - it('Should allow to use a strict Schema', () => { - interface IFoo { - foo: string; - bar: number; - } - const schema: StrictSchema = { foo: 'qux', bar: () => 1 }; - const source = { qux: 'foo' }; - const target = morphism(schema, source); - - expect(target.foo).toEqual(source.qux); - expect(target.bar).toEqual(1); - }); - }); -}); diff --git a/src/utils-test.ts b/src/utils-test.ts new file mode 100644 index 0000000..e0a65c2 --- /dev/null +++ b/src/utils-test.ts @@ -0,0 +1,48 @@ +export class User { + firstName?: string; + lastName?: string; + phoneNumber?: string; + type?: string; + + groups: Array = new Array(); + + constructor(firstName?: string, lastName?: string, phoneNumber?: string) { + this.firstName = firstName; + this.lastName = lastName; + this.phoneNumber = phoneNumber; + + this.type = 'User'; // Use to test default value scenario + } + + /** + * Use to test runtime access to the created object context + * @param {} group + * @param {} externalTrigger + */ + addToGroup(group: any, externalTrigger: any) { + this.groups.push(group); + externalTrigger(this, group); + } +} + +export interface MockData { + firstName: string; + lastName: string; + age: number; + address: { + streetAddress: string; + city: string; + state: string; + postalCode: string; + }; + phoneNumber: [ + { + type: string; + number: string; + }, + { + type: string; + number: string; + } + ]; +} From 6f4f7b085baf79e4e9ce9df1948318f9fee2292d Mon Sep 17 00:00:00 2001 From: Yann Renaudin Date: Mon, 18 Mar 2019 17:29:31 -0400 Subject: [PATCH 30/30] fix: typescript screaming when using ActionAggregator - more UTs --- src/classObjects.spec.ts | 49 ------------------- src/morphism.spec.ts | 101 +++++++++++++++++++++++++++++++++++++++ src/types.ts | 15 +++--- 3 files changed, 109 insertions(+), 56 deletions(-) diff --git a/src/classObjects.spec.ts b/src/classObjects.spec.ts index 4d3fe8c..cf7a39c 100644 --- a/src/classObjects.spec.ts +++ b/src/classObjects.spec.ts @@ -195,55 +195,6 @@ describe('Class Objects', () => { expect(res.field).toBeDefined(); }); }); - - it('should accept deep nested actions', () => { - interface Source { - keyA: string; - } - const sample: Source = { - keyA: 'value' - }; - - interface Target { - keyA: { keyA1: string }; - } - - const schema: StrictSchema = { keyA: { keyA1: source => source.keyA } }; - - const target = morphism(schema, sample); - expect(target).toEqual({ keyA: { keyA1: 'value' } }); - }); - - it('should accept deep nested actions into array', () => { - interface Source { - keySource: string; - } - const sample: Source = { - keySource: 'value' - }; - - interface Target { - keyA: { - keyA1: [ - { - keyA11: string; - keyA12: number; - } - ]; - keyA2: string; - }; - } - const schema: StrictSchema = { - keyA: { - keyA1: [{ keyA11: 'keySource', keyA12: 'keySource' }], - keyA2: 'keySource' - } - }; - - const target = morphism(schema, sample); - - expect(target).toEqual({ keyA: { keyA1: [{ keyA11: 'value', keyA12: 'value' }], keyA2: 'value' } }); - }); }); describe('Class Decorators', () => { const schema = { foo: 'bar' }; diff --git a/src/morphism.spec.ts b/src/morphism.spec.ts index 36f8c1d..f633200 100644 --- a/src/morphism.spec.ts +++ b/src/morphism.spec.ts @@ -1,5 +1,6 @@ import Morphism, { StrictSchema, morphism, Schema } from './morphism'; import { User, MockData } from './utils-test'; +import { ActionSelector, ActionAggregator } from './types'; describe('Morphism', () => { const dataToCrunch: MockData[] = [ @@ -415,5 +416,105 @@ describe('Morphism', () => { let results = Morphism(schema, dataToCrunch); expect(results[0]).toEqual(desiredResult); }); + + it('should accept deep nested actions', () => { + interface Source { + keyA: string; + } + const sample: Source = { + keyA: 'value' + }; + + interface Target { + keyA: { keyA1: string }; + } + + const schema: StrictSchema = { keyA: { keyA1: source => source.keyA } }; + + const target = morphism(schema, sample); + expect(target).toEqual({ keyA: { keyA1: 'value' } }); + }); + + it('should accept deep nested actions into array', () => { + interface Source { + keySource: string; + } + const sample: Source = { + keySource: 'value' + }; + + interface Target { + keyA: { + keyA1: [ + { + keyA11: string; + keyA12: number; + } + ]; + keyA2: string; + }; + } + const schema: StrictSchema = { + keyA: { + keyA1: [{ keyA11: 'keySource', keyA12: 'keySource' }], + keyA2: 'keySource' + } + }; + + const target = morphism(schema, sample); + + expect(target).toEqual({ keyA: { keyA1: [{ keyA11: 'value', keyA12: 'value' }], keyA2: 'value' } }); + }); + + it('should accept a selector action in deep nested schema property', () => { + interface Source { + keySource: string; + keySource1: string; + } + const sample: Source = { + keySource: 'value', + keySource1: 'value1' + }; + + interface Target { + keyA: { + keyA1: [ + { + keyA11: string; + keyA12: number; + } + ]; + keyA2: string; + }; + } + const selector: ActionSelector = { + path: 'keySource', + fn: () => 'value-test' + }; + const aggregator: ActionAggregator = ['keySource', 'keySource1']; + const schema: StrictSchema = { + keyA: { + keyA1: [{ keyA11: aggregator, keyA12: selector }], + keyA2: 'keySource' + } + }; + + const target = morphism(schema, sample); + + expect(target).toEqual({ + keyA: { + keyA1: [ + { + keyA11: { + keySource: 'value', + keySource1: 'value1' + }, + keyA12: 'value-test' + } + ], + keyA2: 'value' + } + }); + }); }); }); diff --git a/src/types.ts b/src/types.ts index 8e95b4b..90044ab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -34,7 +34,7 @@ export type StrictSchema = { [destinationProperty in keyof Target]: | ActionString | ActionFunction - | ActionAggregator + | ActionAggregator | ActionSelector | StrictSchema }; @@ -43,7 +43,7 @@ export type Schema = { [destinationProperty in keyof Target]?: | ActionString | ActionFunction - | ActionAggregator + | ActionAggregator | ActionSelector | Schema }; @@ -106,7 +106,8 @@ export interface ActionFunction { * ``` * */ -export type ActionString = keyof T; +export type ActionString = string; // TODO: ActionString should support string and string[] for deep properties + /** * An Array of String that allows to perform a function over source property * @@ -125,7 +126,7 @@ export type ActionString = keyof T; * //=> { fooAndBar: { foo: 'foo', bar: 'bar' } } * ``` */ -export type ActionAggregator = string[]; +export type ActionAggregator = T extends object ? (keyof T)[] | string[] : string[]; /** * An Object that allows to perform a function over a source property's value * @@ -149,10 +150,10 @@ export type ActionAggregator = string[]; *``` * */ -export type ActionSelector = { - path: string | string[]; +export interface ActionSelector { + path: ActionString | ActionAggregator; fn: (fieldValue: any, object: Source, items: Source, objectToCompute: R) => R; -}; +} export interface Constructable { new (...args: any[]): T;