Show HN: TypeBridge – Compile-time RPC for client/server
14 hours ago
2
A compile-time RPC system for TypeScript that allows seamless function calls between client and server code with full type safety.
TypeBridge transforms server imports into RPC calls at build time, eliminating boilerplate while preserving complete TypeScript type information across the client-server boundary.
// Complex API with hooks and query keysconstuser=trpc.user.getUser.useQuery('user-123');constcreateUserMutation=trpc.user.createUser.useMutation();
✅ TypeBridge Server Definition:
// Just regular TypeScript functionsexportasyncfunctiongetUser(id: string): Promise<User>{returnawaitdb.findUser(id);}exportasyncfunctioncreateUser(userData: CreateUserData): Promise<User>{returnawaitdb.createUser(userData);}
✅ TypeBridge Client Usage:
// Direct imports and function callsimport{getUser,createUser}from'../../server/api/users';constuser=awaitgetUser('user-123');constnewUser=awaitcreateUser({name: 'John',email: '[email protected]'});
❌ React Server Actions Oddities:
// Server Action requires 'use server' directiveasyncfunctioncreateUser(formData: FormData){'use server';// Awkward FormData parsingconstname=formData.get('name')asstring;constemail=formData.get('email')asstring;returnawaitdb.createUser({ name, email });}// Client: Form-specific, limited to form submissions<formaction={createUser}><inputname="name"/><inputname="email"/><buttontype="submit">Create</button></form>// For non-form usage, needs bindingconstboundAction=createUser.bind(null,userData);
✅ TypeBridge Approach:
// Server: Regular functionexportasyncfunctioncreateUser(userData: CreateUserData): Promise<User>{returnawaitdb.createUser(userData);}// Client: Clean form with automatic data conversionimport{Form}from'@seamless-rpc/core';import{createUser}from'../../server/api/users';<Formaction={createUser}><inputname="name"/><inputname="email"/><buttontype="submit">Create</button></Form>// Also works in event handlers, useEffect, anywhereconsthandleClick=async()=>{constuser=awaitcreateUser(userData);};
🎯 Zero Learning Curve: Write server functions, import them in client code - that's it
⚡ Natural TypeScript: No schemas, procedures, or directives - just functions
🚀 Universal Usage: Call server functions anywhere, not just forms or hooks
📦 Minimal Overhead: No runtime libraries, just optimized RPC calls
🔧 Any Framework: Works with React, Vue, Svelte, vanilla JS
🎨 True Imports: Real ES6 imports that your IDE understands completely
📝 Smart Forms: Automatic FormData to object conversion with full type safety
Zero Ceremony: Import server functions and call them like local functions
Full Type Safety: Complete TypeScript support with no type loss
Smart Forms: TypeBridge <Form> component with automatic data conversion
Fast Builds: Sub-second rebuild times with Bun
Great DX: Clear errors, hot reload, debugging support
Production Ready: Optimized bundles, robust error handling
Framework Agnostic: Works with React, Vue, Svelte, Angular
# Install dependencies
bun install
# Build all packages
bun run build:all
# Run the sample application
bun run dev
Current Limitation: TypeBridge Form component doesn't automatically handle file uploads through RPC calls, as files cannot be JSON serialized across the network boundary.
Recommended Solution: Use dedicated file upload services for clean architecture:
// Recommended approach using Uppy or Filestackimport{Uppy}from'@uppy/core';import{createUser}from'../../server/api/users';constuppy=newUppy({restrictions: {maxFileSize: 5000000}// 5MB});exportfunctionUserForm(){const[uploadedFileUrl,setUploadedFileUrl]=useState<string>('');useEffect(()=>{uppy.on('upload-success',(file,response)=>{setUploadedFileUrl(response.uploadURL);});},[]);return(<Formaction={createUser}><inputname="name"/><inputname="email"/><inputname="avatarUrl"type="hidden"value={uploadedFileUrl}/>{/* File upload handled separately */}<divid="uppy-upload"></div><buttontype="submit"disabled={!uploadedFileUrl}>CreateUser</button></Form>);}// Server function receives clean URLexportasyncfunctioncreateUser(data: {name: string;email: string;avatarUrl: string;// Just a URL string}){returnawaitdb.createUser(data);}
Why this approach works better:
Separation of concerns: File handling separate from business logic
Performance: Files upload independently, no blocking RPC calls
# Install dependencies
bun install
# Build all packages in development mode
bun run dev:packages
# Run type checking across all packages
bun run type-check:all
# Clean all build artifacts
bun run clean
Primitives: string, number, boolean, null
Date objects
Arrays and plain objects
Nested combinations of the above
Collections: Map, Set
Advanced types: BigInt, RegExp
Custom serializable classes
Client HMR: React components update instantly with Vite Fast Refresh
Server Hot Reload: Server function changes automatically restart backend
Detailed Errors: Full server stack traces in development
Call Logging: All RPC calls are logged with timing information
Type Safety: Compile-time validation of function signatures
Tree Shaking: Unused server functions are removed from client bundles
Optimized Serialization: Efficient JSON serialization with Date handling
Request Deduplication: Automatic deduplication of identical requests
Retry Logic: Exponential backoff for network failures