diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af6b8d36d..a1455fe35 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,9 +33,9 @@ jobs: - run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - run: npm install - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} +# - run: npm install +# env: +# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: npm run build - run: npx eslint --ext .ts packages/** - run: npm run test @@ -63,9 +63,9 @@ jobs: - run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - run: npm install - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} +# - run: npm install +# env: +# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: git config --global user.name 'ima-bot' - run: git config --global user.email 'ima-bot@ima-tech.ca' - run: npm run bump-patch diff --git a/package-lock.json b/package-lock.json index fdcef274d..ff9100f3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@pristine-ts/common": "file:packages/common", "@pristine-ts/configuration": "file:packages/configuration", "@pristine-ts/core": "file:packages/core", - "@pristine-ts/data-transformer": "file:packages/data-transformer", + "@pristine-ts/data-mapping": "file:packages/data-mapping", "@pristine-ts/e2e": "file:tests/e2e", "@pristine-ts/express": "file:packages/express", "@pristine-ts/file": "file:packages/file", @@ -4116,8 +4116,8 @@ "resolved": "packages/core", "link": true }, - "node_modules/@pristine-ts/data-transformer": { - "resolved": "packages/data-transformer", + "node_modules/@pristine-ts/data-mapping": { + "resolved": "packages/data-mapping", "link": true }, "node_modules/@pristine-ts/e2e": { @@ -15599,9 +15599,23 @@ "uuid": "dist/bin/uuid" } }, + "packages/data-mapping": { + "name": "@pristine-ts/data-mapper", + "version": "0.0.276", + "license": "ISC", + "dependencies": { + "@pristine-ts/class-validator": "^1.0.22", + "@pristine-ts/common": "file:../common", + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" + } + }, "packages/data-transformer": { "name": "@pristine-ts/data-transformer", "version": "0.0.276", + "extraneous": true, "license": "ISC", "dependencies": { "@pristine-ts/common": "file:../common", @@ -15804,7 +15818,6 @@ "version": "1.0.0", "dependencies": { "@pristine-ts/auth0": "file:../../packages/auth0", - "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/cli": "file:../../packages/cli", "@pristine-ts/common": "file:../../packages/common", "@pristine-ts/core": "file:../../packages/core", @@ -19233,18 +19246,21 @@ } } }, - "@pristine-ts/data-transformer": { - "version": "file:packages/data-transformer", + "@pristine-ts/data-mapping": { + "version": "file:packages/data-mapping", "requires": { + "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/common": "file:../common", - "@pristine-ts/logging": "file:../logging" + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" } }, "@pristine-ts/e2e": { "version": "file:tests/e2e", "requires": { "@pristine-ts/auth0": "file:../../packages/auth0", - "@pristine-ts/class-validator": "^1.0.22", "@pristine-ts/cli": "file:../../packages/cli", "@pristine-ts/common": "file:../../packages/common", "@pristine-ts/core": "file:../../packages/core", diff --git a/package.json b/package.json index 6e68b1d64..bf0ed758f 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@pristine-ts/common": "file:packages/common", "@pristine-ts/configuration": "file:packages/configuration", "@pristine-ts/core": "file:packages/core", - "@pristine-ts/data-transformer": "file:packages/data-transformer", + "@pristine-ts/data-mapping": "file:packages/data-mapping", "@pristine-ts/e2e": "file:tests/e2e", "@pristine-ts/express": "file:packages/express", "@pristine-ts/file": "file:packages/file", diff --git a/packages/data-mapping/package-lock.json b/packages/data-mapping/package-lock.json new file mode 100644 index 000000000..0a77c046c --- /dev/null +++ b/packages/data-mapping/package-lock.json @@ -0,0 +1,231 @@ +{ + "name": "@pristine-ts/data-mapper", + "version": "0.0.276", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@pristine-ts/data-mapper", + "version": "0.0.276", + "license": "ISC", + "dependencies": { + "@pristine-ts/class-validator": "^1.0.22", + "@pristine-ts/common": "file:../common", + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" + } + }, + "../common": { + "name": "@pristine-ts/common", + "version": "0.0.276", + "license": "ISC", + "dependencies": { + "@pristine-ts/metadata": "^1.0.2", + "reflect-metadata": "^0.1.13", + "tsyringe": "^4.8.0" + } + }, + "../logging": { + "name": "@pristine-ts/logging", + "version": "0.0.276", + "license": "ISC", + "dependencies": { + "@pristine-ts/common": "file:../common", + "@pristine-ts/configuration": "file:../configuration", + "date-fns": "^2.30.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", + "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@pristine-ts/class-validator": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@pristine-ts/class-validator/-/class-validator-1.0.22.tgz", + "integrity": "sha512-Fuvl2rq3wz/lO6gcoBHt3FZjr1diHBfz2Ug5wCqZceQZIHeUWwHM3T6GxazPDOjXPIH1yNGZo07e5o3Uma0ibw==", + "dependencies": { + "@pristine-ts/metadata": "^1.0.2", + "date-fns": "^2.28.0", + "libphonenumber-js": "^1.10.6", + "reflect-metadata": "^0.2.1", + "validator": "^13.7.0" + } + }, + "node_modules/@pristine-ts/common": { + "resolved": "../common", + "link": true + }, + "node_modules/@pristine-ts/logging": { + "resolved": "../logging", + "link": true + }, + "node_modules/@pristine-ts/metadata": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pristine-ts/metadata/-/metadata-1.0.2.tgz", + "integrity": "sha512-flztBNC8Rf8IqrapZCrz86IUc/wf+L13gRL+R3Z5wCQxldG5GTGd+eJYEgDfzKa7crxoofz8Y1+2kJDj4nScdg==", + "dependencies": { + "reflect-metadata": "^0.2.1" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.10.53", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.53.tgz", + "integrity": "sha512-sDTnnqlWK4vH4AlDQuswz3n4Hx7bIQWTpIcScJX+Sp7St3LXHmfiax/ZFfyYxHmkdCvydOLSuvtAO/XpXiSySw==" + }, + "node_modules/reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tsyringe": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.8.0.tgz", + "integrity": "sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } + } + }, + "dependencies": { + "@babel/runtime": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", + "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@pristine-ts/class-validator": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@pristine-ts/class-validator/-/class-validator-1.0.22.tgz", + "integrity": "sha512-Fuvl2rq3wz/lO6gcoBHt3FZjr1diHBfz2Ug5wCqZceQZIHeUWwHM3T6GxazPDOjXPIH1yNGZo07e5o3Uma0ibw==", + "requires": { + "@pristine-ts/metadata": "^1.0.2", + "date-fns": "^2.28.0", + "libphonenumber-js": "^1.10.6", + "reflect-metadata": "^0.2.1", + "validator": "^13.7.0" + } + }, + "@pristine-ts/common": { + "version": "file:../common", + "requires": { + "@pristine-ts/metadata": "^1.0.2", + "reflect-metadata": "^0.1.13", + "tsyringe": "^4.8.0" + } + }, + "@pristine-ts/logging": { + "version": "file:../logging", + "requires": { + "@pristine-ts/common": "file:../common", + "@pristine-ts/configuration": "file:../configuration", + "date-fns": "^2.30.0" + } + }, + "@pristine-ts/metadata": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pristine-ts/metadata/-/metadata-1.0.2.tgz", + "integrity": "sha512-flztBNC8Rf8IqrapZCrz86IUc/wf+L13gRL+R3Z5wCQxldG5GTGd+eJYEgDfzKa7crxoofz8Y1+2kJDj4nScdg==", + "requires": { + "reflect-metadata": "^0.2.1" + } + }, + "class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "requires": { + "@babel/runtime": "^7.21.0" + } + }, + "libphonenumber-js": { + "version": "1.10.53", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.53.tgz", + "integrity": "sha512-sDTnnqlWK4vH4AlDQuswz3n4Hx7bIQWTpIcScJX+Sp7St3LXHmfiax/ZFfyYxHmkdCvydOLSuvtAO/XpXiSySw==" + }, + "reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "tsyringe": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.8.0.tgz", + "integrity": "sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==", + "requires": { + "tslib": "^1.9.3" + } + }, + "validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==" + } + } +} diff --git a/packages/data-transformer/package.json b/packages/data-mapping/package.json similarity index 75% rename from packages/data-transformer/package.json rename to packages/data-mapping/package.json index 0c23c3707..fd7be07f3 100644 --- a/packages/data-transformer/package.json +++ b/packages/data-mapping/package.json @@ -1,10 +1,10 @@ { - "name": "@pristine-ts/data-transformer", + "name": "@pristine-ts/data-mapping", "version": "0.0.276", "description": "", - "module": "dist/lib/esm/data-transformer.module.js", - "main": "dist/lib/cjs/data-transformer.module.js", - "types": "dist/types/data-transformer.module.d.ts", + "module": "dist/lib/esm/data-mapping.module.js", + "main": "dist/lib/cjs/data-mapping.module.js", + "types": "dist/types/data-mapping.module.d.ts", "scripts": { "build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json", "prepublish": "npm run build", @@ -21,7 +21,11 @@ }, "dependencies": { "@pristine-ts/common": "file:../common", - "@pristine-ts/logging": "file:../logging" + "@pristine-ts/logging": "file:../logging", + "@pristine-ts/metadata": "~1.0.2", + "@pristine-ts/class-validator": "^1.0.22", + "class-transformer": "^0.5.1", + "tsyringe": "^4.8.0" }, "jest": { "transform": { diff --git a/packages/data-mapping/readme.md b/packages/data-mapping/readme.md new file mode 100644 index 000000000..ec59df714 --- /dev/null +++ b/packages/data-mapping/readme.md @@ -0,0 +1 @@ +# Data Mapping module. \ No newline at end of file diff --git a/packages/data-mapping/src/builders/builders.ts b/packages/data-mapping/src/builders/builders.ts new file mode 100644 index 000000000..c9cfed586 --- /dev/null +++ b/packages/data-mapping/src/builders/builders.ts @@ -0,0 +1 @@ +export * from "./data-mapping.builder"; \ No newline at end of file diff --git a/packages/data-mapping/src/builders/data-mapping.builder.spec.ts b/packages/data-mapping/src/builders/data-mapping.builder.spec.ts new file mode 100644 index 000000000..5437b673a --- /dev/null +++ b/packages/data-mapping/src/builders/data-mapping.builder.spec.ts @@ -0,0 +1,209 @@ +import {DataMappingBuilder} from "./data-mapping.builder"; +import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataMappingNode} from "../nodes/data-mapping.node"; +import {DataMappingLeaf} from "../nodes/data-mapping.leaf"; + +describe("Data Mapping Builder", () => { + it("should properly build a simple DataMappingBuilder", () => { + class Source { + title: string; + + rank: number; + + firstName: string; + + lastName: string; + } + + class Destination { + name: string; + + position: number; + + familyName: string; + } + + const dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .add() + .setSourceProperty("rank") + .setDestinationProperty("position") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .add() + .setSourceProperty("lastName") + .setDestinationProperty("familyName") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .end() + + expect(dataMappingBuilder.nodes.title).toBeDefined() + expect(dataMappingBuilder.nodes.title.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.title.destinationProperty).toBe("name") + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + expect(dataMappingBuilder.nodes.rank).toBeDefined() + expect(dataMappingBuilder.nodes.rank.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.rank.destinationProperty).toBe("position") + expect((dataMappingBuilder.nodes.rank as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.rank as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + expect(dataMappingBuilder.nodes.lastName).toBeDefined() + expect(dataMappingBuilder.nodes.lastName.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.lastName.destinationProperty).toBe("familyName") + expect((dataMappingBuilder.nodes.lastName as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.lastName as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + expect(dataMappingBuilder.nodes.firstName).toBeUndefined() + }) + it("should properly build a complex DataMappingBuilder", () => { + class ArraySource { + rank: number; + } + + class NestedSource { + nestedTitle: string; + } + + class Source { + title: string; + + nested: NestedSource; + + array: ArraySource[] = []; + + children: string[] = []; + } + + class ArrayDestination { + position: number; + } + + class NestedDestination { + nestedName: string; + } + + class Destination { + name: string; + + child: NestedDestination; + + list: ArrayDestination[]; + + infants: string[] = [] + } + + const dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .addNestingLevel() + .setSourceProperty("nested") + .setDestinationProperty("child") + .add() + .setSourceProperty("nestedTitle") + .setDestinationProperty("nestedName") + .end() + .end() + .addArrayOfObjects() + .setSourceProperty("array") + .setDestinationProperty("list") + .add() + .setSourceProperty("rank") + .setDestinationProperty("position") + .end() + .end() + .addArrayOfScalar() + .setSourceProperty("children") + .setDestinationProperty("infants") + .end() + .end(); + + expect(dataMappingBuilder.nodes.title).toBeDefined() + expect(dataMappingBuilder.nodes.title.type).toBe(DataMappingNodeTypeEnum.Leaf) + expect(dataMappingBuilder.nodes.title.destinationProperty).toBe("name") + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.size).toBe(1) + expect((dataMappingBuilder.nodes.title as DataMappingLeaf).excludedNormalizers.has(LowercaseNormalizer.name)).toBeTruthy() + + expect(dataMappingBuilder.nodes.nested).toBeDefined() + expect(dataMappingBuilder.nodes.nested.type).toBe(DataMappingNodeTypeEnum.Node) + expect(dataMappingBuilder.nodes.nested.destinationProperty).toBe("child") + expect((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nestedTitle.sourceProperty).toBe("nestedTitle") + expect((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nestedTitle.destinationProperty).toBe("nestedName") + + expect(dataMappingBuilder.nodes.array).toBeDefined() + expect(dataMappingBuilder.nodes.array.type).toBe(DataMappingNodeTypeEnum.ObjectArray) + expect(dataMappingBuilder.nodes.array.destinationProperty).toBe("list") + expect((dataMappingBuilder.nodes.array as DataMappingNode).nodes.rank.sourceProperty).toBe("rank") + expect((dataMappingBuilder.nodes.array as DataMappingNode).nodes.rank.destinationProperty).toBe("position") + + expect(dataMappingBuilder.nodes.children).toBeDefined() + expect(dataMappingBuilder.nodes.children.type).toBe(DataMappingNodeTypeEnum.ScalarArray) + expect(dataMappingBuilder.nodes.children.destinationProperty).toBe("infants") + expect((dataMappingBuilder.nodes.children as DataMappingLeaf).sourceProperty).toBe("children") + expect((dataMappingBuilder.nodes.children as DataMappingLeaf).destinationProperty).toBe("infants") + + }) + + it("should properly set the parents", () => { + class NestedSource2 { + nestedTitle2: string; + } + + class NestedSource { + nested2: NestedSource2; + } + + class Source { + nested: NestedSource; + } + + class NestedDestination2 { + nestedTitle2: string; + } + class NestedDestination { + nested2: NestedDestination2; + } + + class Destination { + nested: NestedDestination; + } + + const dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .addNestingLevel() + .setSourceProperty("nested") + .setDestinationProperty("nested") + .addNestingLevel() + .setSourceProperty("nested2") + .setDestinationProperty("nested2") + .add() + .setSourceProperty("nestedTitle2") + .setDestinationProperty("nestedTitle2") + .end() + .end() + .end() + .end(); + + expect((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2.sourceProperty).toBe("nested2"); + expect(((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2 as DataMappingNode).nodes.nestedTitle2.sourceProperty).toBe("nestedTitle2"); + + expect((((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2 as DataMappingNode).parent as DataMappingNode).sourceProperty).toBe("nested"); + + expect(((((dataMappingBuilder.nodes.nested as DataMappingNode).nodes.nested2 as DataMappingNode).nodes.nestedTitle2 as DataMappingLeaf).parent as DataMappingNode).sourceProperty).toBe("nested2"); + + }) +}) \ No newline at end of file diff --git a/packages/data-mapping/src/builders/data-mapping.builder.ts b/packages/data-mapping/src/builders/data-mapping.builder.ts new file mode 100644 index 000000000..39d05215a --- /dev/null +++ b/packages/data-mapping/src/builders/data-mapping.builder.ts @@ -0,0 +1,205 @@ +import {DataMappingNode} from "../nodes/data-mapping.node"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; +import { + DataBeforeMappingInterceptorAlreadyAddedError +} from "../errors/data-before-mapping-interceptor-already-added.error"; +import { + DataAfterMappingInterceptorAlreadyAddedError +} from "../errors/data-after-mapping-interceptor-already-added.error"; +import {DataMappingLeaf} from "../nodes/data-mapping.leaf"; +import {BaseDataMappingNode} from "../nodes/base-data-mapping.node"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; + +export class DataMappingBuilder extends BaseDataMappingNode{ + public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + public beforeMappingInterceptors: { key: DataMappingInterceptorUniqueKeyType, options: any}[] = []; + public afterMappingInterceptors: { key: DataMappingInterceptorUniqueKeyType, options: any}[] = []; + + /** + * This method adds a normalizer to the root that will be applied on each node (unless they explicitly exclude to do + * so). + * + * @param normalizerUniqueKey + * @param options + */ + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingBuilder { + if(this.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); + } + + this.normalizers.push({ + key: normalizerUniqueKey, + options, + }); + return this; + } + + /** + * This method returns whether there's a normalizer for the specified key or not. + * + * @param normalizerUniqueKey + */ + public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { + return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; + } + + /** + * This method adds an interceptor that will be executed **before** the object is mapped. + * + * @param key + * @param options + */ + public addBeforeMappingInterceptor(key: DataMappingInterceptorUniqueKeyType, options?: any): DataMappingBuilder { + if(this.hasBeforeMappingInterceptor(key)) { + throw new DataBeforeMappingInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this Tree.", key, options) + } + + this.beforeMappingInterceptors.push({ + key, + options, + }); + + return this; + } + + /** + * This method returns whether a **before** interceptor already exists. + * @param key + */ + public hasBeforeMappingInterceptor(key: DataMappingInterceptorUniqueKeyType): boolean { + return this.beforeMappingInterceptors.find(element => element.key === key) !== undefined; + } + + /** + * This method adds an interceptor that will be executed **after** the object is mapped. + * + * @param key + * @param options + */ + public addAfterMappingInterceptor(key: DataMappingInterceptorUniqueKeyType, options?: any): DataMappingBuilder { + if(this.hasAfterMappingInterceptor(key)) { + throw new DataAfterMappingInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this Tree.", key, options) + } + + this.afterMappingInterceptors.push({ + key, + options, + }); + + return this; + } + + /** + * This method returns whether a **after** interceptor already exists. + * @param key + */ + public hasAfterMappingInterceptor(key: DataMappingInterceptorUniqueKeyType): boolean { + return this.afterMappingInterceptors.find(element => element.key === key) !== undefined; + } + + /** + * This property creates a new DataMappingLeaf and returns it. It doesn't add it yet. To do so, the `end()` method + * must be called. + */ + public add() { + return new DataMappingLeaf(this, this); + } + + /** + * This method adds a nesting level. This should be used when the property contains an object and you want to map + * this object into another object. + */ + public addNestingLevel() { + return new DataMappingNode(this, this); + } + + /** + * This method adds an array of Scalar allowing you to apply the normalizer on each scalar in the array. The + * `sourceProperty` and `destinationProperty` correspond to the name of the property that is an array. But, the + * values in the array will be normalized using the normalizer. + * + */ + public addArrayOfScalar(): DataMappingLeaf { + return new DataMappingLeaf(this, this, DataMappingNodeTypeEnum.ScalarArray); + } + + /** + * This method adds an array of objects allowing to define a node for each property in the object. Each object in + * the array will be treated as being the same. + */ + public addArrayOfObjects(): DataMappingNode { + return new DataMappingNode(this, this, DataMappingNodeTypeEnum.ObjectArray); + } + + /** + * This method is called at the end just to make it nice since all the nodes will have one, it's nice + * that the builder has one too. + */ + public end(): DataMappingBuilder { + return this; + } + + /** + * This method imports a schema. + * + * @param schema + */ + public import(schema: any) { + this.normalizers = schema.normalizers; + this.beforeMappingInterceptors = schema.beforeMappingInterceptors; + this.afterMappingInterceptors = schema.afterMappingInterceptors; + + const nodes = schema.nodes; + + for(const key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + const nodeInfo = nodes[key]; + + const type: DataMappingNodeTypeEnum = nodeInfo["_type"]; + + switch (type) { + case DataMappingNodeTypeEnum.ScalarArray: + case DataMappingNodeTypeEnum.Leaf: + const leaf = new DataMappingLeaf(this, this, type); + leaf.import(nodeInfo); + this.nodes[leaf.sourceProperty] = leaf; + continue; + + case DataMappingNodeTypeEnum.Node: + case DataMappingNodeTypeEnum.ObjectArray: + const node = new DataMappingNode(this, this, type); + node.import(nodeInfo); + this.nodes[node.sourceProperty] = node; + continue; + } + } + } + + /** + * This method exports this node. + */ + public export() { + const nodes = this.nodes; + + for (const key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + nodes[key] = nodes[key].export(); + } + + return { + "nodes": nodes, + "normalizers":this.normalizers, + "beforeMappingInterceptors": this.beforeMappingInterceptors, + "afterMappingInterceptors": this.afterMappingInterceptors, + + } + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/data-mapping.module.keyname.ts b/packages/data-mapping/src/data-mapping.module.keyname.ts new file mode 100644 index 000000000..18a73cd8b --- /dev/null +++ b/packages/data-mapping/src/data-mapping.module.keyname.ts @@ -0,0 +1 @@ +export const DataMappingModuleKeyname: string = "pristine.data-mapping"; diff --git a/packages/data-transformer/src/data-transformer.module.ts b/packages/data-mapping/src/data-mapping.module.ts similarity index 55% rename from packages/data-transformer/src/data-transformer.module.ts rename to packages/data-mapping/src/data-mapping.module.ts index 5d4ab2b1f..faf05f0ae 100644 --- a/packages/data-transformer/src/data-transformer.module.ts +++ b/packages/data-mapping/src/data-mapping.module.ts @@ -1,19 +1,20 @@ import {ModuleInterface} from "@pristine-ts/common"; import {LoggingModule} from "@pristine-ts/logging"; -import {DataTransformerModuleKeyname} from "./data-transformer.module.keyname"; -import { EnvironmentVariableResolver, NumberResolver} from "@pristine-ts/configuration"; -import {DataTransformerBuilder} from "./transformers/data-transformer.builder"; +import {DataMappingModuleKeyname} from "./data-mapping.module.keyname"; +export * from "./builders/builders"; +export * from "./enums/enums"; export * from "./errors/errors"; export * from "./interceptors/interceptors"; export * from "./interfaces/interfaces"; +export * from "./mappers/mappers"; +export * from "./nodes/nodes"; export * from "./normalizer-options/normalizer-options"; export * from "./normalizers/normalizers"; -export * from "./transformers/transformers"; export * from "./types/types"; -export const DataTransformerModule: ModuleInterface = { - keyname: DataTransformerModuleKeyname, +export const DataMappingModule: ModuleInterface = { + keyname: DataMappingModuleKeyname, importModules: [ LoggingModule, ], diff --git a/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts b/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts new file mode 100644 index 000000000..abfe0423e --- /dev/null +++ b/packages/data-mapping/src/enums/data-mapping-node-type.enum.ts @@ -0,0 +1,6 @@ +export enum DataMappingNodeTypeEnum { + ScalarArray = "DATA_MAPPING_NODE_SCALAR_ARRAY", + ObjectArray = "DATA_MAPPING_NODE_OBJECT_ARRAY", + Node = "DATA_MAPPING_NODE", + Leaf = "DATA_MAPPING_LEAF", +} \ No newline at end of file diff --git a/packages/data-mapping/src/enums/enums.ts b/packages/data-mapping/src/enums/enums.ts new file mode 100644 index 000000000..d8e6e1a82 --- /dev/null +++ b/packages/data-mapping/src/enums/enums.ts @@ -0,0 +1 @@ +export * from "./data-mapping-node-type.enum"; \ No newline at end of file diff --git a/packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts b/packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts new file mode 100644 index 000000000..1fabdd18b --- /dev/null +++ b/packages/data-mapping/src/errors/array-data-mapping-node-invalid-source-property-type.error.ts @@ -0,0 +1,19 @@ +import {LoggableError} from "@pristine-ts/common"; + +/** + * This Error is thrown when a node is of type array but the `source[sourceProperty]` doesn't actually contain an array. + */ +export class ArrayDataMappingNodeInvalidSourcePropertyTypeError extends LoggableError { + + public constructor(message: string, sourceProperty: string) { + super(message, { + sourceProperty, + }); + + + // Set the prototype explicitly. + // As specified in the documentation in TypeScript + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, ArrayDataMappingNodeInvalidSourcePropertyTypeError.prototype); + } +} diff --git a/packages/data-transformer/src/errors/data-after-row-transformer-interceptor-already-added.error.ts b/packages/data-mapping/src/errors/data-after-mapping-interceptor-already-added.error.ts similarity index 56% rename from packages/data-transformer/src/errors/data-after-row-transformer-interceptor-already-added.error.ts rename to packages/data-mapping/src/errors/data-after-mapping-interceptor-already-added.error.ts index 576d3f6c4..b8101f77d 100644 --- a/packages/data-transformer/src/errors/data-after-row-transformer-interceptor-already-added.error.ts +++ b/packages/data-mapping/src/errors/data-after-mapping-interceptor-already-added.error.ts @@ -1,13 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {Request} from "@pristine-ts/common"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; /** * This Error is thrown when the before row interceptor is added more than once to the builder. */ -export class DataAfterRowTransformerInterceptorAlreadyAddedError extends LoggableError { +export class DataAfterMappingInterceptorAlreadyAddedError extends LoggableError { - public constructor(message: string, uniqueKey: DataTransformerInterceptorUniqueKeyType, options?: any) { + public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { uniqueKey, options, @@ -16,6 +16,6 @@ export class DataAfterRowTransformerInterceptorAlreadyAddedError extends Loggabl // Set the prototype explicitly. // As specified in the documentation in TypeScript // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, DataAfterRowTransformerInterceptorAlreadyAddedError.prototype); + Object.setPrototypeOf(this, DataAfterMappingInterceptorAlreadyAddedError.prototype); } } diff --git a/packages/data-transformer/src/errors/data-before-row-transformer-interceptor-already-added.error.ts b/packages/data-mapping/src/errors/data-before-mapping-interceptor-already-added.error.ts similarity index 56% rename from packages/data-transformer/src/errors/data-before-row-transformer-interceptor-already-added.error.ts rename to packages/data-mapping/src/errors/data-before-mapping-interceptor-already-added.error.ts index 0cf89a075..4b872e2cb 100644 --- a/packages/data-transformer/src/errors/data-before-row-transformer-interceptor-already-added.error.ts +++ b/packages/data-mapping/src/errors/data-before-mapping-interceptor-already-added.error.ts @@ -1,13 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {Request} from "@pristine-ts/common"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; /** * This Error is thrown when the after row interceptor is added more than once to the builder. */ -export class DataBeforeRowTransformerInterceptorAlreadyAddedError extends LoggableError { +export class DataBeforeMappingInterceptorAlreadyAddedError extends LoggableError { - public constructor(message: string, uniqueKey: DataTransformerInterceptorUniqueKeyType, options?: any) { + public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { uniqueKey, options, @@ -16,6 +16,6 @@ export class DataBeforeRowTransformerInterceptorAlreadyAddedError extends Loggab // Set the prototype explicitly. // As specified in the documentation in TypeScript // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, DataBeforeRowTransformerInterceptorAlreadyAddedError.prototype); + Object.setPrototypeOf(this, DataBeforeMappingInterceptorAlreadyAddedError.prototype); } } diff --git a/packages/data-transformer/src/errors/data-transformer-interceptor-not-found.error.ts b/packages/data-mapping/src/errors/data-mapping-interceptor-not-found.error.ts similarity index 59% rename from packages/data-transformer/src/errors/data-transformer-interceptor-not-found.error.ts rename to packages/data-mapping/src/errors/data-mapping-interceptor-not-found.error.ts index 766a827e9..cc36abd67 100644 --- a/packages/data-transformer/src/errors/data-transformer-interceptor-not-found.error.ts +++ b/packages/data-mapping/src/errors/data-mapping-interceptor-not-found.error.ts @@ -1,13 +1,13 @@ import {LoggableError} from "@pristine-ts/common"; import {Request} from "@pristine-ts/common"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; /** * This Error is thrown if the Data Transformer Class is not found in the list of available interceptors. It might be missing a tag. */ -export class DataTransformerInterceptorNotFoundError extends LoggableError { +export class DataMappingInterceptorNotFoundError extends LoggableError { - public constructor(message: string, uniqueKey: DataTransformerInterceptorUniqueKeyType, options?: any) { + public constructor(message: string, uniqueKey: DataMappingInterceptorUniqueKeyType, options?: any) { super(message, { uniqueKey, options, @@ -16,6 +16,6 @@ export class DataTransformerInterceptorNotFoundError extends LoggableError { // Set the prototype explicitly. // As specified in the documentation in TypeScript // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, DataTransformerInterceptorNotFoundError.prototype); + Object.setPrototypeOf(this, DataMappingInterceptorNotFoundError.prototype); } } diff --git a/packages/data-transformer/src/errors/data-transformer-source-property-not-found.error.ts b/packages/data-mapping/src/errors/data-mapping-source-property-not-found.error.ts similarity index 77% rename from packages/data-transformer/src/errors/data-transformer-source-property-not-found.error.ts rename to packages/data-mapping/src/errors/data-mapping-source-property-not-found.error.ts index 924fa6225..fab4868f7 100644 --- a/packages/data-transformer/src/errors/data-transformer-source-property-not-found.error.ts +++ b/packages/data-mapping/src/errors/data-mapping-source-property-not-found.error.ts @@ -4,7 +4,7 @@ import {Request} from "@pristine-ts/common"; /** * This Error is thrown when a property isn't optional and should be found in the source object. */ -export class DataTransformerSourcePropertyNotFoundError extends LoggableError { +export class DataMappingSourcePropertyNotFoundError extends LoggableError { public constructor(message: string, sourceProperty: string) { super(message, { @@ -15,6 +15,6 @@ export class DataTransformerSourcePropertyNotFoundError extends LoggableError { // Set the prototype explicitly. // As specified in the documentation in TypeScript // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, DataTransformerSourcePropertyNotFoundError.prototype); + Object.setPrototypeOf(this, DataMappingSourcePropertyNotFoundError.prototype); } } diff --git a/packages/data-transformer/src/errors/data-normalizer-already-added.error.ts b/packages/data-mapping/src/errors/data-normalizer-already-added.error.ts similarity index 100% rename from packages/data-transformer/src/errors/data-normalizer-already-added.error.ts rename to packages/data-mapping/src/errors/data-normalizer-already-added.error.ts diff --git a/packages/data-mapping/src/errors/errors.ts b/packages/data-mapping/src/errors/errors.ts new file mode 100644 index 000000000..2f2b6d06d --- /dev/null +++ b/packages/data-mapping/src/errors/errors.ts @@ -0,0 +1,8 @@ +export * from "./array-data-mapping-node-invalid-source-property-type.error"; +export * from "./data-after-mapping-interceptor-already-added.error"; +export * from "./data-before-mapping-interceptor-already-added.error"; +export * from "./data-normalizer-already-added.error"; +export * from "./data-mapping-interceptor-not-found.error"; +export * from "./data-mapping-source-property-not-found.error"; +export * from "./normalizer-invalid-source-type.error"; +export * from "./undefined-source-property.error"; \ No newline at end of file diff --git a/packages/data-transformer/src/errors/normalizer-invalid-source-type.error.ts b/packages/data-mapping/src/errors/normalizer-invalid-source-type.error.ts similarity index 100% rename from packages/data-transformer/src/errors/normalizer-invalid-source-type.error.ts rename to packages/data-mapping/src/errors/normalizer-invalid-source-type.error.ts diff --git a/packages/data-mapping/src/errors/undefined-source-property.error.ts b/packages/data-mapping/src/errors/undefined-source-property.error.ts new file mode 100644 index 000000000..e4d21cea7 --- /dev/null +++ b/packages/data-mapping/src/errors/undefined-source-property.error.ts @@ -0,0 +1,20 @@ +import {LoggableError} from "@pristine-ts/common"; +import {DataMappingLeaf} from "../nodes/data-mapping.leaf"; +import {DataMappingNode} from "../nodes/data-mapping.node"; + +/** + * This Error is thrown when you are trying to add a Node which has an undefined sourceProperty value. + */ +export class UndefinedSourcePropertyError extends LoggableError { + + public constructor(node: DataMappingLeaf | DataMappingNode) { + super("The `sourceProperty` property of the Node cannot be undefined to be added as a Node to its parent.", { + node, + }); + + // Set the prototype explicitly. + // As specified in the documentation in TypeScript + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, UndefinedSourcePropertyError.prototype); + } +} diff --git a/packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts b/packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts new file mode 100644 index 000000000..70021fd86 --- /dev/null +++ b/packages/data-mapping/src/interceptors/default-data-mapping.interceptor.ts @@ -0,0 +1,22 @@ +import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; +import {moduleScoped, tag} from "@pristine-ts/common"; +import {DataMappingModuleKeyname} from "../data-mapping.module.keyname"; +import {injectable} from "tsyringe"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; + +@tag("DataTransformerInterceptor") +@moduleScoped(DataMappingModuleKeyname) +@injectable() +export class DefaultDataMappingInterceptor implements DataMappingInterceptorInterface { + async afterMapping(row: any): Promise { + return row; + } + + async beforeMapping(row: any): Promise { + return row; + } + + getUniqueKey(): DataMappingInterceptorUniqueKeyType { + return DefaultDataMappingInterceptor.name; + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/interceptors/interceptors.ts b/packages/data-mapping/src/interceptors/interceptors.ts new file mode 100644 index 000000000..e97b987e1 --- /dev/null +++ b/packages/data-mapping/src/interceptors/interceptors.ts @@ -0,0 +1 @@ +export * from "./default-data-mapping.interceptor"; \ No newline at end of file diff --git a/packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts b/packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts new file mode 100644 index 000000000..143372998 --- /dev/null +++ b/packages/data-mapping/src/interfaces/data-mapping-interceptor.interface.ts @@ -0,0 +1,22 @@ +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; + +export interface DataMappingInterceptorInterface { + /** + * Every data mapping interceptor must define a unique key. Then, during the mapping, the schema can specify which + * interceptors must be called. + */ + getUniqueKey(): DataMappingInterceptorUniqueKeyType; + + /** + * This method is called before the row is being mapped and normalized. It allows you to combine fields for example if that's what you want. + * @param row + */ + beforeMapping(row: any): Promise; + + /** + * This method is called after the row is being mapped and normalized. It can allow you to apply operations on each + * field or combine fields for example. + * @param row + */ + afterMapping(row: any): Promise; +} \ No newline at end of file diff --git a/packages/data-transformer/src/interfaces/data-normalizer.interface.ts b/packages/data-mapping/src/interfaces/data-normalizer.interface.ts similarity index 100% rename from packages/data-transformer/src/interfaces/data-normalizer.interface.ts rename to packages/data-mapping/src/interfaces/data-normalizer.interface.ts diff --git a/packages/data-mapping/src/interfaces/interfaces.ts b/packages/data-mapping/src/interfaces/interfaces.ts new file mode 100644 index 000000000..224b612f3 --- /dev/null +++ b/packages/data-mapping/src/interfaces/interfaces.ts @@ -0,0 +1,2 @@ +export * from "./data-normalizer.interface"; +export * from "./data-mapping-interceptor.interface"; \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/data.mapper.spec.ts b/packages/data-mapping/src/mappers/data.mapper.spec.ts new file mode 100644 index 000000000..123c13280 --- /dev/null +++ b/packages/data-mapping/src/mappers/data.mapper.spec.ts @@ -0,0 +1,385 @@ +import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; +import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; +import {DataMappingInterceptorNotFoundError} from "../errors/data-mapping-interceptor-not-found.error"; +import {DataMappingSourcePropertyNotFoundError} from "../errors/data-mapping-source-property-not-found.error"; +import {property} from "@pristine-ts/metadata"; +import "jest-extended" +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {DataMapper} from "./data.mapper"; +import {Type} from "class-transformer"; + +describe("Data Mapper", () =>{ + it("should map a very complex object into another complex object. Then, it should export the builder, import the builder and make sure it still maps everything properly.", async () => { + class ArraySource { + rank: number; + } + + class NestedSource { + nestedTitle: string; + } + + class Source { + title: string; + + nested: NestedSource; + + array: ArraySource[] = []; + + children: string[] = []; + } + + class ArrayDestination { + position: number; + } + + class NestedDestination { + nestedName: string; + } + + class Destination { + name: string; + + @Type(() => NestedDestination) + child: NestedDestination; + + @Type(() => ArrayDestination) + list: ArrayDestination[]; + + infants: string[] = [] + } + + let dataMappingBuilder = new DataMappingBuilder(); + + dataMappingBuilder + .addNormalizer(LowercaseNormalizer.name) + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .excludeNormalizer(LowercaseNormalizer.name) + .end() + .addNestingLevel() + .setSourceProperty("nested") + .setDestinationProperty("child") + .add() + .setSourceProperty("nestedTitle") + .setDestinationProperty("nestedName") + .end() + .end() + .addArrayOfObjects() + .setSourceProperty("array") + .setDestinationProperty("list") + .add() + .setSourceProperty("rank") + .setDestinationProperty("position") + .end() + .end() + .addArrayOfScalar() + .setSourceProperty("children") + .setDestinationProperty("infants") + .end() + .end(); + + const source = new Source(); + source.title = "TITLE"; + source.children = ["Etienne", "Antoine", "Olivier"]; + source.nested = new NestedSource(); + source.nested.nestedTitle = "NESTED_TITLE"; + source.array = []; + source.array[0] = new ArraySource() + source.array[0].rank = 1 + source.array[1] = new ArraySource() + source.array[1].rank = 2 + + + let dataMapper = new DataMapper([new LowercaseNormalizer()], []); + + const destination: Destination = await dataMapper.map(dataMappingBuilder, source, Destination); + + // Check that the mapping was properly done. + expect(destination instanceof Destination).toBeTruthy(); + + expect(destination.name).toBe("TITLE"); + expect(destination.infants.length).toBe(3); + expect(destination.infants[0]).toBe("etienne") + expect(destination.infants[1]).toBe("antoine") + expect(destination.infants[2]).toBe("olivier") + expect(destination.child).toBeDefined() + expect(destination.child instanceof NestedDestination).toBeTruthy(); + expect(destination.child.nestedName).toBe("nested_title") + expect(destination.list).toBeDefined() + expect(destination.list.length).toBe(2) + expect(destination.list[0] instanceof ArrayDestination).toBeTruthy() + expect(destination.list[0].position).toBe(1) + expect(destination.list[1] instanceof ArrayDestination).toBeTruthy() + expect(destination.list[1].position).toBe(2) + + // Make sure that the import and export work and still map properly + const schema = dataMappingBuilder.export(); + + dataMapper = new DataMapper([new LowercaseNormalizer()], []); + dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder.import(schema); + + const destinationAfterExportAndReimport = await dataMapper.map(dataMappingBuilder, source, Destination); + + // Check AGAIN that the mapping was properly done. + expect(destinationAfterExportAndReimport instanceof Destination).toBeTruthy(); + + expect(destinationAfterExportAndReimport.name).toBe("TITLE"); + expect(destinationAfterExportAndReimport.infants.length).toBe(3); + expect(destinationAfterExportAndReimport.infants[0]).toBe("etienne") + expect(destinationAfterExportAndReimport.infants[1]).toBe("antoine") + expect(destinationAfterExportAndReimport.infants[2]).toBe("olivier") + expect(destinationAfterExportAndReimport.child).toBeDefined() + expect(destinationAfterExportAndReimport.child instanceof NestedDestination).toBeTruthy(); + expect(destinationAfterExportAndReimport.child.nestedName).toBe("nested_title") + expect(destinationAfterExportAndReimport.list).toBeDefined() + expect(destinationAfterExportAndReimport.list.length).toBe(2) + expect(destinationAfterExportAndReimport.list[0] instanceof ArrayDestination).toBeTruthy() + expect(destinationAfterExportAndReimport.list[0].position).toBe(1) + expect(destinationAfterExportAndReimport.list[1] instanceof ArrayDestination).toBeTruthy() + expect(destinationAfterExportAndReimport.list[1].position).toBe(2) + }) + + it("should properly transform", async () => { + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); + + const source = [{ + NAME: "Etienne Noel", + province: "QUEBEC", + TOTAL: 10, + }]; + + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .add() + .setSourceProperty("NAME") + .setDestinationProperty("name") + .end() + .add() + .setSourceProperty("province") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") + .end() + .add() + .setSourceProperty("TOTAL") + .setDestinationProperty("total") + .end(); + + const destination = await dataMapper.mapAll(dataMappingBuilder, source); + + expect(destination.length).toBe(1) + + expect(destination[0].name).toBeDefined() + expect(destination[0].name).toBe("Etienne Noel"); + expect(destination[0].province).toBe("quebec"); + expect(destination[0].total).toBe(10); + }) + + it("should properly transform an array with numerical indices", async () => { + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); + + const source = [ + ["Etienne Noel", "QUEBEC", 10], + ]; + + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .add() + .setSourceProperty("0") + .setDestinationProperty("name") + .end() + .add() + .setSourceProperty("1") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") + .end() + .add() + .setSourceProperty("2") + .setDestinationProperty("total") + .end(); + + const destination = await dataMapper.mapAll(dataMappingBuilder, source); + + expect(destination.length).toBe(1) + + expect(destination[0].name).toBeDefined() + expect(destination[0].name).toBe("Etienne Noel"); + expect(destination[0].province).toBe("quebec"); + expect(destination[0].total).toBe(10); + }) + + it("should properly call the before row transformers and respect the order of calls", async () => { + const firstInterceptor: DataMappingInterceptorInterface = { + async beforeMapping(row: any): Promise { + return row; + }, + async afterMapping(row: any): Promise { + return { + "after": row["name"] + row["province"] + row["total"] + row["added_property"], + }; + }, + getUniqueKey(): DataMappingInterceptorUniqueKeyType { + return "first_interceptor"; + }, + } + const secondInterceptor: DataMappingInterceptorInterface = { + async beforeMapping(row: any): Promise { + row[3] = "Property added in the beforeRowTransform"; + return row; + }, + async afterMapping(row: any): Promise { + return row; + }, + getUniqueKey(): DataMappingInterceptorUniqueKeyType { + return "second_interceptor"; + }, + } + + const beforeRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "beforeMapping") + const afterRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "afterMapping") + const beforeRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "beforeMapping") + const afterRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "afterMapping") + + const dataTransformer = new DataMapper([new LowercaseNormalizer()], [ + firstInterceptor, + secondInterceptor, + ]); + + const source = [ + ["Etienne Noel", "QUEBEC", 10], + ]; + + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .addBeforeMappingInterceptor("first_interceptor") + .addBeforeMappingInterceptor("second_interceptor") + .addAfterMappingInterceptor("first_interceptor") + .addAfterMappingInterceptor("second_interceptor") + .add() + .setSourceProperty("0") + .setDestinationProperty("name") + .end() + .add() + .setSourceProperty("1") + .addNormalizer(LowercaseNormalizer.name) + .setDestinationProperty("province") + .end() + .add() + .setSourceProperty("2") + .setDestinationProperty("total") + .end() + .add() + .setSourceProperty("3") + .setDestinationProperty("added_property") + .end(); + + const mappedData: any[] = await dataTransformer.mapAll(dataMappingBuilder, source); + + expect(beforeRowFirstInterceptorSpy).toHaveBeenCalledBefore(beforeRowSecondInterceptorSpy); + expect(beforeRowSecondInterceptorSpy).toHaveBeenCalledBefore(afterRowFirstInterceptorSpy) + expect(afterRowFirstInterceptorSpy).toHaveBeenCalledBefore(afterRowSecondInterceptorSpy); + + expect(mappedData[0]["after"]).toBe("Etienne Noelquebec10Property added in the beforeRowTransform") + }) + + it("should throw properly when before row transformer cannot be found", async () => { + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); + + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .addBeforeMappingInterceptor("first_interceptor") + .add() + .setSourceProperty("0") + .setDestinationProperty("name") + .end() + + await expect(dataMapper.mapAll(dataMappingBuilder, [{"a": "a"}])).rejects.toThrowError(DataMappingInterceptorNotFoundError); + }) + + it("should throw properly when after row transformer cannot be found", async () => { + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); + + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .addAfterMappingInterceptor("first_interceptor") + .add() + .setSourceProperty("0") + .setDestinationProperty("name") + .end() + + await expect(dataMapper.mapAll(dataMappingBuilder, [{"0": "a"}])).rejects.toThrowError(DataMappingInterceptorNotFoundError); + }) + + it("should throw properly when an element is not optional and not found in the source", async () => { + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); + + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .add() + .setSourceProperty("0") + .setDestinationProperty("name") + .end() + + await expect(dataMapper.mapAll(dataMappingBuilder, [{"a": "a"}])).rejects.toThrowError(DataMappingSourcePropertyNotFoundError); + }) + + it("should properly type the return object when a destinationType is passed", async () => { + class Source { + @property() + title: string; + } + + class Destination { + @property() + name: string; + } + + const source = new Source(); + source.title = "TITLE"; + + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); + + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .addNormalizer(LowercaseNormalizer.name) + .end() + + const destination = await dataMapper.map(dataMappingBuilder, source, Destination); + + expect(destination.name).toBe("title"); + }) + + it("should properly type the nested objects", async () => { + class Source { + @property() + title: string; + } + + class Destination { + @property() + name: string; + } + + const source = new Source(); + source.title = "TITLE"; + + const dataMapper = new DataMapper([new LowercaseNormalizer()], []); + + const dataMappingBuilder = new DataMappingBuilder(); + dataMappingBuilder + .add() + .setSourceProperty("title") + .setDestinationProperty("name") + .addNormalizer(LowercaseNormalizer.name) + .end() + + const destination = await dataMapper.map(dataMappingBuilder, source, Destination); + + expect(destination.name).toBe("title"); + }) +}) \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/data.mapper.ts b/packages/data-mapping/src/mappers/data.mapper.ts new file mode 100644 index 000000000..e0fc2a131 --- /dev/null +++ b/packages/data-mapping/src/mappers/data.mapper.ts @@ -0,0 +1,99 @@ +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import {DataMappingInterceptorUniqueKeyType} from "../types/data-mapping-interceptor-unique-key.type"; +import {DataMappingInterceptorInterface} from "../interfaces/data-mapping-interceptor.interface"; +import {moduleScoped} from "@pristine-ts/common"; +import {DataMappingModuleKeyname} from "../data-mapping.module.keyname"; +import {injectable, injectAll} from "tsyringe"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {ClassConstructor, plainToInstance} from "class-transformer"; +import {DataMappingInterceptorNotFoundError} from "../errors/data-mapping-interceptor-not-found.error"; + +@moduleScoped(DataMappingModuleKeyname) +@injectable() +export class DataMapper { + private readonly dataNormalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface } = {} + private readonly dataTransformerInterceptorsMap: { [key in DataMappingInterceptorUniqueKeyType]: DataMappingInterceptorInterface } = {} + + public constructor(@injectAll("DataNormalizerInterface") private readonly dataNormalizers: DataNormalizerInterface[], + @injectAll("DataTransformerInterceptor") private readonly dataTransformerInterceptors: DataMappingInterceptorInterface[],) { + dataNormalizers.forEach(dataNormalizer => { + this.dataNormalizersMap[dataNormalizer.getUniqueKey()] = dataNormalizer; + }) + + dataTransformerInterceptors.forEach(interceptor => { + this.dataTransformerInterceptorsMap[interceptor.getUniqueKey()] = interceptor; + }); + } + + /** + * This method takes an array of source and maps each item. + * + * @param builder + * @param source + * @param destinationType + */ + public async mapAll(builder: DataMappingBuilder, source: any[], destinationType?: ClassConstructor): Promise { + const destination = []; + + for(const element of source) { + destination.push(await this.map(builder, element, destinationType)); + } + + return destination; + } + + /** + * This method takes a builder, a source and maps it according to the builder. You can pass a `destinationType (optional)` + * that is an object that will be constructed. + * + * @param builder + * @param source + * @param destinationType + */ + public async map(builder: DataMappingBuilder, source: any, destinationType?: ClassConstructor): Promise { + let destination = {}; + + let interceptedSource = source; + + // Execute the before interceptors. + for (const element of builder.beforeMappingInterceptors) { + const interceptor = this.dataTransformerInterceptorsMap[element.key]; + + if (interceptor === undefined) { + throw new DataMappingInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); + } + + // todo: Pass the options when we start using them. + interceptedSource = await interceptor.beforeMapping(interceptedSource); + } + + // Loop over the properties defined in the builder + for (const key in builder.nodes) { + if(builder.nodes.hasOwnProperty(key) === false) { + continue; + } + + const node = builder.nodes[key]; + await node.map(interceptedSource, destination, this.dataNormalizersMap); + } + + // Execute the before interceptors. + for (const element of builder.afterMappingInterceptors) { + const interceptor: DataMappingInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; + + if (interceptor === undefined) { + throw new DataMappingInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); + } + + // todo pass the options when we start using it. + destination = await interceptor.afterMapping(destination); + } + + if(destinationType) { + destination = plainToInstance(destinationType, destination); + } + + return destination; + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/mappers/mappers.ts b/packages/data-mapping/src/mappers/mappers.ts new file mode 100644 index 000000000..209812d7e --- /dev/null +++ b/packages/data-mapping/src/mappers/mappers.ts @@ -0,0 +1 @@ +export * from "./data.mapper"; \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/base-data-mapping.node.ts b/packages/data-mapping/src/nodes/base-data-mapping.node.ts new file mode 100644 index 000000000..7910183e0 --- /dev/null +++ b/packages/data-mapping/src/nodes/base-data-mapping.node.ts @@ -0,0 +1,24 @@ +import {DataMappingNode} from "./data-mapping.node"; +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {UndefinedSourcePropertyError} from "../errors/undefined-source-property.error"; + +export abstract class BaseDataMappingNode { + public nodes: {[sourceProperty in string]: DataMappingNode | DataMappingLeaf} = {}; + + /** + * This method is called by the node itself to tell its parent that it has been build and is ready to be added. + * We use this mechanism to force the `end()` method on the leaf to be called so we can do some validations before + * adding it to the tree. + * + * @param node + */ + public addNode(node: DataMappingLeaf | DataMappingNode) { + if(node.sourceProperty === undefined) { + throw new UndefinedSourcePropertyError(node); + } + + this.nodes[node.sourceProperty] = node; + } + + +} \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts new file mode 100644 index 000000000..dd4f09d45 --- /dev/null +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.spec.ts @@ -0,0 +1,105 @@ +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; + +describe("Data Mapping Leaf", () => { + it("should map the property corresponding to the leaf from the source to the destination while also applying the normalizers and excluding the specified normalizers", async () => { + class Source { + title: string = "TITLE"; + } + + class Destination { + name: string; + } + + class PrependUnderscoresNormalizer implements DataNormalizerInterface { + getUniqueKey(): DataNormalizerUniqueKey { + return PrependUnderscoresNormalizer.name; + } + + normalize(source: any, options?: {}): string { + return "__" + source; + } + } + + class AppendUnderscoresNormalizer implements DataNormalizerInterface { + getUniqueKey(): DataNormalizerUniqueKey { + return AppendUnderscoresNormalizer.name; + } + + normalize(source: any, options?: {}): string { + return source + "__"; + } + } + + const dataBuilder = new DataMappingBuilder(); + dataBuilder.addNormalizer(PrependUnderscoresNormalizer.name); + dataBuilder.addNormalizer(AppendUnderscoresNormalizer.name); + + const leaf = new DataMappingLeaf(dataBuilder, dataBuilder); + leaf.setSourceProperty("title"); + leaf.setDestinationProperty("name"); + leaf.addNormalizer(LowercaseNormalizer.name) + leaf.excludeNormalizer(PrependUnderscoresNormalizer.name) + + const destination = new Destination(); + + await leaf.map(new Source(), destination, { + [LowercaseNormalizer.name]: new LowercaseNormalizer(), + [PrependUnderscoresNormalizer.name]: new PrependUnderscoresNormalizer(), + [AppendUnderscoresNormalizer.name]: new AppendUnderscoresNormalizer(), + }); + + expect(destination.name).toBeDefined() + expect(destination.name).toBe("title__"); + }) + + it("should properly map an array of scalars", async () => { + class Source { + array: string[] = [ + "-4", + "100", + "9350", + "5", + ] + } + + class Destination { + name: number[] = []; + } + + class ConvertToNumber implements DataNormalizerInterface { + getUniqueKey(): DataNormalizerUniqueKey { + return ConvertToNumber.name; + } + + normalize(source: any, options?: {}): number { + return parseInt(source); + } + } + + const dataBuilder = new DataMappingBuilder(); + dataBuilder.addNormalizer(ConvertToNumber.name); + + const leaf = new DataMappingLeaf(dataBuilder, dataBuilder, DataMappingNodeTypeEnum.ScalarArray); + leaf.setSourceProperty("array"); + leaf.setDestinationProperty("name"); + + const destination = new Destination(); + + await leaf.map(new Source(), destination, { + [ConvertToNumber.name]: new ConvertToNumber(), + }); + + expect(destination.name).toBeDefined() + expect(Array.isArray(destination.name)).toBeTruthy() + expect(destination.name.length).toBe(4); + expect(destination.name[0]).toBe(-4); + expect(destination.name[1]).toBe(100); + expect(destination.name[2]).toBe(9350); + expect(destination.name[3]).toBe(5); + }) +}); \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.leaf.ts b/packages/data-mapping/src/nodes/data-mapping.leaf.ts new file mode 100644 index 000000000..2d66e17bb --- /dev/null +++ b/packages/data-mapping/src/nodes/data-mapping.leaf.ts @@ -0,0 +1,221 @@ +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {DataMappingNode} from "./data-mapping.node"; +import {DataMappingSourcePropertyNotFoundError} from "../errors/data-mapping-source-property-not-found.error"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import { + ArrayDataMappingNodeInvalidSourcePropertyTypeError +} from "../errors/array-data-mapping-node-invalid-source-property-type.error"; + +export class DataMappingLeaf { + /** + * This property represents the property referenced in the `source` object. + */ + public sourceProperty!: string; + + /** + * This property represents the property referenced in the `destination` object. + */ + public destinationProperty!: string; + + /** + * This property contains an array of Normalizers to apply sequentially when mapping this property. + */ + public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; + + /** + * This property contains an array of Normalizers that must be excluded from normalizers defined by parents. + */ + public excludedNormalizers: Set = new Set(); + + /** + * This method specified whether it's possible that this element not be present in the `source` object. + */ + public isOptional: boolean = false; + + public constructor( + private readonly root: DataMappingBuilder, + public readonly parent: DataMappingNode | DataMappingBuilder, + public readonly type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Leaf, + ) { + } + + /** + * This is a setter for `sourceProperty`. + * @param sourceProperty + */ + public setSourceProperty(sourceProperty: string): DataMappingLeaf { + this.sourceProperty = sourceProperty; + return this; + } + + /** + * This is a setter for `destinationProperty`. + * @param destinationProperty + */ + public setDestinationProperty(destinationProperty: string): DataMappingLeaf { + this.destinationProperty = destinationProperty; + return this; + } + + /** + * This is a setter for `isOptional`. + * @param isOptional + */ + public setIsOptional(isOptional: boolean): DataMappingLeaf { + this.isOptional = isOptional; + + return this; + } + + /** + * This methods adds a normalizer but checks that this normalizer hasn't been added already (either at the root) or + * directly on this leaf. + * + * @param normalizerUniqueKey + * @param options + */ + public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataMappingLeaf { + if(this.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the leaf with destination property: '" + this.destinationProperty + "'.", normalizerUniqueKey, options) + } + + if(this.root.hasNormalizer(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the root and cannot be also added to the leaf with destination property: '" + this.destinationProperty + "'.", normalizerUniqueKey, options) + } + + this.normalizers.push({ + key: normalizerUniqueKey, + options, + }); + + return this; + } + + /** + * This method simply returns whether the normalizer was already added to this node. + * @param normalizerUniqueKey + */ + public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { + return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; + } + + /** + * This method adds a normalizer that must be excluded from the normalizers applied at a higher level.à + * @param normalizerUniqueKey + */ + public excludeNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): DataMappingLeaf { + if(this.excludedNormalizers.has(normalizerUniqueKey)) { + throw new DataNormalizerAlreadyAdded("The EXCLUDED data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey) + } + + this.excludedNormalizers.add(normalizerUniqueKey); + + return this; + } + + /** + * This method adds this node to its parent and returns the parent. + */ + public end(): DataMappingNode | DataMappingBuilder { + // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. + this.parent.addNode(this) + + return this.parent; + } + + /** + * This method maps the `sourceProperty` from the `source` object and maps it to the `destinationProperty` of the + * `destination` object while applying the normalizers. + * + * @param source + * @param destination + * @param normalizersMap + */ + public async map(source: any, destination: any, normalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface }) { + if(source.hasOwnProperty(this.sourceProperty) === false) { + if(this.isOptional) { + return + } + + throw new DataMappingSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) + } + + const normalizers = this.root.normalizers.filter(element => this.excludedNormalizers.has(element.key) === false); + normalizers.push(...this.normalizers); + + if(this.type === DataMappingNodeTypeEnum.ScalarArray) { + // This means that the source[propertyKey] contains an array of objects and each object should be mapped + const array = source[this.sourceProperty]; + + if(Array.isArray(array) === false) { + throw new ArrayDataMappingNodeInvalidSourcePropertyTypeError(`According to your schema, the property '${this.sourceProperty}' in the source object must contain an Array of Scalar. Instead, it contains: '${typeof array}'.`, this.sourceProperty); + } + + destination[this.destinationProperty] = []; + + for (let value of array) { + normalizers.forEach(element => { + const normalizer = normalizersMap[element.key]; + value = normalizer.normalize(value, element.options); + }) + + destination[this.destinationProperty].push(value); + } + + return; + } + + let value = source[this.sourceProperty]; + normalizers.forEach(element => { + const normalizer = normalizersMap[element.key]; + value = normalizer.normalize(value, element.options); + }) + + destination[this.destinationProperty] = value; + + return; + } + + /** + * This method imports a schema. + * + * @param schema + */ + public import(schema: any) { + this.normalizers = schema.normalizers; + + this.excludedNormalizers = new Set() + if(schema.hasOwnProperty("excludedNormalizers")) { + for(const item in schema.excludedNormalizers) { + this.excludeNormalizer(item); + } + } + + this.isOptional = schema.isOptional; + this.sourceProperty = schema.sourceProperty; + this.destinationProperty = schema.destinationProperty; + } + + /** + * This method exports this node. + */ + public export(): any { + const excludedNormalizers: any = {} + + for(const element of this.excludedNormalizers.values()) { + excludedNormalizers[element] = true; + } + + return { + "_type": this.type, + "sourceProperty": this.sourceProperty, + "destinationProperty": this.destinationProperty, + "isOptional": this.isOptional, + "normalizers": this.normalizers, + "excludedNormalizers": excludedNormalizers, + } + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/data-mapping.node.ts b/packages/data-mapping/src/nodes/data-mapping.node.ts new file mode 100644 index 000000000..0597945ae --- /dev/null +++ b/packages/data-mapping/src/nodes/data-mapping.node.ts @@ -0,0 +1,239 @@ +import {DataMappingNodeTypeEnum} from "../enums/data-mapping-node-type.enum"; +import {DataMappingLeaf} from "./data-mapping.leaf"; +import {DataMappingBuilder} from "../builders/data-mapping.builder"; +import {BaseDataMappingNode} from "./base-data-mapping.node"; +import {DataMappingSourcePropertyNotFoundError} from "../errors/data-mapping-source-property-not-found.error"; +import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; +import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; +import { + ArrayDataMappingNodeInvalidSourcePropertyTypeError +} from "../errors/array-data-mapping-node-invalid-source-property-type.error"; + +export class DataMappingNode extends BaseDataMappingNode { + /** + * This property represents the property referenced in the `source` object. + */ + public sourceProperty!: string; + + /** + * This property represents the property referenced in the `destination` object. + */ + public destinationProperty!: string; + + /** + * This method specified whether it's possible that this element not be present in the `source` object. + */ + public isOptional: boolean = false; + + constructor(public readonly root: DataMappingBuilder, + public readonly parent: DataMappingNode | DataMappingBuilder, + public readonly type: DataMappingNodeTypeEnum = DataMappingNodeTypeEnum.Node, + ) { + super(); + } + + /** + * This is a setter for `sourceProperty`. + * @param sourceProperty + */ + public setSourceProperty(sourceProperty: string): DataMappingNode { + this.sourceProperty = sourceProperty; + return this; + } + + /** + * This is a setter for `destinationProperty`. + * @param destinationProperty + */ + public setDestinationProperty(destinationProperty: string): DataMappingNode { + this.destinationProperty = destinationProperty; + return this; + } + + /** + * This is a setter for `isOptional`. + * @param isOptional + */ + public setIsOptional(isOptional: boolean): DataMappingNode { + this.isOptional = isOptional; + + return this; + } + + /** + * This property creates a new DataMappingLeaf and returns it. It doesn't add it yet. To do so, the `end()` method + * must be called. + */ + public add() { + return new DataMappingLeaf(this.root, this); + } + + /** + * This method adds a nesting level. This should be used when the property contains an object and you want to map + * this object into another object. + */ + public addNestingLevel() { + return new DataMappingNode(this.root, this); + } + + /** + * This method adds an array of Scalar allowing you to apply the normalizer on each scalar in the array. The + * `sourceProperty` and `destinationProperty` correspond to the name of the property that is an array. But, the + * values in the array will be normalized using the normalizer. + * + */ + public addArrayOfScalar(): DataMappingLeaf { + return new DataMappingLeaf(this.root, this, DataMappingNodeTypeEnum.ScalarArray); + } + + /** + * This method adds an array of objects allowing to define a node for each property in the object. Each object in + * the array will be treated as being the same. + */ + public addArrayOfObjects(): DataMappingNode { + return new DataMappingNode(this.root, this, DataMappingNodeTypeEnum.ObjectArray); + } + + /** + * This method adds this node to its parent and returns the parent. + */ + public end(): DataMappingNode | DataMappingBuilder { + // todo: Validate that we actually have all the properties needed (sourceProperty and destinationProperty) for example. + this.parent.addNode(this) + + return this.parent; + } + + /** + * This method maps the `sourceProperty` from the `source` object and maps it to the `destinationProperty` of the + * `destination` object while applying the normalizers. + * + * @param source + * @param destination + * @param normalizersMap + */ + public async map(source: any, destination: any, normalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface }) { + if(source.hasOwnProperty(this.sourceProperty) === false) { + if(this.isOptional) { + return + } + + throw new DataMappingSourcePropertyNotFoundError("The property '" + this.sourceProperty + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", this.sourceProperty) + } + + const sourceElement = source[this.sourceProperty]; + + if(destination[this.destinationProperty] === undefined) { + if(this.type === DataMappingNodeTypeEnum.ObjectArray) { + destination[this.destinationProperty] = []; + } else { + // todo: we need to get the expected Type of the `destination[this.destinationProperty]` and actually instantiate it. + destination[this.destinationProperty] = {}; + } + } + + const destinationElement = destination[this.destinationProperty]; + + if(this.type === DataMappingNodeTypeEnum.ObjectArray) { + // This means that the source[propertyKey] contains an array of objects and each object should be mapped + const array = source[this.sourceProperty]; + + if(Array.isArray(array) === false) { + throw new ArrayDataMappingNodeInvalidSourcePropertyTypeError(`According to your schema, the property '${this.sourceProperty}' in the source object must contain an Array of objects. Instead, it contains: '${typeof array}'.`, this.sourceProperty); + } + + for (const element of array) { + // todo: we need to get the expected Type of the object in the array in the Destination object + const dest = {}; + + for (const key in this.nodes) { + if(this.nodes.hasOwnProperty(key) === false) { + continue; + } + + const node = this.nodes[key]; + + await node.map(element, dest, normalizersMap); + } + + destinationElement.push(dest); + } + + return; + } + + // When the current node is not an array, we simply iterate + for (const key in this.nodes) { + if(this.nodes.hasOwnProperty(key) === false) { + continue; + } + + const node = this.nodes[key]; + + await node.map(sourceElement, destinationElement, normalizersMap); + } + } + + /** + * This method imports a schema. + * + * @param schema + */ + public import(schema: any) { + this.sourceProperty = schema.sourceProperty; + this.destinationProperty = schema.destinationProperty; + this.isOptional = schema.isOptional; + this.nodes = {}; + + const nodes = schema.nodes; + + for(const key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + const nodeInfo = nodes[key]; + + const type: DataMappingNodeTypeEnum = nodeInfo["_type"]; + + switch (type) { + case DataMappingNodeTypeEnum.ScalarArray: + case DataMappingNodeTypeEnum.Leaf: + const leaf = new DataMappingLeaf(this.root, this, type); + leaf.import(nodeInfo); + this.nodes[leaf.sourceProperty] = leaf; + continue; + + case DataMappingNodeTypeEnum.Node: + case DataMappingNodeTypeEnum.ObjectArray: + const node = new DataMappingNode(this.root, this, type); + node.import(nodeInfo); + this.nodes[node.sourceProperty] = node; + continue; + } + } + } + + /** + * This method exports this node. + */ + public export() { + const nodes = this.nodes; + + for (const key in nodes) { + if(nodes.hasOwnProperty(key) === false) { + continue; + } + + nodes[key] = nodes[key].export(); + } + + return { + "_type": this.type, + "sourceProperty": this.sourceProperty, + "destinationProperty": this.destinationProperty, + "isOptional": this.isOptional, + "nodes": nodes, + } + } +} \ No newline at end of file diff --git a/packages/data-mapping/src/nodes/nodes.ts b/packages/data-mapping/src/nodes/nodes.ts new file mode 100644 index 000000000..7a31c999f --- /dev/null +++ b/packages/data-mapping/src/nodes/nodes.ts @@ -0,0 +1,3 @@ +export * from "./base-data-mapping.node"; +export * from "./data-mapping.leaf"; +export * from "./data-mapping.node"; diff --git a/packages/data-transformer/src/normalizer-options/base-normalizer.options.ts b/packages/data-mapping/src/normalizer-options/base-normalizer.options.ts similarity index 100% rename from packages/data-transformer/src/normalizer-options/base-normalizer.options.ts rename to packages/data-mapping/src/normalizer-options/base-normalizer.options.ts diff --git a/packages/data-transformer/src/normalizer-options/lowercase-normalizer.options.ts b/packages/data-mapping/src/normalizer-options/lowercase-normalizer.options.ts similarity index 100% rename from packages/data-transformer/src/normalizer-options/lowercase-normalizer.options.ts rename to packages/data-mapping/src/normalizer-options/lowercase-normalizer.options.ts diff --git a/packages/data-transformer/src/normalizer-options/normalizer-options.ts b/packages/data-mapping/src/normalizer-options/normalizer-options.ts similarity index 100% rename from packages/data-transformer/src/normalizer-options/normalizer-options.ts rename to packages/data-mapping/src/normalizer-options/normalizer-options.ts diff --git a/packages/data-transformer/src/normalizers/lowercase.normalizer.spec.ts b/packages/data-mapping/src/normalizers/lowercase.normalizer.spec.ts similarity index 100% rename from packages/data-transformer/src/normalizers/lowercase.normalizer.spec.ts rename to packages/data-mapping/src/normalizers/lowercase.normalizer.spec.ts diff --git a/packages/data-transformer/src/normalizers/lowercase.normalizer.ts b/packages/data-mapping/src/normalizers/lowercase.normalizer.ts similarity index 100% rename from packages/data-transformer/src/normalizers/lowercase.normalizer.ts rename to packages/data-mapping/src/normalizers/lowercase.normalizer.ts diff --git a/packages/data-transformer/src/normalizers/normalizers.ts b/packages/data-mapping/src/normalizers/normalizers.ts similarity index 100% rename from packages/data-transformer/src/normalizers/normalizers.ts rename to packages/data-mapping/src/normalizers/normalizers.ts diff --git a/packages/data-transformer/src/test.spec.ts b/packages/data-mapping/src/test.spec.ts similarity index 100% rename from packages/data-transformer/src/test.spec.ts rename to packages/data-mapping/src/test.spec.ts diff --git a/packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts b/packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts new file mode 100644 index 000000000..7791ad01b --- /dev/null +++ b/packages/data-mapping/src/types/data-mapping-interceptor-unique-key.type.ts @@ -0,0 +1 @@ +export type DataMappingInterceptorUniqueKeyType = string; \ No newline at end of file diff --git a/packages/data-transformer/src/types/data-normalizer-unique-key.type.ts b/packages/data-mapping/src/types/data-normalizer-unique-key.type.ts similarity index 100% rename from packages/data-transformer/src/types/data-normalizer-unique-key.type.ts rename to packages/data-mapping/src/types/data-normalizer-unique-key.type.ts diff --git a/packages/data-mapping/src/types/types.ts b/packages/data-mapping/src/types/types.ts new file mode 100644 index 000000000..ee9ebfee7 --- /dev/null +++ b/packages/data-mapping/src/types/types.ts @@ -0,0 +1,2 @@ +export * from "./data-normalizer-unique-key.type"; +export * from "./data-mapping-interceptor-unique-key.type"; \ No newline at end of file diff --git a/packages/data-transformer/tsconfig.cjs.json b/packages/data-mapping/tsconfig.cjs.json similarity index 100% rename from packages/data-transformer/tsconfig.cjs.json rename to packages/data-mapping/tsconfig.cjs.json diff --git a/packages/data-transformer/tsconfig.json b/packages/data-mapping/tsconfig.json similarity index 100% rename from packages/data-transformer/tsconfig.json rename to packages/data-mapping/tsconfig.json diff --git a/packages/data-transformer/package-lock.json b/packages/data-transformer/package-lock.json deleted file mode 100644 index 6d6ac0810..000000000 --- a/packages/data-transformer/package-lock.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "@pristine-ts/data-transformer", - "version": "0.0.276", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@pristine-ts/data-transformer", - "version": "0.0.276", - "license": "ISC", - "dependencies": { - "@pristine-ts/common": "file:../common", - "@pristine-ts/logging": "file:../logging" - } - }, - "../common": { - "name": "@pristine-ts/common", - "version": "0.0.276", - "license": "ISC", - "dependencies": { - "@pristine-ts/metadata": "^1.0.2", - "reflect-metadata": "^0.1.13", - "tsyringe": "^4.8.0" - } - }, - "../logging": { - "name": "@pristine-ts/logging", - "version": "0.0.276", - "license": "ISC", - "dependencies": { - "@pristine-ts/common": "file:../common", - "@pristine-ts/configuration": "file:../configuration", - "date-fns": "^2.30.0" - } - }, - "node_modules/@pristine-ts/common": { - "resolved": "../common", - "link": true - }, - "node_modules/@pristine-ts/logging": { - "resolved": "../logging", - "link": true - } - }, - "dependencies": { - "@pristine-ts/common": { - "version": "file:../common", - "requires": { - "@pristine-ts/metadata": "^1.0.2", - "reflect-metadata": "^0.1.13", - "tsyringe": "^4.8.0" - } - }, - "@pristine-ts/logging": { - "version": "file:../logging", - "requires": { - "@pristine-ts/common": "file:../common", - "@pristine-ts/configuration": "file:../configuration", - "date-fns": "^2.30.0" - } - } - } -} diff --git a/packages/data-transformer/readme.md b/packages/data-transformer/readme.md deleted file mode 100644 index a99a4cab4..000000000 --- a/packages/data-transformer/readme.md +++ /dev/null @@ -1 +0,0 @@ -# Data Transformer module. \ No newline at end of file diff --git a/packages/data-transformer/src/data-transformer.module.keyname.ts b/packages/data-transformer/src/data-transformer.module.keyname.ts deleted file mode 100644 index adfcf2047..000000000 --- a/packages/data-transformer/src/data-transformer.module.keyname.ts +++ /dev/null @@ -1 +0,0 @@ -export const DataTransformerModuleKeyname: string = "pristine.data-transformer"; diff --git a/packages/data-transformer/src/errors/errors.ts b/packages/data-transformer/src/errors/errors.ts deleted file mode 100644 index 2e08cba08..000000000 --- a/packages/data-transformer/src/errors/errors.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./data-after-row-transformer-interceptor-already-added.error"; -export * from "./data-before-row-transformer-interceptor-already-added.error"; -export * from "./data-normalizer-already-added.error"; -export * from "./data-transformer-source-property-not-found.error"; -export * from "./normalizer-invalid-source-type.error"; \ No newline at end of file diff --git a/packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts b/packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts deleted file mode 100644 index 7396b4001..000000000 --- a/packages/data-transformer/src/interceptors/default-data-transformer.interceptor.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; -import {moduleScoped, tag} from "@pristine-ts/common"; -import {DataTransformerModuleKeyname} from "../data-transformer.module.keyname"; -import {injectable} from "tsyringe"; -import {DataTransformerRow} from "../types/data-transformer.row"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; - -@tag("DataTransformerInterceptor") -@moduleScoped(DataTransformerModuleKeyname) -@injectable() -export class DefaultDataTransformerInterceptor implements DataTransformerInterceptorInterface { - async afterRowTransform(row: DataTransformerRow): Promise { - return row; - } - - async beforeRowTransform(row: DataTransformerRow): Promise { - return row; - } - - getUniqueKey(): DataTransformerInterceptorUniqueKeyType { - return DefaultDataTransformerInterceptor.name; - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/interceptors/interceptors.ts b/packages/data-transformer/src/interceptors/interceptors.ts deleted file mode 100644 index 14629e7c1..000000000 --- a/packages/data-transformer/src/interceptors/interceptors.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./default-data-transformer.interceptor"; \ No newline at end of file diff --git a/packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts b/packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts deleted file mode 100644 index afbe878eb..000000000 --- a/packages/data-transformer/src/interfaces/data-transformer-interceptor.interface.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {DataTransformerRow} from "../types/data-transformer.row"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; - -export interface DataTransformerInterceptorInterface { - /** - * Every data transformer interceptor must define a unique key. Then, during the transformation, the schema can specify which - * interceptors must be called. - */ - getUniqueKey(): DataTransformerInterceptorUniqueKeyType; - - /** - * This method is called before the row is being transformed and normalized. It allows you to combine fields for example if that's what you want. - * @param row - */ - beforeRowTransform(row: DataTransformerRow): Promise; - - /** - * This method is called after the row is being transformed and normalized. It can allow you to apply operations on each - * field or combine fields for example. - * @param row - */ - afterRowTransform(row: DataTransformerRow): Promise; -} \ No newline at end of file diff --git a/packages/data-transformer/src/interfaces/interfaces.ts b/packages/data-transformer/src/interfaces/interfaces.ts deleted file mode 100644 index c336d245c..000000000 --- a/packages/data-transformer/src/interfaces/interfaces.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./data-normalizer.interface"; -export * from "./data-transformer-interceptor.interface"; \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.builder.spec.ts b/packages/data-transformer/src/transformers/data-transformer.builder.spec.ts deleted file mode 100644 index 10ee82ea4..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.builder.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import "reflect-metadata" -import {DataTransformerBuilder} from "./data-transformer.builder"; -import {DataTransformerProperty} from "./data-transformer.property"; -import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; - -describe('Data Transformer Builder', () => { - let dataTransformerBuilder: DataTransformerBuilder; - - beforeEach(() => { - dataTransformerBuilder = new DataTransformerBuilder(); - }) - - it("should properly add a normalizer", () => { - const options = { - "optionA": true, - }; - - dataTransformerBuilder.addNormalizer("normalizer", options); - - expect(dataTransformerBuilder.normalizers.length).toBe(1); - expect(dataTransformerBuilder.normalizers[0].key).toBe("normalizer") - expect(dataTransformerBuilder.normalizers[0].options).toBe(options); - }) - - it("should return if it has a normalizer or not", () => { - const options = { - "optionA": true, - }; - - dataTransformerBuilder.addNormalizer("normalizer", options); - - expect(dataTransformerBuilder.hasNormalizer("normalizer")).toBeTruthy(); - expect(dataTransformerBuilder.hasNormalizer("dafds")).toBeFalsy(); - }) - - it("should properly add a new data transformer property", () => { - expect(dataTransformerBuilder.add() instanceof DataTransformerProperty).toBeTruthy() - }) - - it("should support being exported and then re-imported", () => { - const dataTransformerBuilder = new DataTransformerBuilder(); - const builder = dataTransformerBuilder - .addBeforeRowTransformInterceptor("first_interceptor") - .addBeforeRowTransformInterceptor("second_interceptor") - .addAfterRowTransformInterceptor("first_interceptor") - .addAfterRowTransformInterceptor("second_interceptor") - .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .excludeNormalizer(LowercaseNormalizer.name) - .end() - .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() - .add() - .setSourceProperty("2") - .setDestinationProperty("total") - .end(); - - const exportedBuilder = builder.export(); - - - const serializedObject: any = { - "normalizers": [], - "beforeRowTransformInterceptors":[{"key":"first_interceptor"},{"key":"second_interceptor"}], - "afterRowTransformInterceptors":[{"key":"first_interceptor"},{"key":"second_interceptor"}], - "properties":{ - "0":{ - "sourceProperty":"0", - "destinationProperty":"name", - "isOptional": false, - "normalizers": [], - "excludedNormalizers": { - [LowercaseNormalizer.name]: true, - } - }, - "1": { - "sourceProperty":"1", - "destinationProperty":"province", - "isOptional": false, - "normalizers": [ - { - "key": LowercaseNormalizer.name - } - ], - "excludedNormalizers": {} - }, - "2": { - "sourceProperty": "2", - "destinationProperty": "total", - "isOptional": false, - "normalizers": [], - "excludedNormalizers": {}, - } - }, - }; - - const stringifiedObject = JSON.stringify(serializedObject); - - expect(exportedBuilder).toBe(stringifiedObject); - - // Create a new builder and import the serializedObject. - - const importedBuilder = new DataTransformerBuilder(); - importedBuilder.import(stringifiedObject); - - expect(importedBuilder.normalizers.length).toBe(0); - expect(importedBuilder.beforeRowTransformInterceptors.length).toBe(2); - expect(importedBuilder.afterRowTransformInterceptors.length).toBe(2); - expect(Object.keys(importedBuilder.properties).length).toBe(3); - expect(importedBuilder.properties[0].normalizers.length).toBe(0) - expect(importedBuilder.properties[0].excludedNormalizers.size).toBe(1) - expect(importedBuilder.properties[0].isOptional).toBeFalsy(); - expect(importedBuilder.properties[0].sourceProperty).toBe("0"); - expect(importedBuilder.properties[0].destinationProperty).toBe("name"); - - expect(importedBuilder.properties[1].normalizers.length).toBe(1) - expect(importedBuilder.properties[1].excludedNormalizers.size).toBe(0) - expect(importedBuilder.properties[1].isOptional).toBeFalsy(); - expect(importedBuilder.properties[1].sourceProperty).toBe("1"); - expect(importedBuilder.properties[1].destinationProperty).toBe("province"); - - expect(importedBuilder.properties[2].normalizers.length).toBe(0) - expect(importedBuilder.properties[2].excludedNormalizers.size).toBe(0) - expect(importedBuilder.properties[2].isOptional).toBeFalsy(); - expect(importedBuilder.properties[2].sourceProperty).toBe("2"); - expect(importedBuilder.properties[2].destinationProperty).toBe("total"); - }) -}); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.builder.ts b/packages/data-transformer/src/transformers/data-transformer.builder.ts deleted file mode 100644 index 49d1b627b..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.builder.ts +++ /dev/null @@ -1,156 +0,0 @@ -import {injectable} from "tsyringe"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerProperty} from "./data-transformer.property"; -import {moduleScoped} from "@pristine-ts/common"; -import {DataTransformerModuleKeyname} from "../data-transformer.module.keyname"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import { - DataAfterRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-after-row-transformer-interceptor-already-added.error"; -import { - DataBeforeRowTransformerInterceptorAlreadyAddedError -} from "../errors/data-before-row-transformer-interceptor-already-added.error"; - -@moduleScoped(DataTransformerModuleKeyname) -@injectable() -export class DataTransformerBuilder { - public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; - public beforeRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - public afterRowTransformInterceptors: { key: DataTransformerInterceptorUniqueKeyType, options: any}[] = []; - - public properties: {[sourceProperty in string]: DataTransformerProperty} = {} - - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerBuilder { - if(this.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this builder.", normalizerUniqueKey, options); - } - - this.normalizers.push({ - key: normalizerUniqueKey, - options, - }); - return this; - } - - public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { - return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; - } - - public addBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataTransformerBuilder { - if(this.hasBeforeRowTransformInterceptor(key)) { - throw new DataBeforeRowTransformerInterceptorAlreadyAddedError("The before row transform interceptor has already been added to this builder.", key, options) - } - - this.beforeRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasBeforeRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.beforeRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public addAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType, options?: any): DataTransformerBuilder { - if(this.hasAfterRowTransformInterceptor(key)) { - throw new DataAfterRowTransformerInterceptorAlreadyAddedError("The after row transform interceptor has already been added to this builder.", key, options) - } - - this.afterRowTransformInterceptors.push({ - key, - options, - }); - - return this; - } - - public hasAfterRowTransformInterceptor(key: DataTransformerInterceptorUniqueKeyType): boolean { - return this.afterRowTransformInterceptors.find(element => element.key === key) !== undefined; - } - - public add() { - return new DataTransformerProperty(this); - } - - public addNewProperty(property: DataTransformerProperty) { - this.properties[property.sourceProperty] = property; - } - - public import(jsonString: string) { - const object = JSON.parse(jsonString); - - if(object.hasOwnProperty("normalizers") && Array.isArray(object.normalizers)) { - this.normalizers = object.normalizers; - } - - if(object.hasOwnProperty("beforeRowTransformInterceptors") && Array.isArray(object.beforeRowTransformInterceptors)) { - this.beforeRowTransformInterceptors = object.beforeRowTransformInterceptors; - } - - if(object.hasOwnProperty("afterRowTransformInterceptors") && Array.isArray(object.afterRowTransformInterceptors)) { - this.afterRowTransformInterceptors = object.afterRowTransformInterceptors; - } - - if(object.hasOwnProperty("properties") && typeof object.properties === "object") { - for(const key in object.properties) { - if(object.properties.hasOwnProperty(key) === false) { - continue; - } - - const property = object.properties[key]; - const newProperty = this.add(); - - newProperty.normalizers = property.normalizers; - if(property.hasOwnProperty("excludedNormalizers")) { - for(const item in property.excludedNormalizers) { - newProperty.excludeNormalizer(item); - } - } - - newProperty.isOptional = property.isOptional; - newProperty.sourceProperty = property.sourceProperty; - newProperty.destinationProperty = property.destinationProperty; - - newProperty.end(); - } - } - - return this; - } - - public export(): string { - const properties: any = {}; - - for (const key in this.properties) { - if(this.properties.hasOwnProperty(key) === false) { - continue; - } - - const property = this.properties[key]; - - const excludedNormalizers: any = {} - - for(const element of property.excludedNormalizers.values()) { - excludedNormalizers[element] = true; - } - - properties[key] = { - "sourceProperty": property.sourceProperty, - "destinationProperty": property.destinationProperty, - "isOptional": property.isOptional, - "normalizers": property.normalizers, - "excludedNormalizers": excludedNormalizers, - }; - } - - return JSON.stringify({ - "normalizers": this.normalizers, - "beforeRowTransformInterceptors": this.beforeRowTransformInterceptors, - "afterRowTransformInterceptors": this.afterRowTransformInterceptors, - "properties": properties, - }); - } -} diff --git a/packages/data-transformer/src/transformers/data-transformer.property.spec.ts b/packages/data-transformer/src/transformers/data-transformer.property.spec.ts deleted file mode 100644 index 576cf44e4..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.property.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import {DataTransformerProperty} from "./data-transformer.property"; -import {DataTransformerBuilder} from "./data-transformer.builder"; - -describe('Data Transformer Property', () => { - const dataTransformerBuilder: DataTransformerBuilder = new DataTransformerBuilder(); - let dataTransformerProperty: DataTransformerProperty; - - beforeEach(() => { - dataTransformerProperty = new DataTransformerProperty(dataTransformerBuilder); - }) - - it("should set the Source Property correctly", () => { - dataTransformerProperty.setSourceProperty("property"); - - expect(dataTransformerProperty.sourceProperty).toBe("property") - }) - - it("should set the Destination Property correctly", () => { - dataTransformerProperty.setDestinationProperty("property"); - - expect(dataTransformerProperty.destinationProperty).toBe("property") - }) - - it("should add a normalizer correctly", () => { - const options = { - "optionA": true, - }; - - dataTransformerProperty.addNormalizer("normalizer", options); - - expect(dataTransformerProperty.normalizers.length).toBe(1); - expect(dataTransformerProperty.normalizers[0].key).toBe("normalizer") - expect(dataTransformerProperty.normalizers[0].options).toBe(options) - }) - - it("should return correctly if it has a normalizer correctly", () => { - const options = { - "optionA": true, - }; - - dataTransformerProperty.addNormalizer("normalizer", options); - - expect(dataTransformerProperty.hasNormalizer("normalizer")).toBeTruthy(); - expect(dataTransformerProperty.hasNormalizer("dafds")).toBeFalsy(); - }) - - it("should add an excluded normalizer correctly", () => { - const options = { - "optionA": true, - }; - - dataTransformerProperty.excludeNormalizer("normalizer"); - - expect(dataTransformerProperty.excludedNormalizers.size).toBe(1); - expect(dataTransformerProperty.excludedNormalizers.has("normalizer")).toBeTruthy() - }) - - it("should set the IsOptional property", () => { - dataTransformerProperty.setIsOptional(true); - - expect(dataTransformerProperty.isOptional).toBeTruthy(); - }) - - it("should properly return the builder when calling end()", () => { - expect(dataTransformerProperty.end()).toBe(dataTransformerBuilder); - }) -}); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data-transformer.property.ts b/packages/data-transformer/src/transformers/data-transformer.property.ts deleted file mode 100644 index 2cd4b51bb..000000000 --- a/packages/data-transformer/src/transformers/data-transformer.property.ts +++ /dev/null @@ -1,67 +0,0 @@ -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataNormalizerAlreadyAdded} from "../errors/data-normalizer-already-added.error"; -import {DataTransformerBuilder} from "./data-transformer.builder"; - -export class DataTransformerProperty { - public sourceProperty!: string; - public destinationProperty!: string; - public normalizers: { key: DataNormalizerUniqueKey, options: any}[] = []; - public excludedNormalizers: Set = new Set(); - public isOptional: boolean = false; - - public constructor(private readonly builder: DataTransformerBuilder) { - } - - public setSourceProperty(sourceProperty: string): DataTransformerProperty { - this.sourceProperty = sourceProperty; - return this; - } - public setDestinationProperty(destinationProperty: string): DataTransformerProperty { - this.destinationProperty = destinationProperty; - return this; - } - - public addNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey, options?: any): DataTransformerProperty { - if(this.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey, options) - } - - if(this.builder.hasNormalizer(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The data normalizer '" + normalizerUniqueKey + "' has already been added to the builder and cannot be also added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey, options) - } - - this.normalizers.push({ - key: normalizerUniqueKey, - options, - }); - - return this; - } - - public hasNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): boolean { - return this.normalizers.find(element => element.key === normalizerUniqueKey) !== undefined; - } - - public excludeNormalizer(normalizerUniqueKey: DataNormalizerUniqueKey): DataTransformerProperty { - if(this.excludedNormalizers.has(normalizerUniqueKey)) { - throw new DataNormalizerAlreadyAdded("The EXCLUDED data normalizer '" + normalizerUniqueKey + "' has already been added to this source property: '" + this.sourceProperty + "'.", normalizerUniqueKey) - } - - this.excludedNormalizers.add(normalizerUniqueKey); - - return this; - } - - public setIsOptional(isOptional: boolean): DataTransformerProperty { - this.isOptional = isOptional; - - return this; - } - - public end(): DataTransformerBuilder { - this.builder.addNewProperty(this); - - return this.builder; - } - -} \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data.transformer.spec.ts b/packages/data-transformer/src/transformers/data.transformer.spec.ts deleted file mode 100644 index 6c7280571..000000000 --- a/packages/data-transformer/src/transformers/data.transformer.spec.ts +++ /dev/null @@ -1,194 +0,0 @@ -import "reflect-metadata" -import {DataTransformer} from "./data.transformer"; -import {LowercaseNormalizer} from "../normalizers/lowercase.normalizer"; -import {DataTransformerBuilder} from "./data-transformer.builder"; -import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import {DataTransformerRow} from "../types/data-transformer.row"; -import 'jest-extended'; -import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; -import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; - -describe('Data Transformer', () => { - it("should properly transform", async () => { - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); - - const source = [{ - NAME: "Etienne Noel", - province: "QUEBEC", - TOTAL: 10, - }]; - - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder - .add() - .setSourceProperty("NAME") - .setDestinationProperty("name") - .end() - .add() - .setSourceProperty("province") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() - .add() - .setSourceProperty("TOTAL") - .setDestinationProperty("total") - .end(); - - const destination = await dataTransformer.transform(dataTransformerBuilder, source); - - expect(destination.length).toBe(1) - - expect(destination[0].name).toBeDefined() - expect(destination[0].name).toBe("Etienne Noel"); - expect(destination[0].province).toBe("quebec"); - expect(destination[0].total).toBe(10); - }) - - it("should properly transform an array with numerical indices", async () => { - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); - - const source = [ - ["Etienne Noel", "QUEBEC", 10], - ]; - - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder - .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .end() - .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() - .add() - .setSourceProperty("2") - .setDestinationProperty("total") - .end(); - - const destination = await dataTransformer.transform(dataTransformerBuilder, source); - - expect(destination.length).toBe(1) - - expect(destination[0].name).toBeDefined() - expect(destination[0].name).toBe("Etienne Noel"); - expect(destination[0].province).toBe("quebec"); - expect(destination[0].total).toBe(10); - }) - - it("should properly call the before row transformers and respect the order of calls", async () => { - const firstInterceptor: DataTransformerInterceptorInterface = { - async beforeRowTransform(row: DataTransformerRow): Promise { - return row; - }, - async afterRowTransform(row: DataTransformerRow): Promise { - return { - "after": row["name"] + row["province"] + row["total"] + row["added_property"], - }; - }, - getUniqueKey(): DataTransformerInterceptorUniqueKeyType { - return "first_interceptor"; - }, - } - const secondInterceptor: DataTransformerInterceptorInterface = { - async beforeRowTransform(row: DataTransformerRow): Promise { - row[3] = "Property added in the beforeRowTransform"; - return row; - }, - async afterRowTransform(row: DataTransformerRow): Promise { - return row; - }, - getUniqueKey(): DataTransformerInterceptorUniqueKeyType { - return "second_interceptor"; - }, - } - - const beforeRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "beforeRowTransform") - const afterRowFirstInterceptorSpy = jest.spyOn(firstInterceptor, "afterRowTransform") - const beforeRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "beforeRowTransform") - const afterRowSecondInterceptorSpy = jest.spyOn(secondInterceptor, "afterRowTransform") - - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], [ - firstInterceptor, - secondInterceptor, - ]); - - const source = [ - ["Etienne Noel", "QUEBEC", 10], - ]; - - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder - .addBeforeRowTransformInterceptor("first_interceptor") - .addBeforeRowTransformInterceptor("second_interceptor") - .addAfterRowTransformInterceptor("first_interceptor") - .addAfterRowTransformInterceptor("second_interceptor") - .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .end() - .add() - .setSourceProperty("1") - .addNormalizer(LowercaseNormalizer.name) - .setDestinationProperty("province") - .end() - .add() - .setSourceProperty("2") - .setDestinationProperty("total") - .end() - .add() - .setSourceProperty("3") - .setDestinationProperty("added_property") - .end(); - - const transformedData: any[] = await dataTransformer.transform(dataTransformerBuilder, source); - - expect(beforeRowFirstInterceptorSpy).toHaveBeenCalledBefore(beforeRowSecondInterceptorSpy); - expect(beforeRowSecondInterceptorSpy).toHaveBeenCalledBefore(afterRowFirstInterceptorSpy) - expect(afterRowFirstInterceptorSpy).toHaveBeenCalledBefore(afterRowSecondInterceptorSpy); - - expect(transformedData[0]["after"]).toBe("Etienne Noelquebec10Property added in the beforeRowTransform") - }) - - it("should throw properly when before row transformer cannot be found", async () => { - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); - - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder - .addBeforeRowTransformInterceptor("first_interceptor") - .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .end() - - await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); - }) - - it("should throw properly when after row transformer cannot be found", async () => { - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); - - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder - .addAfterRowTransformInterceptor("first_interceptor") - .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .end() - - await expect(dataTransformer.transform(dataTransformerBuilder, [{"0": "a"}])).rejects.toThrowError(DataTransformerInterceptorNotFoundError); - }) - it("should throw properly when an element is not optional and not found in the source", async () => { - const dataTransformer = new DataTransformer([new LowercaseNormalizer()], []); - - const dataTransformerBuilder = new DataTransformerBuilder(); - dataTransformerBuilder - .add() - .setSourceProperty("0") - .setDestinationProperty("name") - .end() - - await expect(dataTransformer.transform(dataTransformerBuilder, [{"a": "a"}])).rejects.toThrowError(DataTransformerSourcePropertyNotFoundError); - }) -}); \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/data.transformer.ts b/packages/data-transformer/src/transformers/data.transformer.ts deleted file mode 100644 index 2569d97b0..000000000 --- a/packages/data-transformer/src/transformers/data.transformer.ts +++ /dev/null @@ -1,103 +0,0 @@ -import {injectable, injectAll} from "tsyringe"; -import {moduleScoped, tag} from "@pristine-ts/common"; -import {DataTransformerModuleKeyname} from "../data-transformer.module.keyname"; -import {DataNormalizerInterface} from "../interfaces/data-normalizer.interface"; -import {DataTransformerBuilder} from "./data-transformer.builder"; -import {DataTransformerProperty} from "./data-transformer.property"; -import {DataTransformerSourcePropertyNotFoundError} from "../errors/data-transformer-source-property-not-found.error"; -import {DataNormalizerUniqueKey} from "../types/data-normalizer-unique-key.type"; -import {DataTransformerRow} from "../types/data-transformer.row"; -import {DataTransformerInterceptorUniqueKeyType} from "../types/data-transformer-interceptor-unique-key.type"; -import {DataTransformerInterceptorInterface} from "../interfaces/data-transformer-interceptor.interface"; -import {DataTransformerInterceptorNotFoundError} from "../errors/data-transformer-interceptor-not-found.error"; - -@moduleScoped(DataTransformerModuleKeyname) -@injectable() -export class DataTransformer { - private readonly dataNormalizersMap: { [key in DataNormalizerUniqueKey]: DataNormalizerInterface} = {} - private readonly dataTransformerInterceptorsMap: { [key in DataTransformerInterceptorUniqueKeyType]: DataTransformerInterceptorInterface} = {} - - public constructor(@injectAll("DataNormalizerInterface") private readonly dataNormalizers: DataNormalizerInterface[], - @injectAll("DataTransformerInterceptor") private readonly dataTransformerInterceptors: DataTransformerInterceptorInterface[],) { - dataNormalizers.forEach(dataNormalizer => { - this.dataNormalizersMap[dataNormalizer.getUniqueKey()] = dataNormalizer; - }) - - dataTransformerInterceptors.forEach(interceptor => { - this.dataTransformerInterceptorsMap[interceptor.getUniqueKey()] = interceptor; - }); - } - - public async transform(builder: DataTransformerBuilder, source: (DataTransformerRow)[]): Promise { - const globalNormalizers = builder.normalizers; - - const destination: DataTransformerRow[] = []; - - let row: DataTransformerRow = {}; - for(const key in source) { - if(source.hasOwnProperty(key) === false) { - continue; - } - - let interceptedInputRow = source[key]; - - // Execute the before row interceptors. - for(const element of builder.beforeRowTransformInterceptors) { - const interceptor = this.dataTransformerInterceptorsMap[element.key]; - - if(interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); - } - - // todo: Pass the options when we start using them. - interceptedInputRow = await interceptor.beforeRowTransform(interceptedInputRow); - } - - // Loop over the properties defined in the builder - for (const key in builder.properties) { - if(builder.properties.hasOwnProperty(key) === false) { - continue; - } - - const property: DataTransformerProperty = builder.properties[key]; - if(interceptedInputRow.hasOwnProperty(property.sourceProperty) === false) { - if(property.isOptional) { - continue; - } - - throw new DataTransformerSourcePropertyNotFoundError("The property '" + key + "' isn't found in the Source object and isn't marked as Optional. If you want to ignore this property, use the 'setIsOptional(true)' method in the builder.", key) - } - - let value = interceptedInputRow[property.sourceProperty]; - - // Remove the normalizers part of the excludedNormalizers - const normalizers = globalNormalizers.filter(element => property.excludedNormalizers.has(element.key) === false); - normalizers.push(...property.normalizers); - - normalizers.forEach(element => { - const dataNormalizer = this.dataNormalizersMap[element.key]; - value = dataNormalizer.normalize(value, element.options); - }) - - // Assign the resulting value in the destination - row[property.destinationProperty] = value; - } - - // Execute the before row interceptors. - for(const element of builder.afterRowTransformInterceptors) { - const interceptor: DataTransformerInterceptorInterface = this.dataTransformerInterceptorsMap[element.key]; - - if(interceptor === undefined) { - throw new DataTransformerInterceptorNotFoundError("The interceptor wasn't found and cannot be loaded.", element.key); - } - - // todo pass the options when we start using it. - row = await interceptor.afterRowTransform(row); - } - - destination.push(row); - } - - return destination; - } -} \ No newline at end of file diff --git a/packages/data-transformer/src/transformers/transformers.ts b/packages/data-transformer/src/transformers/transformers.ts deleted file mode 100644 index 458cda5bd..000000000 --- a/packages/data-transformer/src/transformers/transformers.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./data-transformer.builder"; -export * from "./data-transformer.property"; - -export * from "./data.transformer"; \ No newline at end of file diff --git a/packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts b/packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts deleted file mode 100644 index ca2901216..000000000 --- a/packages/data-transformer/src/types/data-transformer-interceptor-unique-key.type.ts +++ /dev/null @@ -1 +0,0 @@ -export type DataTransformerInterceptorUniqueKeyType = string; \ No newline at end of file diff --git a/packages/data-transformer/src/types/data-transformer.row.ts b/packages/data-transformer/src/types/data-transformer.row.ts deleted file mode 100644 index bb6ffc7de..000000000 --- a/packages/data-transformer/src/types/data-transformer.row.ts +++ /dev/null @@ -1 +0,0 @@ -export type DataTransformerRow = {[key in string]: any} | any; \ No newline at end of file diff --git a/packages/data-transformer/src/types/types.ts b/packages/data-transformer/src/types/types.ts deleted file mode 100644 index ccceae527..000000000 --- a/packages/data-transformer/src/types/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./data-normalizer-unique-key.type"; -export * from "./data-transformer.row"; -export * from "./data-transformer-interceptor-unique-key.type"; \ No newline at end of file