Breaking change: JSONRPC context is always 1st argument
This commit is contained in:
parent
b8fb7c2eba
commit
0222455cd7
@ -6,6 +6,7 @@ import {createClient} from './supertest'
|
|||||||
import {ensure} from './ensure'
|
import {ensure} from './ensure'
|
||||||
import {jsonrpc} from './express'
|
import {jsonrpc} from './express'
|
||||||
import {noopLogger} from './test-utils'
|
import {noopLogger} from './test-utils'
|
||||||
|
import {Contextual} from './types'
|
||||||
|
|
||||||
describe('jsonrpc', () => {
|
describe('jsonrpc', () => {
|
||||||
|
|
||||||
@ -20,15 +21,15 @@ describe('jsonrpc', () => {
|
|||||||
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
|
addWithContext(a: number, b: number): number
|
||||||
addWithContext2(a: number, b: number): Promise<number>
|
addWithContext2(a: number, b: number): Promise<number>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ensureLoggedIn = ensure<IContext>(c => !!c.userId)
|
const ensureLoggedIn = ensure<IContext>(c => !!c.userId)
|
||||||
|
|
||||||
class Service implements IService {
|
class Service implements Contextual<IService, IContext> {
|
||||||
constructor(readonly time: number) {}
|
constructor(readonly time: number) {}
|
||||||
add(a: number, b: number) {
|
add(context: IContext, a: number, b: number) {
|
||||||
return a + b
|
return a + b
|
||||||
}
|
}
|
||||||
multiply(...numbers: number[]) {
|
multiply(...numbers: number[]) {
|
||||||
@ -39,13 +40,13 @@ describe('jsonrpc', () => {
|
|||||||
setTimeout(resolve, this.time)
|
setTimeout(resolve, this.time)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
syncError(message: string) {
|
syncError(context: IContext, message: string) {
|
||||||
throw new Error(message)
|
throw new Error(message)
|
||||||
}
|
}
|
||||||
async asyncError(message: string) {
|
async asyncError(context: IContext, message: string) {
|
||||||
throw new Error(message)
|
throw new Error(message)
|
||||||
}
|
}
|
||||||
async httpError(statusCode: number, message: string) {
|
async httpError(context: IContext, statusCode: number, message: string) {
|
||||||
const err: any = new Error(message)
|
const err: any = new Error(message)
|
||||||
err.statusCode = statusCode
|
err.statusCode = statusCode
|
||||||
err.errors = [{
|
err.errors = [{
|
||||||
@ -53,12 +54,12 @@ describe('jsonrpc', () => {
|
|||||||
}]
|
}]
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
addWithContext = (a: number, b: number) => (ctx: IContext): number => {
|
addWithContext = (ctx: IContext, a: number, b: number) => {
|
||||||
return a + b + ctx.userId
|
return a + b + ctx.userId
|
||||||
}
|
}
|
||||||
|
|
||||||
@ensureLoggedIn
|
@ensureLoggedIn
|
||||||
addWithContext2(a: number, b: number, ctx?: IContext) {
|
addWithContext2(ctx: IContext, a: number, b: number) {
|
||||||
return Promise.resolve(a + b + ctx!.userId)
|
return Promise.resolve(a + b + ctx!.userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -157,15 +157,7 @@ export const createRpcService = <T, M extends FunctionPropertyNames<T>>(
|
|||||||
|
|
||||||
await validateServiceContext(id, service, method, context)
|
await validateServiceContext(id, service, method, context)
|
||||||
|
|
||||||
// FIXME TODO if user specified too many parameters in the request,
|
let retValue = (rpcService[method] as any)(context, ...params)
|
||||||
// they might override the context argument! this is dangerous as it
|
|
||||||
// could allow them to set any userId they would like. We should compare
|
|
||||||
// method arguments length before invoking this function.
|
|
||||||
let retValue = (rpcService[method] as any)(...params, context)
|
|
||||||
|
|
||||||
if (typeof retValue === 'function') {
|
|
||||||
retValue = retValue(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isPromise(retValue) && isNotification) {
|
if (!isPromise(retValue) && isNotification) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import {createLocalClient} from './local'
|
import {createLocalClient} from './local'
|
||||||
import {keys} from 'ts-transformer-keys'
|
import {keys} from 'ts-transformer-keys'
|
||||||
|
import {Contextual, ReverseContextual, TAsyncified} from './types'
|
||||||
|
|
||||||
describe('local', () => {
|
describe('local', () => {
|
||||||
|
|
||||||
interface IService {
|
interface IService {
|
||||||
add(a: number, b: number): number
|
add(a: number, b: number): number
|
||||||
addWithContext(a: number, b: number): (context: IContext) => number
|
addWithContext(a: number, b: number): number
|
||||||
}
|
}
|
||||||
const IServiceKeys = keys<IService>()
|
const IServiceKeys = keys<IService>()
|
||||||
|
|
||||||
@ -13,19 +14,18 @@ describe('local', () => {
|
|||||||
userId: 1000
|
userId: 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
class Service implements IService {
|
class Service implements Contextual<IService, IContext> {
|
||||||
add(a: number, b: number) {
|
add(cx: IContext, a: number, b: number) {
|
||||||
return a + b
|
return a + b
|
||||||
}
|
}
|
||||||
addWithContext = (a: number, b: number) => (context: IContext) => {
|
addWithContext = (cx: IContext, a: number, b: number) => {
|
||||||
return a + b + context.userId
|
return a + b + cx.userId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const service = new Service()
|
const service = new Service()
|
||||||
const proxy = createLocalClient<IService>(service, {
|
|
||||||
userId: 1000,
|
const proxy = createLocalClient(service, () => ({userId: 1000}))
|
||||||
})
|
|
||||||
|
|
||||||
describe('add', () => {
|
describe('add', () => {
|
||||||
it('should add two numbers', async () => {
|
it('should add two numbers', async () => {
|
||||||
|
|||||||
@ -1,21 +1,24 @@
|
|||||||
import {TAsyncified} from './types'
|
import {TAsyncified, Contextual, ReverseContextual} from './types'
|
||||||
|
import {Request} from 'express'
|
||||||
|
import {TGetContext} from './express'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a local client for a specific service instance. The actual service
|
* 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
|
* will be invoked as if it would be remotely. This helps keep the API similar
|
||||||
* on the client- and server-side.
|
* on the client- and server-side.
|
||||||
*/
|
*/
|
||||||
export function createLocalClient<T>(service: T, context: any) {
|
export function createLocalClient<T, Context>(
|
||||||
|
service: T,
|
||||||
|
getContext: () => Context,
|
||||||
|
): TAsyncified<ReverseContextual<T>> {
|
||||||
const proxy = new Proxy({}, {
|
const proxy = new Proxy({}, {
|
||||||
get(obj, prop) {
|
get(obj, prop) {
|
||||||
|
const context = getContext()
|
||||||
return async function makeRequest(...args: any[]) {
|
return async function makeRequest(...args: any[]) {
|
||||||
const result = (service as any)[prop](...args)
|
const result = (service as any)[prop](context, ...args)
|
||||||
if (typeof result === 'function') {
|
|
||||||
return result(context)
|
|
||||||
}
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return proxy as TAsyncified<T>
|
return proxy as any
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import bodyParser from 'body-parser'
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import {AddressInfo} from 'net'
|
import {AddressInfo} from 'net'
|
||||||
import {Server} from 'http'
|
import {Server} from 'http'
|
||||||
import {TPendingActions, TAllActions} from './types'
|
import {Contextual, TPendingActions, TAllActions} from './types'
|
||||||
import {combineReducers} from 'redux'
|
import {combineReducers} from 'redux'
|
||||||
import {createActions, createReducer} from './redux'
|
import {createActions, createReducer} from './redux'
|
||||||
import {createRemoteClient} from './remote'
|
import {createRemoteClient} from './remote'
|
||||||
@ -21,9 +21,8 @@ describe('createActions', () => {
|
|||||||
add(a: number, b: number): number
|
add(a: number, b: number): number
|
||||||
addAsync(a: number, b: number): Promise<number>
|
addAsync(a: number, b: number): Promise<number>
|
||||||
addStringsAsync(a: string, b: string): Promise<string>
|
addStringsAsync(a: string, b: string): Promise<string>
|
||||||
addWithContext(a: number, b: number): (ctx: IContext) => number
|
addWithContext(a: number, b: number): number
|
||||||
addAsyncWithContext(a: number, b: number): (ctx: IContext) =>
|
addAsyncWithContext(a: number, b: number): Promise<number>
|
||||||
Promise<number>
|
|
||||||
throwError(bool: boolean): boolean
|
throwError(bool: boolean): boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,21 +30,21 @@ describe('createActions', () => {
|
|||||||
userId: number
|
userId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
class Service implements IService {
|
class Service implements Contextual<IService, IContext> {
|
||||||
add(a: number, b: number) {
|
add(cx: IContext, a: number, b: number) {
|
||||||
return a + b
|
return a + b
|
||||||
}
|
}
|
||||||
addAsync(a: number, b: number) {
|
addAsync(cx: IContext, a: number, b: number) {
|
||||||
return new Promise<number>(resolve => resolve(a + b))
|
return new Promise<number>(resolve => resolve(a + b))
|
||||||
}
|
}
|
||||||
addStringsAsync(a: string, b: string) {
|
addStringsAsync(cx: IContext, a: string, b: string) {
|
||||||
return new Promise<string>(resolve => resolve(a + b))
|
return new Promise<string>(resolve => resolve(a + b))
|
||||||
}
|
}
|
||||||
addWithContext = (a: number, b: number) => (ctx: IContext) =>
|
addWithContext = (cx: IContext, a: number, b: number) =>
|
||||||
a + b + ctx.userId
|
a + b + cx.userId
|
||||||
addAsyncWithContext = (a: number, b: number) => (ctx: IContext) =>
|
addAsyncWithContext = (cx: IContext, a: number, b: number) =>
|
||||||
new Promise<number>(resolve => resolve(a + b + ctx.userId))
|
new Promise<number>(resolve => resolve(a + b + cx.userId))
|
||||||
throwError(bool: boolean) {
|
throwError(cx: IContext, bool: boolean) {
|
||||||
if (bool) {
|
if (bool) {
|
||||||
throw new Error('test')
|
throw new Error('test')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {createRemoteClient} from './remote'
|
|||||||
import {jsonrpc} from './express'
|
import {jsonrpc} from './express'
|
||||||
import {keys} from 'ts-transformer-keys'
|
import {keys} from 'ts-transformer-keys'
|
||||||
import {noopLogger} from './test-utils'
|
import {noopLogger} from './test-utils'
|
||||||
|
import {Contextual} from './types'
|
||||||
|
|
||||||
describe('remote', () => {
|
describe('remote', () => {
|
||||||
|
|
||||||
@ -20,11 +21,11 @@ describe('remote', () => {
|
|||||||
}
|
}
|
||||||
const IServiceKeys = keys<IService>()
|
const IServiceKeys = keys<IService>()
|
||||||
|
|
||||||
class Service implements IService {
|
class Service implements Contextual<IService, {}> {
|
||||||
add(a: number, b: number) {
|
add(ctx: {}, a: number, b: number) {
|
||||||
return a + b
|
return a + b
|
||||||
}
|
}
|
||||||
async fetchItem(obj1: {a: number}, obj2: {b: number})
|
async fetchItem(ctx: {}, obj1: {a: number}, obj2: {b: number})
|
||||||
: Promise<{a: number, b: number}> {
|
: Promise<{a: number, b: number}> {
|
||||||
return Promise.resolve({...obj1, ...obj2})
|
return Promise.resolve({...obj1, ...obj2})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,10 @@ import {IPendingAction, IResolvedAction, IRejectedAction} from '@rondo.dev/clien
|
|||||||
export type ArgumentTypes<T> =
|
export type ArgumentTypes<T> =
|
||||||
T extends (...args: infer U) => infer R ? U : never
|
T extends (...args: infer U) => infer R ? U : never
|
||||||
export type RetType<T> = T extends (...args: any[]) => infer R ? R : never
|
export type RetType<T> = T extends (...args: any[]) => infer R ? R : never
|
||||||
type UnwrapHOC<T> = T extends (...args: any[]) => infer R ? R : T
|
|
||||||
type UnwrapPromise<T> = T extends Promise<infer V> ? V : T
|
type UnwrapPromise<T> = T extends Promise<infer V> ? V : 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>) =>
|
type PromisifyReturnType<T> = (...a: ArgumentTypes<T>) =>
|
||||||
RetProm<UnwrapHOC<RetType<T>>>
|
RetProm<RetType<T>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helps implement a service from a service definiton that has a context as a
|
* Helps implement a service from a service definiton that has a context as a
|
||||||
@ -15,16 +14,15 @@ type PromisifyReturnType<T> = (...a: ArgumentTypes<T>) =>
|
|||||||
*/
|
*/
|
||||||
export type Contextual<T, Cx> = {
|
export type Contextual<T, Cx> = {
|
||||||
[K in keyof T]:
|
[K in keyof T]:
|
||||||
T[K] extends () => infer R
|
T[K] extends (...args: infer A) => infer R
|
||||||
? (cx: Cx) => R :
|
? (cx: Cx, ...args: A) => R :
|
||||||
T[K] extends (a: infer A) => infer R
|
never
|
||||||
? (a: A, cx: Cx) => R :
|
}
|
||||||
T[K] extends (a: infer A, b: infer B) => infer R
|
|
||||||
? (a: A, b: B, cx: Cx) => R :
|
export type ReverseContextual<T> = {
|
||||||
T[K] extends (a: infer A, b: infer B, c: infer C) => infer R
|
[K in keyof T]:
|
||||||
? (a: A, b: B, c: C, cx: Cx) => R :
|
T[K] extends (cx: any, ...args: infer A) => infer R
|
||||||
T[K] extends (a: infer A, b: infer B, c: infer C, d: infer D) => infer R
|
? (...args: A) => R :
|
||||||
? (a: A, b: B, c: C, d: D, cx: Cx) => R :
|
|
||||||
never
|
never
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +44,7 @@ export interface IReduxed<ActionType extends string> {
|
|||||||
|
|
||||||
export type TReduxed<T, ActionType extends string> = {
|
export type TReduxed<T, ActionType extends string> = {
|
||||||
[K in keyof T]: (...a: ArgumentTypes<T[K]>) =>
|
[K in keyof T]: (...a: ArgumentTypes<T[K]>) =>
|
||||||
IRPCPendingAction<UnwrapPromise<UnwrapHOC<RetType<T[K]>>>, ActionType, K>
|
IRPCPendingAction<UnwrapPromise<RetType<T[K]>>, ActionType, K>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TReduxHandlers<T, State> = {
|
export type TReduxHandlers<T, State> = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user