TwirpScript
TwirpScript is a Protocol Buffers (Protobuf) RPC framework designed for JavaScript and TypeScript environments. It automates the generation of both client and server code from `.proto` service definitions, facilitating type-safe communication in both browser and Node.js runtimes. The package is currently at version 0.0.72 and maintains an active release cadence with frequent minor updates addressing bug fixes and improvements. A key differentiator is its adherence to the Twirp Wire Protocol (v7) and its focus on minimizing bundle sizes, especially through tree-shaking and a lightweight runtime (2KB for TwirpScript, 37KB for the underlying ProtoScript serialization runtime). This makes it an efficient alternative to larger RPC frameworks, particularly for web applications requiring lean client bundles and strict type enforcement.
Common errors
-
Error: Command failed with exit code 1: protoscript --compiler-path ... The system cannot find the path specified.
cause The TwirpScript compiler path could not be resolved correctly on Windows, particularly in monorepo setups.fixUpdate to `twirpscript` version `0.0.72` or newer, which includes a fix for resolving the compiler path on Windows to support monorepos. -
Error: read EAGAIN
cause Intermittent `EAGAIN` errors encountered during the Protobuf compilation process.fixUpdate to `twirpscript` version `0.0.66` or newer, which includes a fix for this intermittent compilation issue. -
Cannot find module 'protoscript/compiler.js' from '.../buf.gen.yaml'
cause The path specified in `buf.gen.yaml` for the `protoc-gen-protoscript` plugin is incorrect or outdated.fixFor Buf users, update the plugin path in your `buf.gen.yaml` from `./node_modules/protoscript/compiler.js` to `./node_modules/protoscript/dist/compiler.js` (applicable for versions `v0.0.67` and newer).
Warnings
- breaking The configuration file format changed from a JSON file (`.twirp.json`) to a JavaScript ES module (`proto.config.mjs`).
- breaking For users leveraging Buf, the path to the `protoc-gen-protoscript` compiler within `buf.gen.yaml` has changed.
- gotcha All generated HTTP header names are now consistently lowercased, affecting both `createTwirpServer` (Node.js) and `createTwirpServerless` usages.
- gotcha Generated TypeScript imports for `.proto` files reverted to using `.pb` extensions instead of `.pb.js` when targeting ESM.
- gotcha Fix for incorrect JSON serialization of `Timestamp` and `Duration` types when their `seconds` or `nanos` fields were zero, causing them to be omitted from the JSON output.
Install
-
npm install twirpscript -
yarn add twirpscript -
pnpm add twirpscript
Imports
- createTwirpClient
const { createTwirpClient } = require('twirpscript')import { createTwirpClient } from 'twirpscript' - createTwirpServer
import createTwirpServer from 'twirpscript'
import { createTwirpServer } from 'twirpscript' - Config
import type { Config } from 'twirpscript/config'import { Config } from 'twirpscript'
Quickstart
// haberdasher.proto (example service definition)
// syntax = "proto3";
// package twirp.example;
// message Hat {
// int32 inches = 1;
// string color = 2;
// string name = 3;
// }
// message Size {
// int32 inches = 1;
// }
// service Haberdasher {
// rpc MakeHat(Size) returns (Hat);
// }
// After running TwirpScript compiler (e.g., twirpscript --proto_path=. haberdasher.proto),
// this would generate `haberdasher.pb.ts` and `haberdasher.twirp.ts`.
// client.ts
import { createTwirpClient } from 'twirpscript';
import { Haberdasher } from './haberdasher.twirp'; // Generated Twirp service client
import { Size } from './haberdasher.pb'; // Generated Protobuf message type
const BASE_URL = 'http://localhost:8080/twirp'; // Replace with your Twirp server URL
async function runClient() {
// Create a Twirp client for the Haberdasher service
const client = createTwirpClient(Haberdasher, BASE_URL);
try {
// Define the request message for MakeHat
const size: Size = { inches: 12 };
// Call the RPC method
const hat = await client.MakeHat(size);
console.log(`Successfully made a hat: ${hat.name} (${hat.color}, ${hat.inches} inches)`);
} catch (error) {
console.error('Error making hat:', error);
}
}
runClient();