Skip to content

Protocol Buffers to ReScript codegen with JSON codecs and gRPC-web client stubs

License

Unknown, Unknown licenses found

Licenses found

Unknown
LICENSE
Unknown
LICENSE.txt
Notifications You must be signed in to change notification settings

hyperpolymath/rescript-grpc

Repository files navigation

rescript-grpc

Overview

rescript-grpc provides a complete .proto → ReScript codegen pipeline:

  • protoc-gen-rescript - Rust-based protoc plugin generating type-safe ReScript

  • @rescript-grpc/runtime - JSON encode/decode runtime (proto3 JSON mapping)

  • gRPC-web client stubs - Optional async HTTP clients for services

Features

  • Type-safe ReScript types from .proto definitions

  • JSON encode/decode codecs following proto3 JSON mapping specification

  • Polymorphic variants for enum types

  • Optional gRPC-web client stubs (opt-in via --rescript_opt=grpc)

  • Topological sorting ensures message dependencies compile correctly

  • Proto3 field semantics (scalars required, messages optional, optional keyword supported)

  • Zero npm dependencies (uses Deno or works standalone)

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    Build Time (protoc)                          │
├─────────────────────────────────────────────────────────────────┤
│  .proto  ──→  protoc-gen-rescript                               │
│                    │                                            │
│                    └──→ ServiceProto.res  (ReScript types)      │
│                              │                                  │
│                              ├─ Message modules with make()     │
│                              ├─ toJson()/fromJson() codecs      │
│                              ├─ Enum toInt()/fromInt()          │
│                              └─ ServiceClient (if --grpc)       │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Runtime                                      │
├─────────────────────────────────────────────────────────────────┤
│  open UserProto                                                 │
│                                                                 │
│  let user = User.make(~name="Alice", ~id=42, ~status=#Active)   │
│  let json = User.toJson(user)                                   │
│  let decoded = User.fromJson(json)                              │
│                                                                 │
│  // With gRPC-web client                                        │
│  let result = await UserServiceClient.getUser(                  │
│    ~config={baseUrl: "http://localhost:8080", headers: None},   │
│    ~request=GetUserRequest.make(~id=1)                          │
│  )                                                              │
└─────────────────────────────────────────────────────────────────┘

Installation

Prerequisites

Install the protoc plugin

# From source
cargo install --path protoc-gen-rescript

# Or build locally
cd protoc-gen-rescript && cargo build --release
# Add target/release to PATH

Add runtime dependency

// rescript.json
{
  "bs-dependencies": [
    "@rescript-grpc/runtime"
  ]
}

Or copy runtime/src/Json.res and runtime/src/Fetch.res directly into your project.

Usage

Generate ReScript from .proto

# Basic generation (types + JSON codecs)
protoc --rescript_out=./src ./protos/user.proto

# With gRPC-web client stubs
protoc --rescript_out=./src --rescript_opt=grpc ./protos/user.proto

Proto3 Example

// user.proto
syntax = "proto3";
package example;

enum Status {
  STATUS_UNKNOWN = 0;
  STATUS_ACTIVE = 1;
  STATUS_INACTIVE = 2;
}

message User {
  string name = 1;
  int32 id = 2;
  optional string email = 3;  // Optional scalar
  Status status = 4;
  repeated string tags = 5;
  Address address = 6;        // Message fields always optional
}

message Address {
  string street = 1;
  string city = 2;
  string country = 3;
}

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}

Generated ReScript

// UserProto.res (generated)
module Status = {
  type t = [#StatusUnknown | #StatusActive | #StatusInactive]
  let toInt = (v: t): int => ...
  let fromInt = (i: int): option<t> => ...
}

module Address = {
  type t = { street: string, city: string, country: string }
  let make = (~street, ~city, ~country): t => ...
  let toJson = (msg: t): Js.Json.t => ...
  let fromJson = (json: Js.Json.t): option<t> => ...
}

module User = {
  type t = {
    name: string,
    id: int,
    email: option<string>,
    status: Status.t,
    tags: array<string>,
    address: option<Address.t>,
  }
  let make = (~name, ~id, ~email=?, ~status, ~tags=[], ~address=?): t => ...
  let toJson = (msg: t): Js.Json.t => ...
  let fromJson = (json: Js.Json.t): option<t> => ...
}

module UserServiceClient = {
  type config = { baseUrl: string, headers: option<Js.Dict.t<string>> }
  type error = NetworkError(string) | GrpcError(int, string) | DecodeError(string)

  let getUser = async (~config, ~request: GetUserRequest.t): result<User.t, error> => ...
  let listUsers = async (~config, ~request: ListUsersRequest.t): result<ListUsersResponse.t, error> => ...
}

Use in application

// Example.res
open UserProto

let alice = User.make(
  ~name="Alice",
  ~id=1,
  ~email="alice@example.com",
  ~status=#StatusActive,
  ~tags=["admin", "developer"],
  ~address=Address.make(~street="123 Main St", ~city="SF", ~country="USA"),
)

// JSON round-trip
let json = User.toJson(alice)
let decoded = User.fromJson(json)

// gRPC-web call
let fetchUser = async (id: int) => {
  let config = { baseUrl: "http://localhost:8080", headers: None }
  let request = GetUserRequest.make(~id)
  await UserServiceClient.getUser(~config, ~request)
}

Project Structure

rescript-grpc/
├── protoc-gen-rescript/     # Rust protoc plugin
│   ├── Cargo.toml
│   └── src/
│       ├── main.rs          # Plugin entry point
│       ├── generator.rs     # Code generation logic
│       └── templates.rs     # ReScript code templates
├── runtime/                 # @rescript-grpc/runtime
│   ├── rescript.json
│   └── src/
│       ├── Json.res         # JSON encode/decode helpers
│       └── Fetch.res        # Fetch API bindings for gRPC-web
├── codec/                   # WASM codec (optional, for binary proto)
│   ├── Cargo.toml
│   └── src/lib.rs
└── examples/
    └── basic/
        ├── protos/user.proto
        └── src/
            ├── UserProto.res  # Generated
            └── Example.res    # Usage example

Proto3 JSON Mapping

The generated codecs follow the proto3 JSON mapping specification:

Proto Type JSON Representation

int32, sint32, sfixed32

number

int64, sint64, sfixed64

string (for precision)

float, double

number

bool

boolean

string

string

bytes

base64 string

enum

integer value

message

object

repeated

array

Roadmap

  • ✓ protoc plugin (Rust)

  • ✓ Type-safe message generation

  • ✓ JSON encode/decode codecs

  • ✓ Enum polymorphic variants

  • ✓ gRPC-web client stubs

  • ✓ Topological message sorting

  • ❏ Streaming RPC support

  • ❏ Server-side handlers

  • ❏ Binary protobuf codec (WASM)

  • ❏ Well-known types (google.protobuf.*)

  • ❏ OneOf support

License

MPL-2.0. See LICENSE.txt.

About

Protocol Buffers to ReScript codegen with JSON codecs and gRPC-web client stubs

Topics

Resources

License

Unknown, Unknown licenses found

Licenses found

Unknown
LICENSE
Unknown
LICENSE.txt

Security policy

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •