jsonrpc: Add ability to specify context via high-order functions

This commit is contained in:
Jerko Steiner 2019-07-31 01:10:27 +08:00
parent 401e5b481a
commit cfc6933e78
2 changed files with 24 additions and 4 deletions

View File

@ -5,12 +5,18 @@ import bodyParser from 'body-parser'
describe('jsonrpc', () => { describe('jsonrpc', () => {
interface IContext {
userId: number
}
interface IService { interface IService {
add(a: number, b: number): number add(a: number, b: number): number
delay(): Promise<void> delay(): Promise<void>
syncError(message: string): void syncError(message: string): void
asyncError(message: string): Promise<void> asyncError(message: string): Promise<void>
httpError(statusCode: number, message: string): Promise<void> httpError(statusCode: number, message: string): Promise<void>
addWithContext(a: number, b: number): (ctx: IContext) => number
} }
class Service implements IService { class Service implements IService {
@ -40,6 +46,9 @@ describe('jsonrpc', () => {
}] }]
throw err throw err
} }
addWithContext = (a: number, b: number) => (ctx: IContext): number => {
return a + b + ctx.userId
}
} }
function createApp() { function createApp() {
@ -51,14 +60,17 @@ describe('jsonrpc', () => {
'syncError', 'syncError',
'asyncError', 'asyncError',
'httpError', 'httpError',
])) 'addWithContext',
], req => ({userId: 1000})))
return app return app
} }
type ArgumentTypes<T> = T extends (...args: infer U) => infer R ? U : never type ArgumentTypes<T> = T extends (...args: infer U) => infer R ? U : never
type RetType<T> = T extends (...args: any[]) => infer R ? R : never type RetType<T> = T extends (...args: any[]) => infer R ? R : never
type UnwrapHOC<T> = T extends (...args: any[]) => infer R ? R : T
type RetProm<T> = T extends Promise<any> ? T : Promise<T> type RetProm<T> = T extends Promise<any> ? T : Promise<T>
type PromisifyReturnType<T> = (...a: ArgumentTypes<T>) => RetProm<RetType<T>> type PromisifyReturnType<T> = (...a: ArgumentTypes<T>) =>
RetProm<UnwrapHOC<RetType<T>>>
type Asyncified<T> = { type Asyncified<T> = {
[K in keyof T]: PromisifyReturnType<T[K]> [K in keyof T]: PromisifyReturnType<T[K]>
} }
@ -157,6 +169,10 @@ describe('jsonrpc', () => {
const response = await client.delay() const response = await client.delay()
expect(response).toEqual(undefined) expect(response).toEqual(undefined)
}) })
it('can use context', async () => {
const response = await client.addWithContext(5, 7)
expect(response).toEqual(1000 + 5 + 7)
})
it('handles synchronous notifications', async () => { it('handles synchronous notifications', async () => {
await request(createApp()) await request(createApp())
.post('/myService') .post('/myService')

View File

@ -95,9 +95,10 @@ function isPromise(value: any): value is Promise<unknown> {
* Adds middleware for handling JSON RPC requests. Expects JSON middleware to * Adds middleware for handling JSON RPC requests. Expects JSON middleware to
* already be configured. * already be configured.
*/ */
export function jsonrpc<T, F extends FunctionPropertyNames<T>>( export function jsonrpc<T, F extends FunctionPropertyNames<T>, Ctx>(
service: T, service: T,
functions: F[], functions: F[],
getContext: (req: Request) => Ctx,
) { ) {
const rpcService = pick(service, functions) const rpcService = pick(service, functions)
@ -135,6 +136,9 @@ export function jsonrpc<T, F extends FunctionPropertyNames<T>>(
let retValue let retValue
try { try {
retValue = (rpcService as any)[method](...params) retValue = (rpcService as any)[method](...params)
if (typeof retValue === 'function') {
retValue = retValue(getContext(req))
}
} catch (err) { } catch (err) {
return handleError(err, req, res, next) return handleError(err, req, res, next)
} }