Do not use proxy in createLocalClient, add bulk methods
This commit is contained in:
parent
d2a5a35543
commit
fbd7a2229b
@ -6,3 +6,4 @@ export * from './local'
|
||||
export * from './redux'
|
||||
export * from './remote'
|
||||
export * from './types'
|
||||
export * from './util'
|
||||
|
||||
@ -1,23 +1,30 @@
|
||||
import {TAsyncified, Contextual, ReverseContextual} from './types'
|
||||
import {Request} from 'express'
|
||||
import {TGetContext} from './express'
|
||||
import {getAllMethods} from './jsonrpc'
|
||||
|
||||
export type LocalClient<T> = TAsyncified<ReverseContextual<T>>
|
||||
|
||||
/**
|
||||
* Creates a local client for a specific service instance. The actual service
|
||||
* will be invoked as if it would be remotely. This helps keep the API similar
|
||||
* on the client- and server-side.
|
||||
*
|
||||
* The service argument is expected to be a class implementing the
|
||||
* Contextual<Service, Context> type. The first (context) argument will be
|
||||
* automatically removed from all methods in the service, and the supplied
|
||||
* context argument will be used instead.
|
||||
*/
|
||||
export function createLocalClient<T extends {}, Context>(
|
||||
service: T,
|
||||
context: Context,
|
||||
): TAsyncified<ReverseContextual<T>> {
|
||||
const proxy = new Proxy({}, {
|
||||
get(obj, prop) {
|
||||
return async function makeRequest(...args: any[]) {
|
||||
const result = (service as any)[prop](context, ...args)
|
||||
return result
|
||||
}
|
||||
},
|
||||
})
|
||||
return proxy as any
|
||||
): LocalClient<T> {
|
||||
return getAllMethods(service)
|
||||
.filter(prop => typeof service[prop] === 'function')
|
||||
.reduce((obj, prop) => {
|
||||
obj[prop] = function makeRequest(...args: any[]) {
|
||||
return (service as any)[prop](context, ...args)
|
||||
}
|
||||
return obj
|
||||
}, {} as any)
|
||||
}
|
||||
|
||||
107
packages/jsonrpc/src/util.test.ts
Normal file
107
packages/jsonrpc/src/util.test.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import * as util from './util'
|
||||
import express from 'express'
|
||||
import {Contextual} from './types'
|
||||
import {jsonrpc} from './express'
|
||||
import {noopLogger} from './test-utils'
|
||||
import {createClient} from './supertest'
|
||||
import {json} from 'body-parser'
|
||||
|
||||
describe('util', () => {
|
||||
|
||||
interface IS1 {
|
||||
add(a: number, b: number): number
|
||||
}
|
||||
|
||||
interface IS2 {
|
||||
mul(a: number, b: number): number
|
||||
concat(...str: string[]): string
|
||||
}
|
||||
|
||||
interface IContext {
|
||||
userId: number
|
||||
}
|
||||
|
||||
class Service1 implements Contextual<IS1, IContext> {
|
||||
add(cx: IContext, a: number, b: number) {
|
||||
return a + b + cx.userId
|
||||
}
|
||||
}
|
||||
|
||||
class Service2 implements Contextual<IS2, IContext> {
|
||||
mul(cx: IContext, a: number, b: number) {
|
||||
return a * b + cx.userId
|
||||
}
|
||||
concat(cx: IContext, ...str: string[]) {
|
||||
return str.join('') + cx.userId
|
||||
}
|
||||
}
|
||||
|
||||
const services = {
|
||||
s1: new Service1(),
|
||||
s2: new Service2(),
|
||||
}
|
||||
|
||||
describe('bulkCreateLocalClient', () => {
|
||||
it('creates a typed local client', async () => {
|
||||
const client = util.bulkCreateLocalClient(services, {userId: 10})
|
||||
|
||||
const r1: number = await client.s1.add(1, 2)
|
||||
expect(r1).toBe(13)
|
||||
|
||||
const r2: number = await client.s2.mul(2, 3)
|
||||
expect(r2).toBe(16)
|
||||
|
||||
const r3: string = await client.s2.concat('a', 'b')
|
||||
expect(r3).toBe('ab10')
|
||||
})
|
||||
})
|
||||
|
||||
describe('bulkCreateActions', () => {
|
||||
it('creates typed actions', async () => {
|
||||
const client = util.bulkCreateLocalClient(services, {userId: 10})
|
||||
const actions = util.bulkCreateActions(client)
|
||||
|
||||
const r1 = actions.s1.add(1, 2)
|
||||
const method1: 'add' = r1.method
|
||||
const status1: 'pending' = r1.status
|
||||
const type1: 's1' = r1.type
|
||||
const payload1: Promise<number> = r1.payload
|
||||
expect(type1).toBe('s1')
|
||||
expect(status1).toBe('pending')
|
||||
expect(method1).toBe('add')
|
||||
expect(await payload1).toBe(13)
|
||||
|
||||
const r2 = actions.s2.mul(2, 3)
|
||||
const method2: 'mul' = r2.method
|
||||
const status2: 'pending' = r2.status
|
||||
const type2: 's2' = r2.type
|
||||
const payload2: Promise<number> = r2.payload
|
||||
expect(type2).toBe('s2')
|
||||
expect(status2).toBe('pending')
|
||||
expect(method2).toBe('mul')
|
||||
expect(await payload2).toBe(16)
|
||||
})
|
||||
})
|
||||
|
||||
describe('bulkJSONRPC', () => {
|
||||
const getContext = () => ({userId: 10})
|
||||
function createApp(router: express.Router) {
|
||||
const app = express()
|
||||
app.use(json())
|
||||
app.use('/rpc', router)
|
||||
return app
|
||||
}
|
||||
|
||||
it('creates JSON RPC services', async () => {
|
||||
const router = util.bulkjsonrpc(
|
||||
jsonrpc(getContext, noopLogger),
|
||||
services,
|
||||
)
|
||||
const app = createApp(router)
|
||||
const client = createClient<IS1>(app, '/rpc/s1')
|
||||
const result: number = await client.add(1, 3)
|
||||
expect(result).toBe(14)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
48
packages/jsonrpc/src/util.ts
Normal file
48
packages/jsonrpc/src/util.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import {IJSONRPCReturnType} from './express'
|
||||
import {TAsyncified, TReduxed} from './types'
|
||||
import {createActions} from './redux'
|
||||
import {createLocalClient, LocalClient} from './local'
|
||||
|
||||
function keys<T>(obj: T): Array<keyof T & string> {
|
||||
return Object.keys(obj) as Array<keyof T & string>
|
||||
}
|
||||
|
||||
type BulkLocalClient<T> = {[K in keyof T & string]: LocalClient<T[K]>}
|
||||
type BulkActions<T> = {[K in keyof T & string]: TReduxed<T[K], K>}
|
||||
type BulkRemoteClient<T> = {[K in keyof T & string]: TAsyncified<T[K]>}
|
||||
|
||||
function bulkCreate<T, R>(
|
||||
src: T,
|
||||
mapValue: <K extends keyof T & string>(key: K, value: T[K]) => any,
|
||||
) {
|
||||
return keys(src).reduce((obj, key) => {
|
||||
const value = mapValue(key, src[key])
|
||||
obj[key] = value
|
||||
return obj
|
||||
}, {} as any)
|
||||
}
|
||||
|
||||
export function bulkCreateLocalClient<T, Cx>(
|
||||
src: T,
|
||||
context: Cx,
|
||||
): BulkLocalClient<T> {
|
||||
return bulkCreate(src, (key, value) => createLocalClient(value, context))
|
||||
}
|
||||
|
||||
export function bulkCreateActions<T extends Record<string, TAsyncified<any>>>(
|
||||
src: T,
|
||||
): BulkActions<T> {
|
||||
return bulkCreate(src, (key, value) => createActions(value, key))
|
||||
}
|
||||
|
||||
export function bulkjsonrpc<T>(
|
||||
jsonrpc: IJSONRPCReturnType,
|
||||
services: T,
|
||||
) {
|
||||
keys(services).forEach(key => {
|
||||
const service = services[key]
|
||||
jsonrpc.addService('/' + key, service)
|
||||
})
|
||||
|
||||
return jsonrpc.router()
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user