diff --git a/README.md b/README.md index d7dd18b0..6626a260 100644 --- a/README.md +++ b/README.md @@ -1398,6 +1398,39 @@ export class UsersController { } ``` +For other IoC providers that don't expose a `get(xxx)` function, you can create an IoC adapter using `IocAdapter` like so: + +```typescript +// inversify-adapter.ts +import { IoCAdapter } from 'routing-controllers' +import { Container } from 'inversify' + +class InversifyAdapter implements IocAdapter { + constructor ( + private readonly container: Container + ) { + } + + get (someClass: ClassConstructor, action?: Action): T { + const childContainer = this.container.createChild() + childContainer.bind(API_SYMBOLS.ClientIp).toConstantValue(action.context.ip) + return childContainer.resolve(someClass) + } +} +``` + +And then tell Routing Controllers to use it: +```typescript +// Somewhere in your app startup +import { useContainer } from 'routing-controllers' +import { Container } from 'inversify' +import { InversifyAdapter } from './inversify-adapter.ts' + +const container = new Container() +const inversifyAdapter = new InversifyAdapter(container) +useContainer(inversifyAdapter) +``` + ## Custom parameter decorators You can create your own parameter decorators. diff --git a/package-lock.json b/package-lock.json index 44644d79..dbb6e4c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "routing-controllers", + "name": "@gritcode/routing-controllers", "version": "0.8.0", "lockfileVersion": 1, "requires": true, diff --git a/package.json b/package.json index 0a0d8a92..aa46fa29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "routing-controllers", - "private": true, + "name": "@gritcode/routing-controllers", + "private": false, "version": "0.8.0", "description": "Create structured, declarative and beautifully organized class-based controllers with heavy decorators usage for Express / Koa using TypeScript.", "license": "MIT", @@ -38,6 +38,10 @@ "reflect-metadata": "^0.1.13", "template-url": "^1.0.0" }, + "peerDependencies": { + "class-transformer": "^0.2.3", + "class-validator": "0.10.1" + }, "devDependencies": { "@types/chai": "^4.2.3", "@types/chai-as-promised": "7.1.2", @@ -82,10 +86,6 @@ "typedi": "~0.8.0", "typescript": "~3.6.3" }, - "peerDependencies": { - "class-transformer": "^0.2.3", - "class-validator": "0.10.1" - }, "scripts": { "build": "rimraf build && echo Using TypeScript && tsc --version && tsc --pretty", "clean": "rimraf build coverage", diff --git a/src/RoutingControllers.ts b/src/RoutingControllers.ts index 0fb3cf6b..914c15b9 100644 --- a/src/RoutingControllers.ts +++ b/src/RoutingControllers.ts @@ -109,7 +109,6 @@ export class RoutingControllers { * Executes given controller action. */ protected executeAction(actionMetadata: ActionMetadata, action: Action, interceptorFns: Function[]) { - // compute all parameters const paramsPromises = actionMetadata.params .sort((param1, param2) => param1.index - param2.index) @@ -120,7 +119,7 @@ export class RoutingControllers { // execute action and handle result const allParams = actionMetadata.appendParams ? actionMetadata.appendParams(action).concat(params) : params; - const result = actionMetadata.methodOverride ? actionMetadata.methodOverride(actionMetadata, action, allParams) : actionMetadata.callMethod(allParams); + const result = actionMetadata.methodOverride ? actionMetadata.methodOverride(actionMetadata, action, allParams) : actionMetadata.callMethod(allParams, action); return this.handleCallMethodResult(result, actionMetadata, action, interceptorFns); }).catch(error => { @@ -172,7 +171,7 @@ export class RoutingControllers { return uses.map(use => { if (use.interceptor.prototype && use.interceptor.prototype.intercept) { // if this is function instance of InterceptorInterface return function (action: Action, result: any) { - return (getFromContainer(use.interceptor) as InterceptorInterface).intercept(action, result); + return (getFromContainer(use.interceptor, action) as InterceptorInterface).intercept(action, result); }; } return use.interceptor; diff --git a/src/container.ts b/src/container.ts index cc18963d..15b75f03 100644 --- a/src/container.ts +++ b/src/container.ts @@ -1,3 +1,4 @@ +import { Action } from "./Action"; /** * Container options. @@ -16,13 +17,15 @@ export interface UseContainerOptions { } +export type ClassConstructor = { new (...args: any[]): T }; + /** * Container to be used by this library for inversion control. If container was not implicitly set then by default * container simply creates a new instance of the given class. */ -const defaultContainer: { get(someClass: { new (...args: any[]): T }|Function): T } = new (class { +const defaultContainer: { get(someClass: ClassConstructor | Function): T } = new (class { private instances: { type: Function, object: any }[] = []; - get(someClass: { new (...args: any[]): T }): T { + get(someClass: ClassConstructor): T { let instance = this.instances.find(instance => instance.type === someClass); if (!instance) { instance = { type: someClass, object: new someClass() }; @@ -33,24 +36,42 @@ const defaultContainer: { get(someClass: { new (...args: any[]): T }|Function } })(); -let userContainer: { get(someClass: { new (...args: any[]): T }|Function): T }; +let userContainer: { get( + someClass: ClassConstructor | Function, + action?: Action +): T }; let userContainerOptions: UseContainerOptions; +/** + * Allows routing controllers to resolve objects using your IoC container + */ +export interface IocAdapter { + /** + * Return + */ + get (someClass: ClassConstructor, action?: Action): T; +} + /** * Sets container to be used by this library. */ -export function useContainer(iocContainer: { get(someClass: any): any }, options?: UseContainerOptions) { - userContainer = iocContainer; +export function useContainer(iocAdapter: IocAdapter, options?: UseContainerOptions) { + userContainer = iocAdapter; userContainerOptions = options; } /** * Gets the IOC container used by this library. + * @param someClass A class constructor to resolve + * @param action The request/response context that `someClass` is being resolved for */ -export function getFromContainer(someClass: { new (...args: any[]): T }|Function): T { +export function getFromContainer( + someClass: ClassConstructor | Function, + action?: Action +): T { if (userContainer) { try { - const instance = userContainer.get(someClass); + const instance = userContainer.get(someClass, action); if (instance) return instance; diff --git a/src/driver/koa/KoaDriver.ts b/src/driver/koa/KoaDriver.ts index c0d91b93..4d8a1761 100644 --- a/src/driver/koa/KoaDriver.ts +++ b/src/driver/koa/KoaDriver.ts @@ -79,7 +79,7 @@ export class KoaDriver extends BaseDriver { const action: Action = { request: context.request, response: context.response, context, next }; try { const checkResult = actionMetadata.authorizedRoles instanceof Function ? - getFromContainer(actionMetadata.authorizedRoles).check(action) : + getFromContainer(actionMetadata.authorizedRoles, action).check(action) : this.authorizationChecker(action, actionMetadata.authorizedRoles); const handleError = (result: any) => { @@ -331,7 +331,7 @@ export class KoaDriver extends BaseDriver { if (use.middleware.prototype && use.middleware.prototype.use) { // if this is function instance of MiddlewareInterface middlewareFunctions.push((context: any, next: (err?: any) => Promise) => { try { - const useResult = (getFromContainer(use.middleware) as KoaMiddlewareInterface).use(context, next); + const useResult = (getFromContainer(use.middleware, { context } as Action) as KoaMiddlewareInterface).use(context, next); if (isPromiseLike(useResult)) { useResult.catch((error: any) => { this.handleError(error, undefined, { diff --git a/src/metadata/ActionMetadata.ts b/src/metadata/ActionMetadata.ts index ab96861c..52b1a1b3 100644 --- a/src/metadata/ActionMetadata.ts +++ b/src/metadata/ActionMetadata.ts @@ -260,8 +260,8 @@ export class ActionMetadata { * Calls action method. * Action method is an action defined in a user controller. */ - callMethod(params: any[]) { - const controllerInstance = this.controllerMetadata.instance; + callMethod(params: any[], action: Action) { + const controllerInstance = this.controllerMetadata.getInstance(action); return controllerInstance[this.method].apply(controllerInstance, params); } diff --git a/src/metadata/ControllerMetadata.ts b/src/metadata/ControllerMetadata.ts index 3ecd2b3a..8abfafbd 100644 --- a/src/metadata/ControllerMetadata.ts +++ b/src/metadata/ControllerMetadata.ts @@ -4,6 +4,7 @@ import {UseMetadata} from "./UseMetadata"; import {getFromContainer} from "../container"; import {ResponseHandlerMetadata} from "./ResponseHandleMetadata"; import {InterceptorMetadata} from "./InterceptorMetadata"; +import { Action } from "../Action"; /** * Controller metadata. @@ -70,9 +71,10 @@ export class ControllerMetadata { /** * Gets instance of the controller. + * @param action Details around the request session */ - get instance(): any { - return getFromContainer(this.target); + getInstance(action: Action): any { + return getFromContainer(this.target, action); } // ------------------------------------------------------------------------- diff --git a/test/functional/container.spec.ts b/test/functional/container.spec.ts index d57cc02e..0b56051f 100644 --- a/test/functional/container.spec.ts +++ b/test/functional/container.spec.ts @@ -1,10 +1,11 @@ import "reflect-metadata"; import {JsonController} from "../../src/decorator/JsonController"; -import {createExpressServer, createKoaServer, getMetadataArgsStorage} from "../../src/index"; +import {createExpressServer, createKoaServer, getMetadataArgsStorage, Action} from "../../src/index"; import {assertRequest} from "./test-utils"; import {Container, Service} from "typedi"; -import {useContainer} from "../../src/container"; +import {useContainer, IocAdapter, ClassConstructor} from "../../src/container"; import {Get} from "../../src/decorator/Get"; +import * as assert from "assert"; const chakram = require("chakram"); const expect = chakram.expect; @@ -104,17 +105,26 @@ describe("container", () => { describe("using custom container should be possible", () => { + let fakeContainer: IocAdapter & { + services: { [key: string]: any } + context: any[] + }; + before(() => { - const fakeContainer = { - services: [] as any, + fakeContainer = { + services: {}, + context: [], + + get(service: ClassConstructor, action: Action): T { - get(service: any) { + this.context.push(action.context); + if (!this.services[service.name]) { this.services[service.name] = new service(); } - return this.services[service.name]; + return this.services[service.name] as T; } }; @@ -206,6 +216,10 @@ describe("container", () => { title: "post #2" }]); }); + + it("should pass the action through to the Ioc adapter", () => { + assert.notEqual(fakeContainer.context.length, 0); + }); }); describe("using custom container with fallback should be possible", () => {