diff --git a/README.md b/README.md index 4fb26cb..e949d24 100644 --- a/README.md +++ b/README.md @@ -358,6 +358,7 @@ That concludes the tutorial! Hope you find it useful, I know I have. The package exports everything from `awilix-router-core` as well as the following **Koa middleware factories**: - `scopePerRequest(container)`: creates a scope per request. +- `attachContainer(container)`: permits use of awilix-koa without creating a scope per request. - `controller(decoratedClassOrController)`: registers routes and delegates to Koa Router. - `importControllers(router, pattern, opts)`: imports files matching a glob pattern, registers their exports as controllers, applying them to the supplied koa-router - `loadControllers(pattern, opts, router)`: loads files matching a glob pattern and registers their exports as controllers and returns a middleware for use with Koa diff --git a/src/controller.ts b/src/controller.ts index 9d4be5a..41e9a61 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -19,24 +19,30 @@ export type ConstructorOrControllerBuilder = | (new (...args: Array) => any) | IAwilixControllerBuilder +export interface InstanceOptions { + singleton?: boolean +} + /** * Registers one or multiple decorated controller classes. * * @param ControllerClass One or multiple "controller" classes * with decorators to register + * @param options */ export function controller( ControllerClass: | ConstructorOrControllerBuilder | Array, + options?: InstanceOptions ): Middleware { const router = new Router() if (Array.isArray(ControllerClass)) { ControllerClass.forEach((c) => - _registerController(router, getStateAndTarget(c)), + _registerController(router, options, getStateAndTarget(c)), ) } else { - _registerController(router, getStateAndTarget(ControllerClass)) + _registerController(router, options, getStateAndTarget(ControllerClass)) } return compose([router.routes(), router.allowedMethods()]) as any @@ -47,17 +53,19 @@ export function controller( * * @param router * @param pattern - * @param opts + * @param globOptions + * @param options */ export function importControllers( router: Router, pattern: string, - opts?: IOptions, + globOptions?: IOptions, + options?: InstanceOptions ): void { findControllers(pattern, { - ...opts, + ...globOptions, absolute: true, - }).forEach(_registerController.bind(null, router)) + }).forEach(_registerController.bind(null, router, options)) } /** @@ -65,16 +73,18 @@ export function importControllers( * This return value must be used with `Koa.use`, and is incompatible with `Router.use` * * @param pattern - * @param opts + * @param globOptions * @param router + * @param options */ export function loadControllers( pattern: string, - opts?: IOptions, + globOptions?: IOptions, router?: Router, + options?: InstanceOptions ): Middleware { const r = router || new Router() - importControllers(r, pattern, opts) + importControllers(r, pattern, globOptions, options) return compose([r.routes(), r.allowedMethods()]) as any } @@ -82,18 +92,20 @@ export function loadControllers( * Reads the config state and registers the routes in the router. * * @param router + * @param options * @param ControllerClass */ function _registerController( router: Router, - stateAndTarget: IStateAndTarget | null, + options?: InstanceOptions, + stateAndTarget?: IStateAndTarget | null, ): void { if (!stateAndTarget) { return } const { state, target } = stateAndTarget - const invoker = makeInvoker(target as any) + const invoker = makeInvoker(target as any, { lifetime: options?.singleton ? 'SINGLETON' : 'SCOPED' }) const rolledUp = rollUpState(state) rolledUp.forEach((methodCfg, methodName) => { methodCfg.verbs.forEach((httpVerb) => { diff --git a/src/invokers.ts b/src/invokers.ts index 22e3a71..88a18ff 100644 --- a/src/invokers.ts +++ b/src/invokers.ts @@ -38,7 +38,8 @@ export function makeInvoker( * only parameter, and then call the `methodToInvoke` on * the result. * - * @param, {Function} fn + * @param {Function} fn + * @param opts * @return {(methodToInvoke: string) => (ctx) => void} */ export function makeFunctionInvoker( @@ -51,7 +52,8 @@ export function makeFunctionInvoker( /** * Same as `makeInvoker` but for classes. * - * @param {Class} Class + * @param {Class} Class + * @param opts * @return {(methodToInvoke: string) => (ctx) => void} */ export function makeClassInvoker( @@ -68,10 +70,13 @@ export function makeClassInvoker( * then call the method on the result, passing in the Koa context * and `next()`. * - * @param, {Resolver} resolver + * @param {Resolver} resolver * @return {(methodToInvoke: string) => (ctx) => void} */ export function makeResolverInvoker(resolver: Resolver) { + const singleton = resolver.lifetime === 'SINGLETON' + let _resolved: any + /** * 2nd step is to create a method to invoke on the result * of the resolver. @@ -89,7 +94,21 @@ export function makeResolverInvoker(resolver: Resolver) { */ return function memberInvoker(ctx: any, ...rest: any[]) { const container: AwilixContainer = ctx.state.container - const resolved: any = container.build(resolver) + if (!container) { + throw new Error('Awilix container not found on Koa state object. Please ensure you use either scopePerRequest or attachContainer') + } + + let resolved: any + if (singleton) { + if (!_resolved) { + _resolved = container.build(resolver) + } + resolved = _resolved + } + else { + resolved = container.build(resolver) + } + assert( methodToInvoke, `methodToInvoke must be a valid method type, such as string, number or symbol, but was ${String( diff --git a/src/scope-per-request.ts b/src/scope-per-request.ts index 91b7eaa..4ea9cfb 100644 --- a/src/scope-per-request.ts +++ b/src/scope-per-request.ts @@ -16,3 +16,22 @@ export function scopePerRequest(container: AwilixContainer) { return next() } } + +/** + * Koa middleware factory that will simply attach the container + * to the context (ctx) state, with no additional scoping. + * + * You should only use one of either scopePerRequest or attachContainer. + * + * @param {AwilixContainer} container + * @return {Function} + */ +export function attachContainer(container: AwilixContainer) { + return function attachContainerMiddleware( + ctx: any, + next: import('koa').Next, + ) { + ctx.state.container = container + return next() + } +}