delight-rpc

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

Usage no npm install needed!

<script type="module">
  import delightRpc from 'https://cdn.skypack.dev/delight-rpc';
</script>

README

delight-rpc

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

Install

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

API

/**
 * The reason why it is divided into two fields
 * is to make it easier to distinguish
 * when sharing channels with other protocols.
 * 
 * Reducing the size of payloads is not the goal of Delight RPC.
 */
interface IDelightRPC {
  protocol: 'delight-rpc'
  version: `2.${number}`

  [key: string]: unknown
}

interface IRequest<T> extends IDelightRPC {
  id: string

  /**
   * The expected server version, based on semver.
   */
  expectedVersion?: Nullable<`${number}.${number}.${number}`>
  
  /**
   * The `method` field can include the namespace it belongs to.
   * For example, `['config','save']` represents the `save` method
   * under the namespace `config`.
   */
  method: string[]
  params: T[]
}

type IResponse<T> = IResult<T> | IError

interface IResult<T> extends IDelightRPC {
  id: string
  result: T
}

interface SerializableError {
  name: string
  message: string
  stack: string | null
  ancestors: string[]
}

interface IError extends IDelightRPC {
  id: string
  error: SerializedError 
}

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

createClient

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

function createClient<Obj extends object, DataType = unknown>(
  send: (request: IRequest<DataType>) => PromiseLike<IResponse<DataType>>
, parameterValidators: ParameterValidators<Obj> = {}
, expectedVersion?: `${number}.${number}.${number}`
): ClientProxy<Obj>

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

MethodNotAvailable

class MethodNotAvailable extends CustomError {}

VersionMismatch

class VersionMismatch extends CustomError {}

InternalError

class InternalError extends CustomError {}

createResponse

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

function createResponse<Obj extends object, DataType = unknown>(
  api: ImplementationOf<Obj>
, request: IRequest<DataType>
, parameterValidators: ParameterValidators<Obj> = {}
, version?: `${number}.${number}.${number}`
): Promise<IResponse<DataType>>

isRequest

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