Skip to main content

Overview

World Monitor uses sebuf, a TypeScript-native RPC framework built on Protocol Buffers. This proto-first approach provides:
  • Type safety — Compile-time checking across client and server
  • Auto-generated code — Clients, servers, and OpenAPI specs from .proto files
  • HTTP/JSON transport — Web-standard REST-like endpoints
  • Breaking change detectionbuf breaking catches API incompatibilities

Workflow

1. Define Service

Create a .proto file in proto/worldmonitor/<domain>/v1/:
// proto/worldmonitor/seismology/v1/service.proto
syntax = "proto3";

package worldmonitor.seismology.v1;

import "sebuf/http/annotations.proto";
import "worldmonitor/seismology/v1/list_earthquakes.proto";

service SeismologyService {
  option (sebuf.http.service_config) = {base_path: "/api/seismology/v1"};

  rpc ListEarthquakes(ListEarthquakesRequest) returns (ListEarthquakesResponse) {
    option (sebuf.http.config) = {
      path: "/list-earthquakes"
      method: HTTP_METHOD_GET
    };
  }
}

2. Define Messages

Create request and response messages:
// proto/worldmonitor/seismology/v1/list_earthquakes.proto
syntax = "proto3";

package worldmonitor.seismology.v1;

import "worldmonitor/seismology/v1/earthquake.proto";

message ListEarthquakesRequest {
  double min_magnitude = 1;
  int32 days = 2;
}

message ListEarthquakesResponse {
  repeated Earthquake earthquakes = 1;
}
// proto/worldmonitor/seismology/v1/earthquake.proto
syntax = "proto3";

package worldmonitor.seismology.v1;

message Earthquake {
  string id = 1;
  double magnitude = 2;
  string location = 3;
  int64 timestamp = 4;
  double latitude = 5;
  double longitude = 6;
  double depth_km = 7;
}

3. Generate Code

Run the code generator:
make generate
This creates:
src/generated/
├── client/worldmonitor/seismology/v1/
│   ├── service_client.ts          # TypeScript client
│   ├── earthquake.ts              # Message types
│   └── list_earthquakes.ts        # Request/response types
└── server/worldmonitor/seismology/v1/
    └── service_server.ts          # Server interface

docs/api/
├── worldmonitor.seismology.v1.yaml   # OpenAPI 3.0 spec
└── worldmonitor.seismology.v1.json   # OpenAPI 3.0 (JSON)

4. Implement Handler

Create a server-side handler in server/worldmonitor/<domain>/v1/handler.ts:
// server/worldmonitor/seismology/v1/handler.ts
import type { SeismologyService } from '@/generated/server/worldmonitor/seismology/v1/service_server';
import type { ListEarthquakesRequest, ListEarthquakesResponse } from '@/generated/client/worldmonitor/seismology/v1/list_earthquakes';

export const seismologyHandler: SeismologyService = {
  async listEarthquakes(
    req: ListEarthquakesRequest,
    context: RequestContext
  ): Promise<ListEarthquakesResponse> {
    // Fetch from USGS API
    const url = `https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson`;
    const response = await fetch(url);
    const data = await response.json();

    // Transform to proto response
    const earthquakes = data.features
      .filter(f => f.properties.mag >= req.minMagnitude)
      .map(f => ({
        id: f.id,
        magnitude: f.properties.mag,
        location: f.properties.place,
        timestamp: f.properties.time,
        latitude: f.geometry.coordinates[1],
        longitude: f.geometry.coordinates[0],
        depthKm: f.geometry.coordinates[2],
      }));

    return { earthquakes };
  },
};

5. Register Routes

Wire the handler into the router (vite.config.ts for dev, Vercel for prod):
// vite.config.ts (dev server plugin)
import { createSeismologyServiceRoutes } from './src/generated/server/worldmonitor/seismology/v1/service_server';
import { seismologyHandler } from './server/worldmonitor/seismology/v1/handler';

const allRoutes = [
  ...createSeismologyServiceRoutes(seismologyHandler, serverOptions),
  // ... other services
];

const router = createRouter(allRoutes);

6. Call from Client

Use the generated client in your frontend:
// src/services/seismology.ts
import { createSeismologyServiceClient } from '@/generated/client/worldmonitor/seismology/v1/service_client';

const client = createSeismologyServiceClient({ baseUrl: '/api/seismology/v1' });

export async function fetchEarthquakes(minMagnitude = 4.5, days = 1) {
  const response = await client.listEarthquakes({
    minMagnitude,
    days,
  });

  return response.earthquakes;
}
// src/components/EarthquakePanel.ts
import { fetchEarthquakes } from '@/services/seismology';

const earthquakes = await fetchEarthquakes(5.0, 7);
console.log(`Found ${earthquakes.length} earthquakes M5.0+ in the last 7 days`);

Generated Client API

The generated client provides a type-safe interface:
interface SeismologyServiceClient {
  listEarthquakes(
    request: ListEarthquakesRequest
  ): Promise<ListEarthquakesResponse>;
}

function createSeismologyServiceClient(options: {
  baseUrl: string;
  fetch?: typeof fetch;
}): SeismologyServiceClient;
Features:
  • Automatic serialization — Messages are JSON-encoded
  • Custom fetch — Inject your own fetch (useful for testing)
  • Error handling — HTTP errors throw with status codes

