Skip to content

🌲 The easiest-to-use RPC library designed for TypeScript and JavaScript.

License

Notifications You must be signed in to change notification settings

delight-rpc/delight-rpc

Repository files navigation

delight-rpc

The easiest-to-use RPC library designed for TypeScript and JavaScript.

Install

npm install --save delight-rpc
# or
yarn add delight-rpc

Usage

Client

import { createClient } from 'delight-rpc'

interface IAPI {
  foo(bar: string): string
}

const client = createClient<IAPI>(send)
const result = await client.foo('bar')

BatchClient

import { createBatchProxy, BatchClient } from 'delight-rpc'

interface IAPI {
  getNumber(): number
  getString(): string
}

const client = new BatchClient(send)
const proxy = createBatchProxy<IAPI>()
const results = await client.parallel(
  proxy.getNumber()
, proxy.getString()
)

// `Result` comes from the `return-style` module, it has Rust-like APIs
results[0] // Result<number, Error>
results[1] // Result<string, Error>

Server

import { createResponse, IRequest, IBatchRequest } from 'delight-rpc'

const api: IAPI = {
  foo(bar: string) {
    return bar
  }
}

async function handle(
  request: IRequest<unknown> | IBatchRequest<unknown>
): Promise<IResponse<unknown> | IBatchResponse<unknown>> {
  return await createResponse(api, request)
}

API

type ImplementationOf<Obj> = {
  [Key in FunctionKeys<Obj> | KeysByType<Obj, object>]:
    Obj[Key] extends (...args: infer Args) => infer Result
      ? (...args: [...args: Args, signal?: AbortSignal]) => Awaitable<Awaited<Result>>
      : ImplementationOf<Obj[Key]>
}

type ParameterValidators<Obj> = Partial<{
  [Key in FunctionKeys<Obj> | KeysByType<Obj, object>]:
    Obj[Key] extends (...args: infer Args) => unknown
      ? (...args: Args) => void
      : ParameterValidators<Obj[Key]>
}>

createClient

type ClientProxy<Obj> = {
  [Key in FunctionKeys<Obj> | KeysByType<Obj, object>]:
    Obj[Key] extends (...args: infer Args) => infer Result
      ? (...args: [...args: Args, signal?: AbortSignal]) => Promise<Awaited<Result>>
      : ClientProxy<Obj[Key]>
}

function createClient<API extends object, DataType = unknown>(
  send: (
    request: IRequest<DataType>
  , signal?: AbortSignal
  ) => Awaitable<IResponse<DataType>>
, options?: {
    parameterValidators?: ParameterValidators<API>
    expectedVersion?: string
    channel?: string
  }
): ClientProxy<API>

For easy distinction, when the method is not available, MethodNotAvailable will be thrown instead of the general Error.

BatchClient

type MapRequestsToResults<RequestTuple extends IRequestForBatchRequest<unknown, unknown>[]> = {
  [Index in keyof RequestTuple]:
    RequestTuple[Index] extends IRequestForBatchRequest<infer T, unknown>
    ? Result<T, Error>
    : never
}

class BatchClient<DataType = unknown> {
  constructor(
    send: (batchRequest: IBatchRequest<DataType>) => Awaitable<
    | IError
    | IBatchResponse<DataType>
    >
  , options?: {
      expectedVersion?: string
      channel?: string
    }
  )

  async parallel<T extends IRequestForBatchRequest<unknown, DataType>[]>(
    ...requests: T
  ): Promise<MapRequestsToResults<T>>

  async series<T extends IRequestForBatchRequest<unknown, DataType>[]>(
    ...requests: T
  ): Promise<MapRequestsToResults<T>>
}

createBatchProxy

type BatchClientProxy<Obj, DataType> = {
  [Key in FunctionKeys<Obj> | KeysByType<Obj, object>]:
    Obj[Key] extends (...args: infer Args) => infer Result
      ? (...args: Args) => IRequestForBatchRequest<Awaited<Result>, DataType>
      : BatchClientProxy<Obj[Key], DataType>
}

function createBatchProxy<API extends object, DataType = unknown>(
  options?: {
    parameterValidators?: ParameterValidators<API>
  }
): BatchClientProxy<API, DataType>

createResponse

const AnyChannel

function createResponse<API, DataType>(
  api: ImplementationOf<API>
, request: IRequest<DataType> | IBatchRequest<DataType>
, { parameterValidators = {}, version, channel, signal, ownPropsOnly = false }: {
    parameterValidators?: ParameterValidators<API>
    version?: `${number}.${number}.${number}`
    channel?: string | RegExp | typeof AnyChannel
    ownPropsOnly?: boolean
    signal?: AbortSignal
  } = {}
): Promise<null | IResponse<DataType> | IBatchResponse<DataType>>

createResponse returns null if the channel does not match.

MethodNotAvailable

class MethodNotAvailable extends CustomError {}

VersionMismatch

class VersionMismatch extends CustomError {}

InternalError

class InternalError extends CustomError {}

isRequst

function isRequest<DataType>(val: unknown): val is IRequest<DataType>

isResult

function isResult<DataType>(val: unknown): val is IResult<DataType>

isError

function isError(val: unknown): val is IError

isBatchRequest

function isBatchRequest<T>(val: unknown): val is IBatchRequest<T>

isBatchResponse

function isBatchResponse<T>(val: unknown): val is IBatchResponse<T> 

matchChannel

function matchChannel<DataType>(
  message: IDelightRPC
, channel:
  | undefined
  | string
  | RegExp
  | typeof AnyChannel
): boolean

createAbort

function createAbort(id: string, channel: string | undefined): IAbort

isAbort

function isAbort(val: unknown): val is IAbort