@xtr-dev/zod-rpc
Version:
Simple, type-safe RPC library with Zod validation and automatic TypeScript inference
125 lines • 4.38 kB
JavaScript
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