An open API service indexing awesome lists of open source software.

https://github.com/varavelio/vdl-plugin-rpc-ts

Plugin to generate VDL RPC for TypeScript
https://github.com/varavelio/vdl-plugin-rpc-ts

api codegen rpc ts typescript varavel vdl vdl-plugin vdl-rpc

Last synced: 9 days ago
JSON representation

Plugin to generate VDL RPC for TypeScript

Awesome Lists containing this project

README

          


VDL logo

VDL RPC TypeScript Plugin


Generate TypeScript RPC clients and RPC servers from annotation-based VDL services.



A Varavel project


VDL Plugin

This plugin is RPC-only.

It does not generate business types, enums, or constants. Those come from [`varavelio/vdl-plugin-ts`](https://github.com/varavelio/vdl-plugin-ts), and this plugin references them through `typesImport`.

All examples below use `varavelio/vdl-plugin-rpc-ts@v0.1.0`.

## Quick Start

1. Generate TypeScript models with `varavelio/vdl-plugin-ts`.
2. Generate RPC client (`target "client"`) and/or RPC server (`target "server"`).
3. Run your normal VDL generation command (for example: `vdl generate`).

```vdl
const config = {
version 1
plugins [
{
src "varavelio/vdl-plugin-ts@v0.1.4"
schema "./schema.vdl"
outDir "./generated/types"
options {
importExtension "js"
}
}
{
src "varavelio/vdl-plugin-rpc-ts@v0.1.0"
schema "./schema.vdl"
outDir "./generated/client"
options {
target "client"
typesImport "../types/index.js"
importExtension "js"
}
}
{
src "varavelio/vdl-plugin-rpc-ts@v0.1.0"
schema "./schema.vdl"
outDir "./generated/server"
options {
target "server"
typesImport "../types/index.js"
importExtension "js"
}
}
]
}
```

If you only need one side, keep only one RPC plugin block.

## Plugin Options

| Option | Type | Required | Default | What it changes |
| ----------------- | ------------------------ | -------- | ------- | ----------------------------------------------------------------------------------------------------- |
| `typesImport` | `string` | yes | - | Import path pointing to the output generated by `varavelio/vdl-plugin-ts` (used exactly as provided). |
| `target` | `"client" \| "server"` | yes | - | Selects what to generate in that invocation. The plugin generates one target at a time. |
| `importExtension` | `"none" \| "js" \| "ts"` | no | `"js"` | Controls internal imports between generated RPC files. It does not rewrite `typesImport`. |

## Generated Files

For `target "client"`:

- `client.ts`

For `target "server"`:

- `server.ts`
- `adapters/node.ts`
- `adapters/fetch.ts`

If your schema has no discovered `@rpc`, `@proc` or `@stream` operations, no files are emitted.

## What You Get

For `target "client"`:

- `NewClient(baseURL).build()` to create a typed client.
- Flattened procedure and stream builders under `client.procs.*()` and `client.streams.*()`.
- Global and operation-level headers (static or dynamic providers).
- Interceptors.
- Retry/timeout for procedures and reconnect policies for streams.
- Stream lifecycle hooks and maximum message size controls.

For `target "server"`:

- `new Server()` with typed request context.
- Typed registration APIs for procedures and streams.
- Middleware at global, RPC, procedure, stream, and stream-emit levels.
- Global and RPC-level error handlers.
- Global, RPC-level, and stream-level ping configuration.
- `createNodeHandler(...)` and `createFetchHandler(...)` adapters for HTTP runtimes.

Both targets include runtime catalogs:

- `VDLPaths`
- `VDLProcedures`
- `VDLStreams`

Non-marker annotations are preserved in operation metadata.

## RPC Annotation Model

This plugin follows the VDL annotation model:

- `@rpc` marks a top-level type as an RPC service.
- `@proc` marks a field as a request-response operation.
- `@stream` marks a field as a server-streaming operation.

```vdl
@rpc
type Messages {
@proc
send {
input {
roomId string
text string
}

output {
accepted bool
}
}

@stream
events {
input {
roomId string
}

output {
text string
}
}
}
```

## Usage Example

### Client

```ts
import { NewClient } from "./generated/client/client";

const client = NewClient("http://localhost:3000/rpc")
.withGlobalHeader("authorization", "Bearer ")
.build();

const procOutput = await client.procs.messagesSend().execute({
roomId: "room-1",
text: "hello",
});

console.log(procOutput.accepted);

const { stream, cancel } = client.streams.messagesEvents().execute({
roomId: "room-1",
});

for await (const event of stream) {
if (!event.ok) {
console.error(event.error);
break;
}

console.log(event.output.text);
}

cancel();
```

### Server (Node.js adapter)

```ts
import { createServer } from "node:http";
import { Server } from "./generated/server/server";
import { createNodeHandler } from "./generated/server/adapters/node";

type Context = { requestId: string };

const rpcServer = new Server();

rpcServer.rpcs
.messages()
.procs.send()
.handle(async (_ctx) => {
return { accepted: true };
});

rpcServer.rpcs
.messages()
.streams.events()
.handle(async (ctx, emit) => {
await emit(ctx, { text: `joined ${ctx.input.roomId}` });
});

const handler = createNodeHandler(rpcServer, () => ({ requestId: "req-1" }), {
prefix: "/rpc",
});

createServer(async (req, res) => {
await handler(req, res);
}).listen(3000);
```

### Server (Fetch-compatible adapter)

```ts
import { Server } from "./generated/server/server";
import { createFetchHandler } from "./generated/server/adapters/fetch";

const rpcServer = new Server();

export default {
fetch: createFetchHandler(rpcServer, undefined, { prefix: "/rpc" }),
};
```

## Important Notes

- `baseURL` in `NewClient(baseURL)` should point to your RPC prefix (for example: `https://api.example.com/rpc`).
- The generated client appends `/{rpcName}/{operationName}` automatically.
- `typesImport` must point to the generated output from `vdl-plugin-ts`.
- To generate both client and server, run this plugin twice (one block per `target`).

## License

This plugin is released under the MIT License. See [LICENSE](LICENSE).