Generated Server API

The generated server creates HTTP routes:
interface SeismologyService {
  listEarthquakes(
    request: ListEarthquakesRequest,
    context: RequestContext
  ): Promise<ListEarthquakesResponse>;
}

function createSeismologyServiceRoutes(
  service: SeismologyService,
  options?: ServerOptions
): Route[];
Each route includes:
  • HTTP method — GET, POST, PUT, DELETE
  • Path pattern/api/seismology/v1/list-earthquakes
  • Handler — Executes your service implementation

HTTP Mapping

Request Mapping

Proto FieldHTTP MethodLocation
Scalar (string, int)GETQuery parameter
MessagePOST/PUTJSON body
Repeated scalarGETComma-separated query param
Repeated messagePOSTJSON array in body

Example Mappings

rpc ListEarthquakes(ListEarthquakesRequest) returns (ListEarthquakesResponse) {
  option (sebuf.http.config) = {
    path: "/list-earthquakes"
    method: HTTP_METHOD_GET
  };
}

message ListEarthquakesRequest {
  double min_magnitude = 1;
  int32 days = 2;
}

Service Domains

World Monitor defines 20 service domains:
DomainPurposeExample RPCs
aviationAirport delays, flight trackingListAirportDelays
climateClimate anomalies, weather alertsListClimateAnomalies
conflictActive conflicts, war zonesListConflicts
cyberThreat intel, IOCs, CVEsListThreatIndicators
displacementRefugee flows, UNHCR dataListDisplacementFlows
economicFRED data, economic indicatorsGetEconomicIndicator
givingCharitable donations, aid flowsListDonations
infrastructureCables, pipelines, portsListUnderseaCables
intelligenceHotspots, focal pointsListIntelligenceHotspots
maritimeAIS vessels, naval activityListVessels
marketStock quotes, crypto pricesListMarketData
militaryMilitary bases, flightsListMilitaryBases
newsRSS feeds, headlinesListNews
positive_eventsGood news, uplifting storiesListPositiveEvents
predictionPolymarket, prediction marketsListPredictions
researcharXiv papers, research feedsListResearchPapers
seismologyEarthquakes, USGS dataListEarthquakes
supply_chainSupply chain disruptionsListSupplyChainEvents
tradeTrade routes, WTO dataListTradeRoutes
unrestProtests, social unrestListUnrestEvents

Buf CLI Commands

Generate Code

make generate
Runs buf generate with plugins configured in proto/buf.gen.yaml:
plugins:
  - local: protoc-gen-ts-client
    out: ../src/generated/client
  - local: protoc-gen-ts-server
    out: ../src/generated/server
  - local: protoc-gen-openapiv3
    out: ../docs/api

Lint Protos

make lint
Checks proto files against style rules:
  • Service names must end with Service
  • RPC names must use PascalCase
  • Field names must use snake_case

Detect Breaking Changes

make breaking
Compares current proto definitions against main branch:
  • Field removals
  • Field type changes
  • Field number changes

Format Protos

make format
Auto-formats proto files using buf format.

Update Dependencies

make deps
Updates proto dependencies defined in proto/buf.yaml.

OpenAPI Generation

Every service generates OpenAPI 3.0 specs in docs/api/:
# docs/api/worldmonitor.seismology.v1.yaml
openapi: 3.0.0
info:
  title: SeismologyService
  version: v1
paths:
  /api/seismology/v1/list-earthquakes:
    get:
      operationId: ListEarthquakes
      parameters:
        - name: min_magnitude
          in: query
          schema:
            type: number
        - name: days
          in: query
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListEarthquakesResponse'
These specs can be imported into:
  • Postman — API testing
  • Swagger UI — Interactive documentation
  • OpenAPI Generator — Client generation for other languages

Desktop Sidecar Bundling

The Tauri desktop app runs a local Node.js sidecar that serves all API handlers. The sidecar bundle is built separately:
npm run build:sidecar-sebuf
This script (scripts/build-sidecar-sebuf.mjs):
  1. Bundles api/[domain]/v1/[rpc].ts with esbuild
  2. Outputs a single ESM file: api/[domain]/v1/[rpc].js
  3. The sidecar discovers and loads this file at runtime

Best Practices

Service Design

Keep services small and focused. Each service should handle one domain (e.g., seismology, aviation).
Never break wire compatibility. Changing field numbers or types breaks existing clients.

Versioning

All services use v1 for now. When breaking changes are needed, create a new v2 package alongside v1.

Error Handling

Handlers should throw errors with HTTP status codes:
export const seismologyHandler: SeismologyService = {
  async listEarthquakes(req, context) {
    if (req.minMagnitude < 0) {
      throw new Error('min_magnitude must be >= 0');
    }

    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`USGS API error: ${response.status}`);
    }

    // ...
  },
};
The server automatically maps these to HTTP error responses.

Testing

Test handlers using the generated client:
import { createSeismologyServiceClient } from '@/generated/client/worldmonitor/seismology/v1/service_client';

const client = createSeismologyServiceClient({ baseUrl: 'http://localhost:3000/api/seismology/v1' });

const response = await client.listEarthquakes({ minMagnitude: 5.0, days: 7 });
assert(response.earthquakes.length > 0);

Next Steps

Building

Build production and desktop apps

Testing

Run E2E and API tests

Build docs developers (and LLMs) love