返回 Skill 列表
extension
分类: 开发与工程无需 API Key

typesafe-rpc

适用于Node/TS的类型安全RPC库,支持端到端类型。定义了模式(实体/操作/处理器)、createRpcHandler(服务器端)、createRpcClient(客户端)、路由和中间件。在编辑typesafe-rpc、添加RPC操作、连接服务器或客户端,或在此仓库中实现处理器和中间件时使用。

person作者: jakexiaohubgithub

typesafe-rpc

Overview

This repo is a type-safe RPC library. The server accepts POST bodies { entity, operation, params } and runs the matching handler. The client is a typed proxy: client.entity.operation(params, signal?, context?). Types are shared so client calls are inferred from the schema.

For real-time server-push/subscriptions over WebSocket, see the companion typesafe-ws package (skills/typesafe-ws/SKILL.md) in this same repo — typesafe-rpc itself is HTTP request/response only.

Core types (shared)

  • BaseContext: { request: Request | Express.Request }
  • Args<Params, Context>: { params: Params; context: Context }
  • Handler<Params, Context, Result>: (args: Args<Params, Context>) => Promise<Result>
  • RpcSchema: { [entity: string]: { [operation: string]: Handler<any, any, any> } }

Handlers receive only params and context (no ExtraParams). Extend BaseContext for app-specific context.

Schema and handlers

Schema is a nested object: entity → operation → handler. Use as const so the client gets literal types.

import type { BaseContext, Handler } from 'typesafe-rpc';

type Ctx = BaseContext & { userId?: string };

const getItem: Handler<{ id: string }, Ctx, { name: string }> = async ({ params, context }) => {
  return { name: 'Item ' + params.id };
};

export const apiSchema = {
  items: {
    getById: getItem,
  },
} as const;

Server: createRpcHandler

From typesafe-rpc/server:

import { createRpcHandler } from 'typesafe-rpc/server';

const result = await createRpcHandler({
  context: { request },           // must include request
  operations: apiSchema,
  errorHandler: (error) => new Response(JSON.stringify({ error: '...' }), { status: 500 }),
  hooks: {
    preCall: (args) => {},
    postCall: (args, performance) => {},
    error: (args, performance, error) => {},
  },
});
  • Expects context.request.method === 'POST'; body must be JSON { entity, operation, params }.
  • Hook args: { entity, operation, params, context }.
  • Returns the handler result directly; throws Response on error.
  • If no errorHandler, throws a generic 500 Response.

Client: createRpcClient

From typesafe-rpc/client:

import { createRpcClient } from 'typesafe-rpc/client';
import type { apiSchema } from './api-schema';

// Static headers
const client = createRpcClient<typeof apiSchema>('/api/rpc', { Authorization: 'Bearer ...' });

// Dynamic headers via function (receives context passed to call)
const client = createRpcClient<typeof apiSchema, MyContext>('/api/rpc', (ctx) => ({
  Authorization: ctx.token,
}));

const result = await client.items.getById({ id: '1' });
const withAbort = await client.items.getById({ id: '1' }, signal);
const withContext = await client.items.getById({ id: '1' }, undefined, context);

Client signature: client.entity.operation(params, signal?, context?). Calls POST endpoint?entity::operation with body { entity, operation, params }. Use the same schema type (typeof apiSchema) for full inference.

Route and middlewares

From typesafe-rpc/server: Route, Middleware, orMiddleware.

  • Middleware<Params, Context>: (args: Args<Params, Context>) => Promise<void> (throw to abort).
  • Route: chain .middleware(...fns) then .handle(handler).
    • Multiple .middleware(a, b, c): OR — first success wins (via orMiddleware).
    • Chained .middleware(a).middleware(b): AND — all run in order.
  • orMiddleware(...middlewares): runs middlewares in order; returns on first that doesn't throw; if all throw, rethrows the first error.
  • OverridableHandler: the handler returned by .handle() has an overrideMiddlewares(...middlewares) method to replace middlewares (useful for testing).
import { Route } from 'typesafe-rpc/server';

const handler = new Route<{ id: string }, BaseContext>()
  .middleware(authOrAnonymous)
  .middleware(requireReadPermission)
  .handle(async ({ params, context }) => ({ name: '...' }));

// For testing: bypass middlewares
handler.overrideMiddlewares(mockAuth);

Use the resulting handler as the function stored in the schema (e.g. getById: handler).

Request/response and errors

  • Server reads body via request.json() (Fetch) or request.body (Express).
  • Client sends JSON and parses response with response.json(). Non-ok responses throw FetchError.

FetchError

From typesafe-rpc/client:

import { FetchError } from 'typesafe-rpc/client';

class FetchError extends Error {
  readonly key: string;     // error key from response JSON, or 'internalError'
  readonly status: number;  // HTTP status code
  readonly data?: any;      // optional data from response JSON
}

The client parses error responses as JSON { key, message, data }. If parsing fails, key defaults to 'internalError' and message is the raw response text.

Conventions in this repo

  • Schema and shared types live in shared/; server in server/, client in client/.
  • Implementations follow the types in libs/typesafe-rpc/src/shared/rpc-types.ts. Prefer those over README if they differ (e.g. no ExtraParams in Handler).
  • For full API and examples, see the project README.