Fix linting errors in packages/jsonrpc
This commit is contained in:
parent
8853c71a0d
commit
50d36f268f
@ -11,6 +11,10 @@ rules:
|
||||
- warn
|
||||
- code: 80
|
||||
ignorePattern: '^import .* from '
|
||||
comma-dangle:
|
||||
- warn
|
||||
- always-multiline
|
||||
|
||||
# semi:
|
||||
# - warn
|
||||
# - never
|
||||
|
||||
8
packages/jsonrpc/.eslintrc.yaml
Normal file
8
packages/jsonrpc/.eslintrc.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
extends:
|
||||
- ../../.eslintrc.yaml
|
||||
rules:
|
||||
# due to the nature of this project, writing code becomes much easier
|
||||
# when any can be used in some places
|
||||
'@typescript-eslint/no-explicit-any': off
|
||||
prefer-rest-params: off
|
||||
prefer-spread: off
|
||||
@ -8,30 +8,30 @@ import {json} from 'body-parser'
|
||||
|
||||
describe('util', () => {
|
||||
|
||||
interface IS1 {
|
||||
interface S1 {
|
||||
add(a: number, b: number): number
|
||||
}
|
||||
|
||||
interface IS2 {
|
||||
interface S2 {
|
||||
mul(a: number, b: number): number
|
||||
concat(...str: string[]): string
|
||||
}
|
||||
|
||||
interface IContext {
|
||||
interface Context {
|
||||
userId: number
|
||||
}
|
||||
|
||||
class Service1 implements WithContext<IS1, IContext> {
|
||||
add(cx: IContext, a: number, b: number) {
|
||||
class Service1 implements WithContext<S1, Context> {
|
||||
add(cx: Context, a: number, b: number) {
|
||||
return a + b + cx.userId
|
||||
}
|
||||
}
|
||||
|
||||
class Service2 implements WithContext<IS2, IContext> {
|
||||
mul(cx: IContext, a: number, b: number) {
|
||||
class Service2 implements WithContext<S2, Context> {
|
||||
mul(cx: Context, a: number, b: number) {
|
||||
return a * b + cx.userId
|
||||
}
|
||||
concat(cx: IContext, ...str: string[]) {
|
||||
concat(cx: Context, ...str: string[]) {
|
||||
return str.join('') + cx.userId
|
||||
}
|
||||
}
|
||||
@ -98,7 +98,7 @@ describe('util', () => {
|
||||
services,
|
||||
)
|
||||
const app = createApp(router)
|
||||
const client = createClient<IS1>(app, '/rpc/s1')
|
||||
const client = createClient<S1>(app, '/rpc/s1')
|
||||
const result: number = await client.add(1, 3)
|
||||
expect(result).toBe(14)
|
||||
})
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {IJSONRPCReturnType} from './express'
|
||||
/* eslint @typescript-eslint/no-explicit-any: off */
|
||||
import {RPCReturnType} from './express'
|
||||
import {WithContext, RPCClient, RPCActions} from './types'
|
||||
import {createActions} from './redux'
|
||||
import {createLocalClient, LocalClient} from './local'
|
||||
@ -39,7 +40,7 @@ export function bulkCreateActions<T extends Record<string, RPCClient<any>>>(
|
||||
}
|
||||
|
||||
export function bulkjsonrpc<T>(
|
||||
jsonrpc: IJSONRPCReturnType,
|
||||
jsonrpc: RPCReturnType,
|
||||
services: T,
|
||||
) {
|
||||
keys(services).forEach(key => {
|
||||
|
||||
@ -2,13 +2,13 @@ import createClientMock from './createClientMock'
|
||||
|
||||
describe('createClientMock', () => {
|
||||
|
||||
interface IService {
|
||||
interface Service {
|
||||
add(a: number, b: number): number
|
||||
concat(a: string, b: string): string
|
||||
}
|
||||
|
||||
it('creates a mock for all methods', async () => {
|
||||
const [client, mock] = createClientMock<IService>(['add', 'concat'])
|
||||
const [client, mock] = createClientMock<Service>(['add', 'concat'])
|
||||
mock.add.mockReturnValue(Promise.resolve(3))
|
||||
mock.concat.mockImplementation((a, b) => Promise.resolve(a + b))
|
||||
expect(await client.add(4, 5)).toBe(3)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { FunctionPropertyNames, RPCClient } from './types'
|
||||
|
||||
export type TMocked<T> = {
|
||||
export type RPCClientMock<T> = {
|
||||
[K in keyof T]:
|
||||
T[K] extends (...args: infer A) => infer R
|
||||
? jest.Mock<R, A>
|
||||
@ -20,7 +20,7 @@ export type TMocked<T> = {
|
||||
*/
|
||||
export default function createClientMock<T extends object>(
|
||||
methods: Array<FunctionPropertyNames<T>>,
|
||||
): [RPCClient<T>, TMocked<RPCClient<T>>] {
|
||||
): [RPCClient<T>, RPCClientMock<RPCClient<T>>] {
|
||||
const client = methods
|
||||
.reduce((obj, prop) => {
|
||||
obj[prop] = jest.fn()
|
||||
@ -29,6 +29,6 @@ export default function createClientMock<T extends object>(
|
||||
|
||||
return [
|
||||
client as RPCClient<T>,
|
||||
client as TMocked<RPCClient<T>>,
|
||||
client as RPCClientMock<RPCClient<T>>,
|
||||
]
|
||||
}
|
||||
|
||||
@ -7,15 +7,15 @@ import {
|
||||
|
||||
describe('ensure', () => {
|
||||
|
||||
interface IContext {
|
||||
interface Context {
|
||||
userId: number
|
||||
}
|
||||
|
||||
const validate: Validate<IContext> = c => c.userId > 0
|
||||
const validate: Validate<Context> = c => c.userId > 0
|
||||
|
||||
it('decorates class methods', () => {
|
||||
class Service {
|
||||
@ensure<IContext>(validate)
|
||||
@ensure<Context>(validate)
|
||||
fetchData() {
|
||||
return 1
|
||||
}
|
||||
@ -32,7 +32,7 @@ describe('ensure', () => {
|
||||
|
||||
it('works with properties/instance method definitions', () => {
|
||||
class Service {
|
||||
@ensure<IContext>(validate)
|
||||
@ensure<Context>(validate)
|
||||
fetchData = () => 1
|
||||
}
|
||||
const s = new Service()
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint @typescript-eslint/no-unused-vars: 0 */
|
||||
import 'reflect-metadata'
|
||||
|
||||
export const ensureKey = Symbol('ensure')
|
||||
@ -7,10 +8,11 @@ export type Validate<Context> = (context: Context) => boolean | Promise<boolean>
|
||||
|
||||
export function ensure<Context>(
|
||||
validate: Validate<Context>,
|
||||
message: string = 'Validation failed',
|
||||
message = 'Validation failed',
|
||||
) {
|
||||
return function ensureImpl(
|
||||
target: any, key?: string, descriptor?: PropertyDescriptor) {
|
||||
target: any, key?: string, descriptor?: PropertyDescriptor,
|
||||
) {
|
||||
switch (arguments.length) {
|
||||
case 1:
|
||||
return ensureClass(validate, message).apply(null, arguments as any)
|
||||
@ -25,7 +27,7 @@ export function ensure<Context>(
|
||||
|
||||
function ensureClass<Context>(
|
||||
validate: Validate<Context>,
|
||||
message: string = 'Validation failed',
|
||||
message = 'Validation failed',
|
||||
) {
|
||||
// tslint:disable-next-line
|
||||
return (target: any) => {
|
||||
@ -40,7 +42,7 @@ function ensureClass<Context>(
|
||||
|
||||
function ensureMethod<Context>(
|
||||
validate: Validate<Context>,
|
||||
message: string = 'Validation failed',
|
||||
message = 'Validation failed',
|
||||
) {
|
||||
return (
|
||||
target: any,
|
||||
|
||||
@ -1,44 +1,44 @@
|
||||
export interface IError {
|
||||
export interface ErrorWithCode {
|
||||
code: number
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface IErrorWithData<T> extends IError {
|
||||
export interface ErrorWithData<T> extends ErrorWithCode {
|
||||
data: T
|
||||
}
|
||||
|
||||
export interface IErrorResponse<T> {
|
||||
export interface ErrorResponse<T> {
|
||||
jsonrpc: '2.0'
|
||||
id: string | number | null
|
||||
result: null
|
||||
error: IErrorWithData<T>
|
||||
error: ErrorWithData<T>
|
||||
}
|
||||
|
||||
export interface IJSONRPCError<T> extends Error {
|
||||
export interface RPCError<T> extends Error {
|
||||
code: number
|
||||
statusCode: number
|
||||
response: IErrorResponse<T>
|
||||
response: ErrorResponse<T>
|
||||
}
|
||||
|
||||
export function isJSONRPCError(err: any): err is IJSONRPCError<unknown> {
|
||||
return err.name === 'IJSONRPCError' &&
|
||||
export function isRPCError(err: any): err is RPCError<unknown> {
|
||||
return err.name === 'RPCError' &&
|
||||
typeof err.message === 'string' &&
|
||||
err.hasOwnProperty('code') &&
|
||||
err.hasOwnProperty('response')
|
||||
Object.prototype.hasOwnProperty.call(err, 'code') &&
|
||||
Object.prototype.hasOwnProperty.call(err, 'response')
|
||||
}
|
||||
|
||||
export function createError<T = null>(
|
||||
error: IError,
|
||||
error: ErrorWithCode,
|
||||
info: {
|
||||
id: number | string | null
|
||||
data: T
|
||||
statusCode: number
|
||||
},
|
||||
): IJSONRPCError<T> {
|
||||
): RPCError<T> {
|
||||
|
||||
const err = new Error(error.message) as IJSONRPCError<T>
|
||||
const err = new Error(error.message) as RPCError<T>
|
||||
|
||||
err.name = 'IJSONRPCError'
|
||||
err.name = 'RPCError'
|
||||
err.code = error.code
|
||||
err.statusCode = info.statusCode
|
||||
err.response = {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import bodyParser from 'body-parser'
|
||||
import express from 'express'
|
||||
import request from 'supertest'
|
||||
import {IRequest} from './jsonrpc'
|
||||
import {Request} from './jsonrpc'
|
||||
import {createClient} from './supertest'
|
||||
import {ensure} from './ensure'
|
||||
import {jsonrpc} from './express'
|
||||
@ -10,11 +10,11 @@ import {WithContext} from './types'
|
||||
|
||||
describe('jsonrpc', () => {
|
||||
|
||||
interface IContext {
|
||||
interface Context {
|
||||
userId: number
|
||||
}
|
||||
|
||||
interface IService {
|
||||
interface Service {
|
||||
add(a: number, b: number): number
|
||||
delay(): Promise<void>
|
||||
syncError(message: string): void
|
||||
@ -25,11 +25,11 @@ describe('jsonrpc', () => {
|
||||
addWithContext2(a: number, b: number): Promise<number>
|
||||
}
|
||||
|
||||
const ensureLoggedIn = ensure<IContext>(c => !!c.userId)
|
||||
const ensureLoggedIn = ensure<Context>(c => !!c.userId)
|
||||
|
||||
class Service implements WithContext<IService, IContext> {
|
||||
class MyService implements WithContext<Service, Context> {
|
||||
constructor(readonly time: number) {}
|
||||
add(context: IContext, a: number, b: number) {
|
||||
add(context: Context, a: number, b: number) {
|
||||
return a + b
|
||||
}
|
||||
multiply(...numbers: number[]) {
|
||||
@ -40,13 +40,13 @@ describe('jsonrpc', () => {
|
||||
setTimeout(resolve, this.time)
|
||||
})
|
||||
}
|
||||
syncError(context: IContext, message: string) {
|
||||
syncError(context: Context, message: string) {
|
||||
throw new Error(message)
|
||||
}
|
||||
async asyncError(context: IContext, message: string) {
|
||||
async asyncError(context: Context, message: string) {
|
||||
throw new Error(message)
|
||||
}
|
||||
async httpError(context: IContext, statusCode: number, message: string) {
|
||||
async httpError(context: Context, statusCode: number, message: string) {
|
||||
const err: any = new Error(message)
|
||||
err.statusCode = statusCode
|
||||
err.errors = [{
|
||||
@ -54,14 +54,14 @@ describe('jsonrpc', () => {
|
||||
}]
|
||||
throw err
|
||||
}
|
||||
addWithContext = (ctx: IContext, a: number, b: number) => {
|
||||
addWithContext = (ctx: Context, a: number, b: number) => {
|
||||
return a + b + ctx.userId
|
||||
}
|
||||
_private = () => {
|
||||
return 1
|
||||
}
|
||||
@ensureLoggedIn
|
||||
addWithContext2(ctx: IContext, a: number, b: number) {
|
||||
addWithContext2(ctx: Context, a: number, b: number) {
|
||||
return Promise.resolve(a + b + ctx!.userId)
|
||||
}
|
||||
}
|
||||
@ -72,8 +72,8 @@ describe('jsonrpc', () => {
|
||||
const app = express()
|
||||
app.use(bodyParser.json())
|
||||
app.use('/',
|
||||
jsonrpc(req => ({userId}), noopLogger)
|
||||
.addService('/myService', new Service(5), [
|
||||
jsonrpc(() => ({userId}), noopLogger)
|
||||
.addService('/myService', new MyService(5), [
|
||||
'add',
|
||||
'delay',
|
||||
'syncError',
|
||||
@ -87,7 +87,7 @@ describe('jsonrpc', () => {
|
||||
return app
|
||||
}
|
||||
|
||||
const client = createClient<IService>(createApp(), '/myService')
|
||||
const client = createClient<Service>(createApp(), '/myService')
|
||||
|
||||
async function getError(promise: Promise<unknown>) {
|
||||
let error
|
||||
@ -260,7 +260,7 @@ describe('jsonrpc', () => {
|
||||
|
||||
describe('hook', () => {
|
||||
|
||||
let requests: IRequest[] = []
|
||||
let requests: Request[] = []
|
||||
let results: any[] = []
|
||||
function create() {
|
||||
requests = []
|
||||
@ -268,12 +268,12 @@ describe('jsonrpc', () => {
|
||||
|
||||
userId = 1000
|
||||
const app = express()
|
||||
const myService = new Service(5)
|
||||
const myService = new MyService(5)
|
||||
// console.log('service', myService, Object.
|
||||
app.use(bodyParser.json())
|
||||
app.use('/',
|
||||
jsonrpc(
|
||||
req => Promise.resolve({userId}),
|
||||
() => Promise.resolve({userId}),
|
||||
noopLogger,
|
||||
async (details, makeRequest) => {
|
||||
requests.push(details.request)
|
||||
|
||||
@ -1,35 +1,29 @@
|
||||
import express, {ErrorRequestHandler} from 'express'
|
||||
import {FunctionPropertyNames} from './types'
|
||||
import {IDEMPOTENT_METHOD_REGEX} from './idempotent'
|
||||
import {IErrorResponse} from './error'
|
||||
import {ILogger} from '@rondo.dev/logger'
|
||||
import {ISuccessResponse} from './jsonrpc'
|
||||
import {NextFunction, Request, Response, Router} from 'express'
|
||||
import {createError, isJSONRPCError, IJSONRPCError, IError} from './error'
|
||||
import {
|
||||
createRpcService, ERROR_SERVER, ERROR_INVALID_PARAMS, ERROR_METHOD_NOT_FOUND,
|
||||
IRequest,
|
||||
} from './jsonrpc'
|
||||
import { Logger } from '@rondo.dev/logger'
|
||||
import express, { ErrorRequestHandler, Request, Response, Router } from 'express'
|
||||
import { createError, ErrorResponse, isRPCError } from './error'
|
||||
import { IDEMPOTENT_METHOD_REGEX } from './idempotent'
|
||||
import { createRpcService, ERROR_METHOD_NOT_FOUND, ERROR_SERVER, IRequest, SuccessResponse } from './jsonrpc'
|
||||
import { FunctionPropertyNames } from './types'
|
||||
|
||||
export type TGetContext<Context> = (req: Request) => Promise<Context> | Context
|
||||
|
||||
export interface IJSONRPCReturnType {
|
||||
export interface RPCReturnType {
|
||||
addService<T, F extends FunctionPropertyNames<T>>(
|
||||
path: string,
|
||||
service: T,
|
||||
methods?: F[],
|
||||
): IJSONRPCReturnType,
|
||||
): RPCReturnType
|
||||
router(): Router
|
||||
}
|
||||
|
||||
export interface IInvocationDetails<A extends IRequest, Context> {
|
||||
export interface InvocationDetails<A extends IRequest, Context> {
|
||||
context: Context
|
||||
path: string
|
||||
request: A
|
||||
}
|
||||
|
||||
async function defaultHook<A extends IRequest, R, Context>(
|
||||
details: IInvocationDetails<A, Context>,
|
||||
details: InvocationDetails<A, Context>,
|
||||
invoke: () => Promise<R>,
|
||||
): Promise<R> {
|
||||
const result = await invoke()
|
||||
@ -38,17 +32,18 @@ async function defaultHook<A extends IRequest, R, Context>(
|
||||
|
||||
export function jsonrpc<Context>(
|
||||
getContext: TGetContext<Context>,
|
||||
logger: ILogger,
|
||||
logger: Logger,
|
||||
hook: <A extends IRequest, R>(
|
||||
details: IInvocationDetails<A, Context>,
|
||||
details: InvocationDetails<A, Context>,
|
||||
invoke: (request?: A) => Promise<R>) => Promise<R> = defaultHook,
|
||||
idempotentMethodRegex = IDEMPOTENT_METHOD_REGEX,
|
||||
): IJSONRPCReturnType {
|
||||
): RPCReturnType {
|
||||
|
||||
/* eslint @typescript-eslint/no-unused-vars: 0 */
|
||||
const handleError: ErrorRequestHandler = (err, req, res, next) => {
|
||||
logger.error('JSON-RPC Error: %s', err.stack)
|
||||
|
||||
if (isJSONRPCError(err)) {
|
||||
if (isRPCError(err)) {
|
||||
res.status(err.statusCode)
|
||||
res.json(err.response)
|
||||
return
|
||||
@ -56,7 +51,7 @@ export function jsonrpc<Context>(
|
||||
|
||||
const id = getRequestId(req)
|
||||
const statusCode: number = err.statusCode || 500
|
||||
const errorResponse: IErrorResponse<unknown> = {
|
||||
const errorResponse: ErrorResponse<unknown> = {
|
||||
jsonrpc: '2.0',
|
||||
id,
|
||||
result: null,
|
||||
@ -85,7 +80,7 @@ export function jsonrpc<Context>(
|
||||
const rpcService = createRpcService(service, methods)
|
||||
|
||||
function handleResponse(
|
||||
response: ISuccessResponse<unknown> | null,
|
||||
response: SuccessResponse<unknown> | null,
|
||||
res: Response,
|
||||
) {
|
||||
if (response === null) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
export type TId = number | string
|
||||
import {ArgumentTypes, FunctionPropertyNames, RetType} from './types'
|
||||
import {isPromise} from './isPromise'
|
||||
import {createError, IErrorResponse, IErrorWithData} from './error'
|
||||
import {createError, ErrorResponse} from './error'
|
||||
import {getValidatorsForMethod, getValidatorsForInstance} from './ensure'
|
||||
import {Validate} from './ensure'
|
||||
|
||||
@ -35,8 +35,8 @@ export const ERROR_SERVER = {
|
||||
message: 'Server error',
|
||||
}
|
||||
|
||||
export function pick<T, K extends FunctionPropertyNames<T>>(t: T, keys: K[])
|
||||
: Pick<T, K> {
|
||||
export function pick<T, K extends FunctionPropertyNames<T>>(
|
||||
t: T, keys: K[]): Pick<T, K> {
|
||||
return keys.reduce((obj, key) => {
|
||||
// tslint:disable-next-line
|
||||
const fn = t[key] as unknown as Function
|
||||
@ -48,8 +48,8 @@ export function pick<T, K extends FunctionPropertyNames<T>>(t: T, keys: K[])
|
||||
export function getAllMethods<T>(obj: T): Array<FunctionPropertyNames<T>> {
|
||||
const props = new Set<string>()
|
||||
do {
|
||||
const l = Object.getOwnPropertyNames(obj)
|
||||
.filter((p, i, arr) => {
|
||||
Object.getOwnPropertyNames(obj)
|
||||
.filter(p => {
|
||||
return typeof (obj as any)[p] === 'function' &&
|
||||
p.startsWith('_') === false &&
|
||||
p !== 'constructor'
|
||||
@ -61,24 +61,25 @@ export function getAllMethods<T>(obj: T): Array<FunctionPropertyNames<T>> {
|
||||
return Array.from(props) as unknown as Array<FunctionPropertyNames<T>>
|
||||
}
|
||||
|
||||
export interface IRequest<M extends string = any, A = any[]> {
|
||||
export interface Request<M extends string = any, A = any[]> {
|
||||
jsonrpc: '2.0'
|
||||
id: TId | null
|
||||
method: M
|
||||
params: A
|
||||
}
|
||||
|
||||
export interface ISuccessResponse<T> {
|
||||
export interface SuccessResponse<T> {
|
||||
jsonrpc: '2.0'
|
||||
id: TId
|
||||
result: T
|
||||
error: null
|
||||
}
|
||||
|
||||
export type IResponse<T = any> = ISuccessResponse<T> | IErrorResponse<T>
|
||||
export type Response<T = any> = SuccessResponse<T> | ErrorResponse<T>
|
||||
|
||||
export function createSuccessResponse<T>(id: number | string, result: T)
|
||||
: ISuccessResponse<T> {
|
||||
export function createSuccessResponse<T>(
|
||||
id: number | string, result: T
|
||||
): SuccessResponse<T> {
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
id,
|
||||
@ -125,9 +126,9 @@ export const createRpcService = <T, M extends FunctionPropertyNames<T>>(
|
||||
pick(service, getAllMethods(service))
|
||||
return {
|
||||
async invoke<Context>(
|
||||
req: IRequest<M, ArgumentTypes<T[M]>>,
|
||||
req: Request<M, ArgumentTypes<T[M]>>,
|
||||
context: Context,
|
||||
): Promise<ISuccessResponse<RetType<T[M]>> | null> {
|
||||
): Promise<SuccessResponse<RetType<T[M]>> | null> {
|
||||
const {id, method, params} = req
|
||||
|
||||
if (
|
||||
@ -145,7 +146,7 @@ export const createRpcService = <T, M extends FunctionPropertyNames<T>>(
|
||||
const isNotification = req.id === null || req.id === undefined
|
||||
|
||||
if (
|
||||
!rpcService.hasOwnProperty(method) ||
|
||||
!Object.prototype.hasOwnProperty.call(rpcService, method) ||
|
||||
typeof rpcService[method] !== 'function'
|
||||
) {
|
||||
throw createError(ERROR_METHOD_NOT_FOUND, {
|
||||
|
||||
@ -1,29 +1,27 @@
|
||||
import {createLocalClient} from './local'
|
||||
import {keys} from 'ts-transformer-keys'
|
||||
import {WithContext, WithoutContext, RPCClient} from './types'
|
||||
import { createLocalClient } from './local'
|
||||
import { WithContext } from './types'
|
||||
|
||||
describe('local', () => {
|
||||
|
||||
interface IService {
|
||||
interface Service {
|
||||
add(a: number, b: number): number
|
||||
addWithContext(a: number, b: number): number
|
||||
}
|
||||
const IServiceKeys = keys<IService>()
|
||||
|
||||
interface IContext {
|
||||
interface Context {
|
||||
userId: 1000
|
||||
}
|
||||
|
||||
class Service implements WithContext<IService, IContext> {
|
||||
add(cx: IContext, a: number, b: number) {
|
||||
class MyService implements WithContext<Service, Context> {
|
||||
add(cx: Context, a: number, b: number) {
|
||||
return a + b
|
||||
}
|
||||
addWithContext = (cx: IContext, a: number, b: number) => {
|
||||
addWithContext = (cx: Context, a: number, b: number) => {
|
||||
return a + b + cx.userId
|
||||
}
|
||||
}
|
||||
|
||||
const service = new Service()
|
||||
const service = new MyService()
|
||||
|
||||
const proxy = createLocalClient(service, {userId: 1000})
|
||||
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import {RPCClient, WithContext, WithoutContext} from './types'
|
||||
import {Request} from 'express'
|
||||
import {TGetContext} from './express'
|
||||
import {getAllMethods} from './jsonrpc'
|
||||
import { getAllMethods } from './jsonrpc'
|
||||
import { RPCClient, WithoutContext } from './types'
|
||||
|
||||
export type LocalClient<T> = RPCClient<WithoutContext<T>>
|
||||
|
||||
|
||||
@ -2,22 +2,22 @@
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import { createStore } from '@rondo.dev/redux'
|
||||
import bodyParser from 'body-parser'
|
||||
import express from 'express'
|
||||
import {AddressInfo} from 'net'
|
||||
import {Server} from 'http'
|
||||
import {WithContext, TPendingActions, TAllActions} from './types'
|
||||
import {combineReducers} from 'redux'
|
||||
import {createActions, createReducer} from './redux'
|
||||
import {createRemoteClient} from './remote'
|
||||
import {createStore} from '@rondo.dev/redux'
|
||||
import {jsonrpc} from './express'
|
||||
import {keys} from 'ts-transformer-keys'
|
||||
import {noopLogger} from './test-utils'
|
||||
import { Server } from 'http'
|
||||
import { AddressInfo } from 'net'
|
||||
import { combineReducers } from 'redux'
|
||||
import { keys } from 'ts-transformer-keys'
|
||||
import { jsonrpc } from './express'
|
||||
import { createActions, createReducer } from './redux'
|
||||
import { createRemoteClient } from './remote'
|
||||
import { noopLogger } from './test-utils'
|
||||
import { WithContext } from './types'
|
||||
|
||||
describe('createActions', () => {
|
||||
|
||||
interface IService {
|
||||
interface Service {
|
||||
add(a: number, b: number): number
|
||||
addAsync(a: number, b: number): Promise<number>
|
||||
addStringsAsync(a: string, b: string): Promise<string>
|
||||
@ -26,25 +26,25 @@ describe('createActions', () => {
|
||||
throwError(bool: boolean): boolean
|
||||
}
|
||||
|
||||
interface IContext {
|
||||
interface Context {
|
||||
userId: number
|
||||
}
|
||||
|
||||
class Service implements WithContext<IService, IContext> {
|
||||
add(cx: IContext, a: number, b: number) {
|
||||
class MyService implements WithContext<Service, Context> {
|
||||
add(cx: Context, a: number, b: number) {
|
||||
return a + b
|
||||
}
|
||||
addAsync(cx: IContext, a: number, b: number) {
|
||||
addAsync(cx: Context, a: number, b: number) {
|
||||
return new Promise<number>(resolve => resolve(a + b))
|
||||
}
|
||||
addStringsAsync(cx: IContext, a: string, b: string) {
|
||||
addStringsAsync(cx: Context, a: string, b: string) {
|
||||
return new Promise<string>(resolve => resolve(a + b))
|
||||
}
|
||||
addWithContext = (cx: IContext, a: number, b: number) =>
|
||||
addWithContext = (cx: Context, a: number, b: number) =>
|
||||
a + b + cx.userId
|
||||
addAsyncWithContext = (cx: IContext, a: number, b: number) =>
|
||||
addAsyncWithContext = (cx: Context, a: number, b: number) =>
|
||||
new Promise<number>(resolve => resolve(a + b + cx.userId))
|
||||
throwError(cx: IContext, bool: boolean) {
|
||||
throwError(cx: Context, bool: boolean) {
|
||||
if (bool) {
|
||||
throw new Error('test')
|
||||
}
|
||||
@ -57,7 +57,7 @@ describe('createActions', () => {
|
||||
app.use(
|
||||
'/',
|
||||
jsonrpc(() => ({userId: 1000}), noopLogger)
|
||||
.addService('/service', new Service(), keys<IService>())
|
||||
.addService('/service', new MyService(), keys<Service>())
|
||||
.router(),
|
||||
)
|
||||
|
||||
@ -78,8 +78,8 @@ describe('createActions', () => {
|
||||
})
|
||||
|
||||
function getClient() {
|
||||
const remoteClient = createRemoteClient<IService>(
|
||||
baseUrl + '/service', keys<IService>())
|
||||
const remoteClient = createRemoteClient<Service>(
|
||||
baseUrl + '/service', keys<Service>())
|
||||
const client = createActions(remoteClient, 'myService')
|
||||
|
||||
const defaultState = {
|
||||
@ -96,16 +96,14 @@ describe('createActions', () => {
|
||||
case 'addAsync':
|
||||
case 'addWithContext':
|
||||
case 'addAsyncWithContext':
|
||||
const r1: number = action.payload
|
||||
return {
|
||||
...state,
|
||||
add: r1,
|
||||
add: action.payload,
|
||||
}
|
||||
case 'addStringsAsync':
|
||||
const r2: string = action.payload
|
||||
return {
|
||||
...state,
|
||||
addStringsAsync: r2,
|
||||
addStringsAsync: action.payload,
|
||||
}
|
||||
default:
|
||||
return state
|
||||
@ -139,7 +137,7 @@ describe('createActions', () => {
|
||||
add: action.payload,
|
||||
}
|
||||
},
|
||||
throwError(state, action) {
|
||||
throwError(state) {
|
||||
return state
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import {
|
||||
IRPCActions,
|
||||
RPCClient,
|
||||
RPCActions,
|
||||
RPCClient,
|
||||
TResolvedActions,
|
||||
TAllActions,
|
||||
RPCReduxHandlers,
|
||||
RPCActionsRecord,
|
||||
} from './types'
|
||||
|
||||
export function createActions<T, ActionType extends string>(
|
||||
@ -27,18 +27,19 @@ export function createActions<T, ActionType extends string>(
|
||||
return service as RPCActions<T, ActionType>
|
||||
}
|
||||
|
||||
export interface IState {
|
||||
export interface TestState {
|
||||
loading: number
|
||||
error: string
|
||||
}
|
||||
|
||||
export function createReducer<ActionType extends string, State extends IState>(
|
||||
export function createReducer<
|
||||
ActionType extends string, State extends TestState>(
|
||||
actionType: ActionType,
|
||||
defaultState: State,
|
||||
) {
|
||||
|
||||
const self = {
|
||||
withHandler<R extends IRPCActions<ActionType>>(
|
||||
withHandler<R extends RPCActionsRecord<ActionType>>(
|
||||
handleAction: (state: State, action: TResolvedActions<R>) => State,
|
||||
): (state: State | undefined, action: TAllActions<R>) => State {
|
||||
return (state: State = defaultState, action: TAllActions<R>): State => {
|
||||
@ -66,7 +67,7 @@ export function createReducer<ActionType extends string, State extends IState>(
|
||||
}, action)
|
||||
}
|
||||
},
|
||||
withMapping<R extends IRPCActions<ActionType>>(
|
||||
withMapping<R extends RPCActionsRecord<ActionType>>(
|
||||
handlers: RPCReduxHandlers<R, State>,
|
||||
) {
|
||||
return self.withHandler<R>((state, action) => {
|
||||
|
||||
@ -14,24 +14,25 @@ import {WithContext} from './types'
|
||||
|
||||
describe('remote', () => {
|
||||
|
||||
interface IService {
|
||||
interface Service {
|
||||
add(a: number, b: number): number
|
||||
fetchItem(obj1: {a: number}, obj2: {b: number})
|
||||
: Promise<{a: number, b: number}>
|
||||
fetchItem(
|
||||
obj1: {a: number}, obj2: {b: number}): Promise<{a: number, b: number}>
|
||||
}
|
||||
const IServiceKeys = keys<IService>()
|
||||
const IServiceKeys = keys<Service>()
|
||||
|
||||
class Service implements WithContext<IService, {}> {
|
||||
class MyService implements WithContext<Service, {}> {
|
||||
add(ctx: {}, a: number, b: number) {
|
||||
return a + b
|
||||
}
|
||||
async fetchItem(ctx: {}, obj1: {a: number}, obj2: {b: number})
|
||||
: Promise<{a: number, b: number}> {
|
||||
async fetchItem(
|
||||
ctx: {}, obj1: {a: number}, obj2: {b: number}
|
||||
): Promise<{a: number, b: number}> {
|
||||
return Promise.resolve({...obj1, ...obj2})
|
||||
}
|
||||
}
|
||||
|
||||
const service = new Service()
|
||||
const service = new MyService()
|
||||
|
||||
function createApp() {
|
||||
const a = express()
|
||||
@ -65,7 +66,7 @@ describe('remote', () => {
|
||||
|
||||
describe('idempotent method invocation (GET)', () => {
|
||||
it('creates a proxy for remote service', async () => {
|
||||
const rpc = createRemoteClient<IService>(
|
||||
const rpc = createRemoteClient<Service>(
|
||||
baseUrl + '/myService', IServiceKeys)
|
||||
const result = await rpc.fetchItem({a: 10}, {b: 20})
|
||||
expect(result).toEqual({a: 10, b: 20})
|
||||
@ -74,7 +75,7 @@ describe('remote', () => {
|
||||
|
||||
describe('method invocation (POST)', () => {
|
||||
it('creates a proxy for remote service', async () => {
|
||||
const rpc = createRemoteClient<IService>(
|
||||
const rpc = createRemoteClient<Service>(
|
||||
baseUrl + '/myService', IServiceKeys)
|
||||
const result = await rpc.add(3, 7)
|
||||
expect(result).toBe(3 + 7)
|
||||
|
||||
@ -21,7 +21,7 @@ export function createRemoteClient<T>(
|
||||
method: string,
|
||||
params: any[],
|
||||
) {
|
||||
const reqMethod = IDEMPOTENT_METHOD_REGEX.test(method) ? 'GET' : 'POST'
|
||||
const reqMethod = idempotentMethodRegex.test(method) ? 'GET' : 'POST'
|
||||
const payloadKey = reqMethod === 'POST' ? 'data' : 'params'
|
||||
|
||||
const response = await axios({
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import {ILogger} from '@rondo.dev/logger'
|
||||
import {Logger} from '@rondo.dev/logger'
|
||||
|
||||
const noop = () => undefined
|
||||
|
||||
export const noopLogger: ILogger = {
|
||||
export const noopLogger: Logger = {
|
||||
error: noop,
|
||||
warn: noop,
|
||||
info: noop,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {IPendingAction, IResolvedAction, IRejectedAction} from '@rondo.dev/redux'
|
||||
import {PendingAction, ResolvedAction, RejectedAction} from '@rondo.dev/redux'
|
||||
|
||||
export type ArgumentTypes<T> =
|
||||
T extends (...args: infer U) => infer R ? U : never
|
||||
@ -38,64 +38,64 @@ export type RPCClient<T> = {
|
||||
[K in keyof T]: PromisifyReturnType<T[K]>
|
||||
}
|
||||
|
||||
export interface IRPCActions<ActionType extends string> {
|
||||
[key: string]: (...a: any[]) => IRPCPendingAction<any, ActionType, typeof key>
|
||||
export interface RPCActionsRecord<ActionType extends string> {
|
||||
[key: string]: (...a: any[]) => RPCPendingAction<any, ActionType, typeof key>
|
||||
}
|
||||
|
||||
export type RPCActions<T, ActionType extends string> = {
|
||||
[K in keyof T]: (...a: ArgumentTypes<T[K]>) =>
|
||||
IRPCPendingAction<UnwrapPromise<RetType<T[K]>>, ActionType, K>
|
||||
RPCPendingAction<UnwrapPromise<RetType<T[K]>>, ActionType, K>
|
||||
}
|
||||
|
||||
export type RPCReduxHandlers<T, State> = {
|
||||
[K in keyof T]: (
|
||||
state: State,
|
||||
action: TResolved<TPending<RetType<T[K]>>>,
|
||||
action: GetResolvedAction<GetPendingAction<RetType<T[K]>>>,
|
||||
) => Partial<State>
|
||||
}
|
||||
|
||||
export interface IRPCPendingAction<
|
||||
export interface RPCPendingAction<
|
||||
T, ActionType extends string, Method extends string | number | symbol
|
||||
> extends IPendingAction<T, ActionType> {
|
||||
> extends PendingAction<T, ActionType> {
|
||||
method: Method
|
||||
}
|
||||
|
||||
export interface IRPCResolvedAction<
|
||||
export interface RPCResolvedAction<
|
||||
T, ActionType extends string, Method extends string | symbol | number
|
||||
> extends IResolvedAction<T, ActionType> {
|
||||
> extends ResolvedAction<T, ActionType> {
|
||||
method: Method
|
||||
}
|
||||
|
||||
export interface IRPCRejectedAction<
|
||||
export interface RPCRejectedAction<
|
||||
ActionType extends string, Method extends string | symbol | number
|
||||
> extends IRejectedAction<ActionType> {
|
||||
> extends RejectedAction<ActionType> {
|
||||
method: Method
|
||||
}
|
||||
|
||||
export type TRPCAction<
|
||||
T, ActionType extends string, Method extends string | number | symbol
|
||||
> =
|
||||
IRPCPendingAction<T, ActionType, Method>
|
||||
| IRPCResolvedAction<T, ActionType, Method>
|
||||
| IRPCRejectedAction<ActionType, Method>
|
||||
RPCPendingAction<T, ActionType, Method>
|
||||
| RPCResolvedAction<T, ActionType, Method>
|
||||
| RPCRejectedAction<ActionType, Method>
|
||||
|
||||
export type TResolved<A> =
|
||||
A extends IRPCPendingAction<infer T, infer ActionType, infer Method>
|
||||
? IRPCResolvedAction<T, ActionType, Method>
|
||||
export type GetResolvedAction<A> =
|
||||
A extends RPCPendingAction<infer T, infer ActionType, infer Method>
|
||||
? RPCResolvedAction<T, ActionType, Method>
|
||||
: never
|
||||
|
||||
export type TRejected<A> =
|
||||
A extends IRPCPendingAction<infer T, infer ActionType, infer Method>
|
||||
? IRPCRejectedAction<ActionType, Method>
|
||||
export type GetRejectedAction<A> =
|
||||
A extends RPCPendingAction<infer T, infer ActionType, infer Method>
|
||||
? RPCRejectedAction<ActionType, Method>
|
||||
: never
|
||||
|
||||
export type TPending<A> =
|
||||
A extends IRPCPendingAction<infer T, infer ActionType, infer Method>
|
||||
? IRPCPendingAction<T, ActionType, Method>
|
||||
export type GetPendingAction<A> =
|
||||
A extends RPCPendingAction<infer T, infer ActionType, infer Method>
|
||||
? RPCPendingAction<T, ActionType, Method>
|
||||
: never
|
||||
|
||||
type Values<T> = T[keyof T]
|
||||
export type TPendingActions<T> = TPending<RetType<Values<T>>>
|
||||
export type TResolvedActions<T> = TResolved<TPendingActions<T>>
|
||||
export type TPendingActions<T> = GetPendingAction<RetType<Values<T>>>
|
||||
export type TResolvedActions<T> = GetResolvedAction<TPendingActions<T>>
|
||||
export type TAllActions<T> = TPendingActions<T>
|
||||
| TResolvedActions<T> | TRejected<TPendingActions<T>>
|
||||
| TResolvedActions<T> | GetRejectedAction<TPendingActions<T>>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {PendingAction} from './IPendingAction'
|
||||
import {PendingAction} from './PendingAction'
|
||||
|
||||
export function createPendingAction<T, ActionType extends string>(
|
||||
payload: Promise<T>,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user