UNPKG

@xtr-dev/zod-rpc

Version:

Simple, type-safe RPC library with Zod validation and automatic TypeScript inference

125 lines 4.38 kB
import { Channel } from './channel.js'; import { createWebSocketTransport, createHTTPTransport } from './transports/index.js'; import { createServiceContracts } from './service.js'; /** * Create a type-safe RPC client for calling remote services. * * @param config - Client configuration including URL, services, and options * @returns Promise that resolves to a fully typed RPC client * * @example * ```typescript * const client = await createRPCClient({ * url: 'ws://localhost:8080', * services: { user: userService, math: mathService } * }); * * const user = await client.user.get({ userId: '123' }); * ``` * @group Client API */ export async function createRPCClient(config) { const { url, services, defaultTarget = 'auto', timeout = 30000, clientId = 'client' } = config; const transport = url.startsWith('ws://') || url.startsWith('wss://') ? createWebSocketTransport(url) : createHTTPTransport({ baseUrl: url }); const channel = new Channel(clientId, timeout); await channel.connect(transport); let currentDefaultTarget = defaultTarget; const client = {}; for (const [serviceName, serviceDefinition] of Object.entries(services)) { const contracts = createServiceContracts(serviceDefinition); const targetId = currentDefaultTarget === 'auto' ? serviceDefinition.id : currentDefaultTarget; const enhancedClient = {}; for (const [methodName, contract] of Object.entries(contracts)) { enhancedClient[methodName] = async (input, options) => { const actualTarget = options?.target || targetId; const actualTimeout = options?.timeout; return channel.invoke(actualTarget, contract.id, input, contract.input, contract.output, actualTimeout); }; } client[serviceName] = enhancedClient; } client.disconnect = () => channel.disconnect(); client.isConnected = () => transport.isConnected(); client.setDefaultTarget = (target) => { currentDefaultTarget = target; }; return client; } /** * Fluent builder pattern for creating RPC clients with advanced configurations. * Provides a chainable API for setting client options before building the final client. * * @example * ```typescript * const client = await connect('ws://localhost:8080') * .withId('my-client') * .withTimeout(10000) * .withServices({ user: userService, math: mathService }) * .build(); * ``` * * @group Client API */ export class RPCClientBuilder { config = {}; services = {}; url = ''; constructor(url) { this.url = url; this.config.transport = url.startsWith('ws://') || url.startsWith('wss://') ? 'websocket' : 'http'; } withId(clientId) { const newBuilder = new RPCClientBuilder(this.url); newBuilder.config = { ...this.config, clientId }; newBuilder.services = { ...this.services }; return newBuilder; } withTimeout(timeout) { const newBuilder = new RPCClientBuilder(this.url); newBuilder.config = { ...this.config, timeout }; newBuilder.services = { ...this.services }; return newBuilder; } withServices(services) { const newBuilder = new RPCClientBuilder(this.url); newBuilder.config = { ...this.config }; newBuilder.services = services; return newBuilder; } async build() { return createRPCClient({ url: this.url, services: this.services, clientId: this.config.clientId, timeout: this.config.timeout, }); } } /** * Create a new RPC client builder for fluent configuration. * This is the preferred way to create clients with custom settings. * * @param url - WebSocket or HTTP URL to connect to * @returns A new RPCClientBuilder instance for chaining configuration * * @example * ```typescript * const client = await connect('ws://localhost:8080') * .withId('my-client') * .withTimeout(10000) * .withServices({ user: userService }) * .build(); * ``` * * @group Client API */ export function connect(url) { return new RPCClientBuilder(url); } // Backward compatibility aliases export { RPCClientBuilder as RpcClientBuilder }; export { createRPCClient as createRpcClient }; //# sourceMappingURL=client.js.map