This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
bun install- Install dependencies (use bun, not npm)bun run index.ts <path>- Generate RPC code from interface definitionsbun run index.ts <path> --watch- Watch mode for automatic regeneration
bun run index.ts ./examples/00-full-app/pkg/rpc/define.ts- Generate from full app example (complete working application)bun run index.ts ./examples/01-basic/define.ts- Generate from basic example (simple interfaces)bun run index.ts ./examples/00-full-app/pkg/rpc/define.ts --package-name "my-rpc" --timeout 3000
This is a TypeScript code generator for Socket.IO RPC packages. The tool generates type-safe client-server communication code from interface definitions.
Main Generator (index.ts)
- CLI entry point using
commander - Core generation logic using
ts-morphAST manipulation - Extracts function signatures from
ClientFunctionsandServerFunctionsinterfaces - Generates client.generated.ts, server.generated.ts, and types.generated.ts files
Key Generation Process
- Parse input TypeScript file containing interface definitions
- Extract function signatures from
ClientFunctionsandServerFunctionsinterfaces - Generate bidirectional RPC functions:
- Client functions call server methods
- Server functions call client methods
- Handler functions set up event listeners
- Generate factory functions (
createRpcClient,createRpcServer) for ergonomic API - Generate error handling with
RpcErrortype - Output complete package with TypeScript declarations
- Factory functions -
createRpcClient()/createRpcServer()for ergonomic API - Client/Server interfaces -
RpcClient,RpcServerwith.handle,.server/.client,.dispose() - Error handling - Built-in
RpcErrortype andisRpcError()guard - Type safety - Full TypeScript support with generated type imports
- Must define
ClientFunctionsandServerFunctionsinterfaces - Do NOT use
Promisein interface return types (automatically wrapped) - Use
voidfor fire-and-forget functions - Non-void functions automatically get acknowledgment handling and timeout support
The generator creates createRpcClient() and createRpcServer() factory functions that provide a clean API with automatic cleanup.
Client Side:
import { createRpcClient } from './rpc/client.generated';
const rpc = createRpcClient(socket);
// Register handlers with rpc.handle.* (for calls FROM server)
rpc.handle.showError(async (error) => {
console.error('Error:', error);
});
rpc.handle.onProgress(async (current, total) => {
console.log(`Progress: ${current}/${total}`);
});
// Make RPC calls with rpc.server.* (calls TO server)
const result = await rpc.server.generateText("Hello!");
// Single cleanup call
rpc.dispose();Server Side:
import { createRpcServer } from './rpc/server.generated';
io.on('connection', (socket) => {
const rpc = createRpcServer(socket);
// Register handlers with rpc.handle.* (for calls FROM client)
rpc.handle.generateText(async (prompt) => {
// Call client methods via rpc.client.* (calls TO client)
rpc.client.showError(new Error("Something happened"));
return "Generated: " + prompt;
});
// Cleanup on disconnect
socket.on('disconnect', () => rpc.dispose());
});<script setup lang="ts">
import { onBeforeUnmount } from 'vue';
import { socket } from './socket';
import { createRpcClient } from './rpc/client.generated';
const rpc = createRpcClient(socket);
// Register handlers - no manual tracking needed
rpc.handle.showError(async (error) => {
console.error('Error:', error);
});
rpc.handle.onProgress(async (current, total) => {
console.log(`Progress: ${current}/${total}`);
});
// Single cleanup call handles everything
onBeforeUnmount(() => rpc.dispose());
</script>import { useEffect, useRef } from 'react';
import { socket } from './socket';
import { createRpcClient, RpcClient } from './rpc/client.generated';
function MyComponent() {
const rpcRef = useRef<RpcClient>();
useEffect(() => {
const rpc = createRpcClient(socket);
rpcRef.current = rpc;
rpc.handle.showError(async (error) => {
console.error('Error:', error);
});
return () => rpc.dispose();
}, []);
return <div>My Component</div>;
}// RpcClient interface
interface RpcClient {
handle: {
// Register handlers for server-to-client calls. Returns an unsubscribe function.
// Re-registering the same name replaces the previous handler.
showError: (handler: (error: Error) => Promise<void>) => UnsubscribeFunction;
askQuestion: (handler: (question: string) => Promise<string>) => UnsubscribeFunction;
// ...
};
server: {
// Call server methods. `opts` carries timeout / AbortSignal / volatile.
generateText: (prompt: string, opts?: RpcCallOptions) => Promise<string | RpcError>;
// ...
};
socket: Socket; // Underlying socket
connected: boolean; // Whether the socket is currently connected
onConnect(handler: () => void): UnsubscribeFunction; // re-sync on (re)connect
onDisconnect(handler: (reason: string) => void): UnsubscribeFunction;
onReconnect(handler: (attempt: number) => void): UnsubscribeFunction;
disposed: boolean; // Whether disposed
dispose(): void; // Cleanup all handlers
}
// RpcServer interface
interface RpcServer {
handle: {
// Register handlers for client-to-server calls. Returns an unsubscribe function.
generateText: (handler: (prompt: string) => Promise<string>) => UnsubscribeFunction;
// ...
};
client: {
// Call client methods. `opts` carries timeout / AbortSignal / volatile.
showError: (error: Error, opts?: RpcCallOptions) => void;
askQuestion: (question: string, opts?: RpcCallOptions) => Promise<string | RpcError>;
// ...
};
socket: Socket; // Underlying socket
connected: boolean; // Whether the socket is currently connected
onDisconnect(handler: (reason: string) => void): UnsubscribeFunction;
disposed: boolean; // Whether disposed
dispose(): void; // Cleanup all handlers
}RpcErroris branded with a__rpcError: truefield;isRpcError()checks the brand, so a successful result shaped like{ message, code }is never misread as an error.- Handlers signal failure by throwing —
throw rpcError(code, message, data?)for a typed error, or any thrown value (normalized toINTERNAL_ERROR). Do not returnRpcErrorfrom a handler. - Standard codes:
TIMEOUT,DISPOSED,DISCONNECTED,ABORTED,INTERNAL_ERROR,INVALID_ARGUMENT. --error-mode throwmakes calls reject with theRpcErrorinstead of returningT | RpcError.
pkg/rpc/
├── define.ts # Interface definitions (input)
├── client.generated.ts # Generated client RPC (includes createRpcClient)
├── server.generated.ts # Generated server RPC (includes createRpcServer)
├── types.generated.ts # Generated types and error handling
├── index.ts # Package entry point
├── package.json # Generated package config
└── tsconfig.json # Generated TypeScript config
The tool automatically infers the output directory from the input file path and generates a complete npm package structure.