-
Notifications
You must be signed in to change notification settings - Fork 7
/
invokers.ts
173 lines (164 loc) · 4.5 KB
/
invokers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import {
asFunction,
Resolver,
AwilixContainer,
ResolverOptions,
Constructor,
asClass,
ClassOrFunctionReturning,
FunctionReturning
} from 'awilix'
import { isClass } from 'awilix/lib/utils'
import { NextFunction, Request, Response } from 'express'
import assert = require('assert')
/**
* Creates either a function invoker or a class invoker, based on whether
* the argument can be classified as a class or not. Uses Awilix' `isClass` utility.
*
* @param functionOrClass
* The function or class to invoke.
*
* @param opts
* Resolver options for the class/function.
*/
export function makeInvoker<T>(
functionOrClass: ClassOrFunctionReturning<T>,
opts?: ResolverOptions<T>
) {
return isClass(functionOrClass)
? /*tslint:disable-next-line*/
makeClassInvoker(functionOrClass as Constructor<T>, opts)
: /*tslint:disable-next-line*/
makeFunctionInvoker(functionOrClass as FunctionReturning<T>, opts)
}
/**
* Returns a function that when called with a name,
* returns another function to be used as Express middleware.
* That function will run `fn` with the container cradle as the
* only parameter, and then call the `methodToInvoke` on
* the result.
*
* @param, {Function} fn
* @return {(methodToInvoke: string) => (req) => void}
*/
export function makeFunctionInvoker<T>(
fn: FunctionReturning<T>,
opts?: ResolverOptions<T>
) {
return makeResolverInvoker(asFunction(fn, opts))
}
/**
* Same as `makeInvoker` but for classes.
*
* @param {Class} Class
* @return {(methodToInvoke: string) => (req) => void}
*/
export function makeClassInvoker<T>(
Class: Constructor<T>,
opts?: ResolverOptions<T>
) {
return makeResolverInvoker(asClass(Class, opts))
}
/**
* Returns a function that when called with a method name,
* returns another function to be used as Express middleware.
* That function will run `container.build(resolver)`, and
* then call the method on the result, passing in the Express request
* and rest of the parameters.
*
* @param, {Resolver} resolver
* @return {(methodToInvoke: string) => (req) => void}
*/
export function makeResolverInvoker<T>(resolver: Resolver<T>) {
/**
* 2nd step is to create a method to invoke on the result
* of the resolver.
*
* @param {MethodName} methodToInvoke
* @return {(req) => void}
*/
return function makeMemberInvoker<K extends keyof T>(methodToInvoke: K) {
/**
* The invoker middleware.
*
* @param {E.Request} req
* @param {...*} rest
* @return {*}
*/
return function memberInvoker(req: any, ...rest: any[]) {
const container: AwilixContainer = req.container
const resolved: any = container.build(resolver)
assert(
methodToInvoke,
`methodToInvoke must be a valid method type, such as string, number or symbol, but was ${String(
methodToInvoke
)}`
)
if (!resolved[methodToInvoke!]) {
throw Error(
`The method attempting to be invoked, '${String(
methodToInvoke
)}', does not exist on the controller`
)
}
return asyncErrorWrapper(resolved[methodToInvoke!].bind(resolved))(
req,
...rest
)
}
}
}
/**
* Injects dependencies into the middleware factory when the middleware is invoked.
*
* @param factory
*/
export function inject(factory: ClassOrFunctionReturning<any> | Resolver<any>) {
const resolver = getResolver(factory)
/**
* The invoker middleware.
*/
return function middlewareFactoryHandler(req: any, ...rest: any[]) {
const container: AwilixContainer = req.container
const resolved: any = container.build(resolver)
return asyncErrorWrapper(resolved)(req, ...rest)
}
}
/**
* Wraps or returns a resolver.
*/
function getResolver<T>(
arg: ClassOrFunctionReturning<T> | Resolver<T>
): Resolver<T> {
if (typeof arg === 'function') {
/*tslint:disable-next-line*/
return asFunction(arg as any)
}
return arg
}
/**
* Wraps a middleware, detects if it returns a Promise and calls next() with the caught error, if necessary.
* @param fn
*/
function asyncErrorWrapper(
fn: (...args: any[]) => any
): (...args: any[]) => any {
return function asyncWrappedMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
const returnValue = fn(req, res, next)
if (
returnValue &&
returnValue.catch &&
typeof returnValue.catch === 'function'
) {
return returnValue.catch((err: any) => {
next(err)
})
} else {
return returnValue
}
}
}