diff --git a/jest.config.js b/jest.config.js index 879b80cea..90712df88 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,5 +4,6 @@ module.exports = { '/packages/classes', '/packages/integration-test', '/packages/pojos', + '/packages/nestjs', ], }; diff --git a/nx.json b/nx.json index 3b9b34ae9..2cf4c1e1c 100644 --- a/nx.json +++ b/nx.json @@ -42,6 +42,9 @@ }, "pojos": { "tags": [] + }, + "nestjs": { + "tags": [] } }, "workspaceLayout": { diff --git a/package-lock.json b/package-lock.json index c80b3e7da..2e447b80a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1061,6 +1061,35 @@ } } }, + "@nestjs/common": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-7.6.5.tgz", + "integrity": "sha512-WvBJd71ktaCRm9KTURVqn1YMyUzsOIkvezjP7WEpP9DVqQUOFVvn6/osJGZky/qL+zE4P7NBNyoXM94bpYvMwQ==", + "dev": true, + "requires": { + "axios": "0.21.1", + "iterare": "1.2.1", + "tslib": "2.0.3", + "uuid": "8.3.2" + }, + "dependencies": { + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "dev": true, + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } + } + }, "@nestjs/schematics": { "version": "7.2.6", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-7.2.6.tgz", @@ -7849,6 +7878,12 @@ "istanbul-lib-report": "^3.0.0" } }, + "iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "dev": true + }, "jake": { "version": "10.8.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", diff --git a/package.json b/package.json index 0be8337da..fb51a961f 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ ], "dependencies": {}, "devDependencies": { + "@nestjs/common": "7.6.5", "@nrwl/cli": "11.0.20", "@nrwl/eslint-plugin-nx": "11.0.20", "@nrwl/jest": "11.0.20", diff --git a/packages/classes/src/lib/classes.ts b/packages/classes/src/lib/classes.ts index 0a6dcd0ac..54158713e 100644 --- a/packages/classes/src/lib/classes.ts +++ b/packages/classes/src/lib/classes.ts @@ -1,9 +1,8 @@ import { createInitialMapping } from '@automapper/core'; import type { Dictionary, - ErrorHandler, Mapping, - MapPlugin, + MapPluginInitializer, } from '@automapper/types'; import { MappingClassId } from '@automapper/types'; import 'reflect-metadata'; @@ -21,9 +20,7 @@ import { instantiate, isClass } from './utils'; * * @param {ErrorHandler} errorHandler */ -export const classes = ( - errorHandler: ErrorHandler -): MapPlugin => { +export const classes: MapPluginInitializer = (errorHandler) => { // Initialize all the storages const metadataStorage = new ClassMetadataStorage(); const mappingStorage = new ClassMappingStorage(); diff --git a/packages/core/src/lib/create-mapper.ts b/packages/core/src/lib/create-mapper.ts index 318988a80..3a9c1c88a 100644 --- a/packages/core/src/lib/create-mapper.ts +++ b/packages/core/src/lib/create-mapper.ts @@ -31,7 +31,7 @@ export function createMapper({ name, pluginInitializer, namingConventions, - errorHandle: customErrorHandler, + errorHandler: customErrorHandler, }: CreateMapperOptions): Mapper { // default errorHandler to console.error const errorHandler = customErrorHandler || { handle: console.error }; diff --git a/packages/integration-test/src/lib/setup.spec.ts b/packages/integration-test/src/lib/setup.spec.ts index 6065af667..ca62713f2 100644 --- a/packages/integration-test/src/lib/setup.spec.ts +++ b/packages/integration-test/src/lib/setup.spec.ts @@ -13,7 +13,7 @@ export function setup( name, pluginInitializer, namingConventions, - errorHandle: { handle: spiedErrorHandle }, + errorHandler: { handle: spiedErrorHandle }, }); afterEach(() => { diff --git a/packages/nestjs/.eslintrc.json b/packages/nestjs/.eslintrc.json new file mode 100644 index 000000000..54ac39535 --- /dev/null +++ b/packages/nestjs/.eslintrc.json @@ -0,0 +1 @@ +{ "extends": "../../.eslintrc.json", "ignorePatterns": ["!**/*"], "rules": {} } diff --git a/packages/nestjs/README.md b/packages/nestjs/README.md new file mode 100644 index 000000000..253c534c9 --- /dev/null +++ b/packages/nestjs/README.md @@ -0,0 +1,7 @@ +# nestjs + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test nestjs` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/packages/nestjs/jest.config.js b/packages/nestjs/jest.config.js new file mode 100644 index 000000000..c7643c5a9 --- /dev/null +++ b/packages/nestjs/jest.config.js @@ -0,0 +1,15 @@ +module.exports = { + displayName: 'nestjs', + preset: '../../jest.preset.js', + globals: { + 'ts-jest': { + tsConfig: '/tsconfig.spec.json', + }, + }, + testEnvironment: 'node', + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/packages/nestjs', +}; diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json new file mode 100644 index 000000000..5b6280a35 --- /dev/null +++ b/packages/nestjs/package.json @@ -0,0 +1,8 @@ +{ + "name": "@automapper/nestjs", + "version": "0.0.1", + "peerDependencies": { + "@nestjs/common": "^7.0.0", + "@nestjs/core": "^7.0.0" + } +} diff --git a/packages/nestjs/src/index.ts b/packages/nestjs/src/index.ts new file mode 100644 index 000000000..20833a4e4 --- /dev/null +++ b/packages/nestjs/src/index.ts @@ -0,0 +1,4 @@ +export * from './lib/automapper.module'; +export * from './lib/interfaces'; +export * from './lib/di'; +export * from './lib/abstracts'; diff --git a/packages/nestjs/src/lib/abstracts/automapper-profile.abstract.ts b/packages/nestjs/src/lib/abstracts/automapper-profile.abstract.ts new file mode 100644 index 000000000..66fe9e1e9 --- /dev/null +++ b/packages/nestjs/src/lib/abstracts/automapper-profile.abstract.ts @@ -0,0 +1,12 @@ +import type { Mapper, MappingProfile } from '@automapper/types'; + +export abstract class AutomapperProfile { + protected mapper: Mapper; + + protected constructor(mapper: Mapper) { + this.mapper = mapper; + this.mapper.addProfile(this.mapProfile()); + } + + abstract mapProfile(): MappingProfile; +} diff --git a/packages/nestjs/src/lib/abstracts/index.ts b/packages/nestjs/src/lib/abstracts/index.ts new file mode 100644 index 000000000..8f82bcc62 --- /dev/null +++ b/packages/nestjs/src/lib/abstracts/index.ts @@ -0,0 +1 @@ +export * from './automapper-profile.abstract'; diff --git a/packages/nestjs/src/lib/automapper.module.ts b/packages/nestjs/src/lib/automapper.module.ts new file mode 100644 index 000000000..742b2edab --- /dev/null +++ b/packages/nestjs/src/lib/automapper.module.ts @@ -0,0 +1,19 @@ +import { DynamicModule, Global, Logger, Module } from '@nestjs/common'; +import type { AutomapperModuleOptions } from './interfaces'; +import { createAutomapperProviders } from './utils'; + +@Global() +@Module({}) +export class AutomapperModule { + private static readonly logger = new Logger(AutomapperModule.name); + + static forRoot(forRootOptions: AutomapperModuleOptions): DynamicModule { + const providers = createAutomapperProviders(forRootOptions, this.logger); + + return { + module: AutomapperModule, + providers, + exports: providers, + }; + } +} diff --git a/packages/nestjs/src/lib/di/get-mapper-token.ts b/packages/nestjs/src/lib/di/get-mapper-token.ts new file mode 100644 index 000000000..e0cc0aeb4 --- /dev/null +++ b/packages/nestjs/src/lib/di/get-mapper-token.ts @@ -0,0 +1,5 @@ +export const DEFAULT_MAPPER_TOKEN = 'automapper:nestjs:default'; + +export function getMapperToken(name?: string) { + return name ? `automapper:nestjs:${name}` : DEFAULT_MAPPER_TOKEN; +} diff --git a/packages/nestjs/src/lib/di/index.ts b/packages/nestjs/src/lib/di/index.ts new file mode 100644 index 000000000..1728e0098 --- /dev/null +++ b/packages/nestjs/src/lib/di/index.ts @@ -0,0 +1 @@ +export * from './get-mapper-token'; diff --git a/packages/nestjs/src/lib/interfaces/automapper-module-options.interface.ts b/packages/nestjs/src/lib/interfaces/automapper-module-options.interface.ts new file mode 100644 index 000000000..4dc52a28e --- /dev/null +++ b/packages/nestjs/src/lib/interfaces/automapper-module-options.interface.ts @@ -0,0 +1,15 @@ +import type { + CreateMapperOptions, + ErrorHandler, + NamingConvention, +} from '@automapper/types'; + +export interface AutomapperModuleOptions { + options: CreateMapperOptions[]; + globalErrorHandler?: ErrorHandler; + globalNamingConventions?: { + source: NamingConvention; + destination: NamingConvention; + }; + singular?: boolean; +} diff --git a/packages/nestjs/src/lib/interfaces/index.ts b/packages/nestjs/src/lib/interfaces/index.ts new file mode 100644 index 000000000..6e90b282a --- /dev/null +++ b/packages/nestjs/src/lib/interfaces/index.ts @@ -0,0 +1 @@ +export * from './automapper-module-options.interface'; diff --git a/packages/nestjs/src/lib/utils/create-automapper-providers.util.ts b/packages/nestjs/src/lib/utils/create-automapper-providers.util.ts new file mode 100644 index 000000000..afb2e4243 --- /dev/null +++ b/packages/nestjs/src/lib/utils/create-automapper-providers.util.ts @@ -0,0 +1,60 @@ +import { createMapper } from '@automapper/core'; +import type { Provider } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; +import { getMapperToken } from '../di'; +import type { AutomapperModuleOptions } from '../interfaces'; + +export function createAutomapperProviders( + forRootOptions: AutomapperModuleOptions, + logger: Logger +): Provider[] { + const { + options, + globalErrorHandler, + globalNamingConventions, + singular = false, + } = forRootOptions; + + if (singular) { + if (options.length > 1) { + const error = + 'singular cannot be set to true when you have more than one plugin'; + logger.error(error); + throw new Error(error); + } + + const [ + { name, namingConventions, errorHandler, pluginInitializer }, + ] = options; + const mapper = createMapper({ + name, + pluginInitializer, + errorHandler: errorHandler || + globalErrorHandler || { handle: logger.error }, + namingConventions: namingConventions || globalNamingConventions, + }); + + return [ + { + provide: getMapperToken(), + useValue: mapper, + }, + ]; + } + + return options.map( + ({ name, errorHandler, namingConventions, pluginInitializer }) => { + const mapper = createMapper({ + name, + pluginInitializer, + errorHandler: errorHandler || + globalErrorHandler || { handle: logger.error }, + namingConventions: namingConventions || globalNamingConventions, + }); + return { + provide: getMapperToken(mapper.name), + useValue: mapper, + }; + } + ); +} diff --git a/packages/nestjs/src/lib/utils/index.ts b/packages/nestjs/src/lib/utils/index.ts new file mode 100644 index 000000000..d540f3735 --- /dev/null +++ b/packages/nestjs/src/lib/utils/index.ts @@ -0,0 +1 @@ +export * from './create-automapper-providers.util' diff --git a/packages/nestjs/tsconfig.json b/packages/nestjs/tsconfig.json new file mode 100644 index 000000000..62ebbd946 --- /dev/null +++ b/packages/nestjs/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/nestjs/tsconfig.lib.json b/packages/nestjs/tsconfig.lib.json new file mode 100644 index 000000000..dfc4f8b6d --- /dev/null +++ b/packages/nestjs/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es6" + }, + "exclude": ["**/*.spec.ts"], + "include": ["**/*.ts"] +} diff --git a/packages/nestjs/tsconfig.spec.json b/packages/nestjs/tsconfig.spec.json new file mode 100644 index 000000000..559410b96 --- /dev/null +++ b/packages/nestjs/tsconfig.spec.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.spec.js", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/packages/types/src/options.ts b/packages/types/src/options.ts index 88cbcca04..8eadb3e3a 100644 --- a/packages/types/src/options.ts +++ b/packages/types/src/options.ts @@ -4,7 +4,7 @@ import type { Mapping, NamingConvention, } from './core'; -import type { MapPlugin } from './plugin'; +import type { MapPluginInitializer } from './plugin'; import type { Dictionary } from './utils'; export interface MapOptions< @@ -32,6 +32,6 @@ export interface CreateMapperOptions { source: NamingConvention; destination: NamingConvention; }; - pluginInitializer: (errorHandler: ErrorHandler) => MapPlugin; - errorHandle?: ErrorHandler; + pluginInitializer: MapPluginInitializer; + errorHandler?: ErrorHandler; } diff --git a/packages/types/src/plugin.ts b/packages/types/src/plugin.ts index 42d0a0638..81a4f0481 100644 --- a/packages/types/src/plugin.ts +++ b/packages/types/src/plugin.ts @@ -1,7 +1,10 @@ -import type { Mapping } from './core'; +import type { ErrorHandler, Mapping } from './core'; import type { CreateMapOptions } from './options'; import type { Dictionary } from './utils'; +/** + * How a plugin should provide + */ export interface MapPlugin { /** * Instruction on how a plugin should initialize a mapping for a pair of Source <> Destination @@ -48,3 +51,10 @@ export interface MapPlugin { */ dispose?(): void; } + +/** + * Plugin initializer + */ +export interface MapPluginInitializer { + (errorHandler: ErrorHandler): MapPlugin; +} diff --git a/tsconfig.base.json b/tsconfig.base.json index f569f8463..58d9d7734 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -10,36 +10,21 @@ "importHelpers": true, "target": "es2015", "module": "esnext", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2017", - "dom" - ], + "typeRoots": ["node_modules/@types"], + "lib": ["es2017", "dom"], "skipLibCheck": true, "skipDefaultLibCheck": true, "baseUrl": ".", "paths": { - "@automapper/core": [ - "packages/core/src/index.ts" - ], - "@automapper/classes": [ - "packages/classes/src/index.ts" - ], - "@automapper/types": [ - "packages/types/src/index.ts" - ], + "@automapper/core": ["packages/core/src/index.ts"], + "@automapper/classes": ["packages/classes/src/index.ts"], + "@automapper/types": ["packages/types/src/index.ts"], "@automapper/integration-test": [ "packages/integration-test/src/index.ts" ], - "@automapper/pojos": [ - "packages/pojos/src/index.ts" - ] + "@automapper/pojos": ["packages/pojos/src/index.ts"], + "@automapper/nestjs": ["packages/nestjs/src/index.ts"] } }, - "exclude": [ - "node_modules", - "tmp" - ] + "exclude": ["node_modules", "tmp"] } diff --git a/workspace.json b/workspace.json index 977f1bc38..eb35628a4 100644 --- a/workspace.json +++ b/workspace.json @@ -183,6 +183,47 @@ } } } + }, + "nestjs": { + "root": "packages/nestjs", + "sourceRoot": "packages/nestjs/src", + "projectType": "library", + "architect": { + "lint": { + "builder": "@nrwl/linter:eslint", + "options": { + "lintFilePatterns": [ + "packages/nestjs/**/*.ts" + ] + } + }, + "test": { + "builder": "@nrwl/jest:jest", + "outputs": [ + "coverage/packages/nestjs" + ], + "options": { + "jestConfig": "packages/nestjs/jest.config.js", + "passWithNoTests": true + } + }, + "build": { + "builder": "@nrwl/node:package", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/nestjs", + "tsConfig": "packages/nestjs/tsconfig.lib.json", + "packageJson": "packages/nestjs/package.json", + "main": "packages/nestjs/src/index.ts", + "assets": [ + "packages/nestjs/*.md" + ], + "buildableProjectDepsInPackageJsonType": "peerDependencies" + } + } + } } }, "cli": {