Skip to content

Generated Clients

The same route declarations that produce the server also generate a typed client for your website's frontend — no hand-written fetch calls, no drift between server and client.

sh
./weld app.weld --emit ts      -o api.ts      # TypeScript
./weld app.weld --emit js      -o api.js      # JS with JSDoc types
./weld app.weld --emit client  -o api         # both api.ts and api.js

Route clients

Path parameters become arguments and {?q} query references become a typed query object:

ts
import { WeldClient } from "./api.ts";

const api = new WeldClient({ baseUrl: "https://example.com" });

await api.getUsersById("42");                               // GET /users/42
await api.getUsersByIdPostsByPost("7", "99", { q: "new" }); // GET /users/7/posts/99?q=new
  • Record types become TypeScript interfaces; a route's -> T return type flows through to the client method's return type.
  • A body x: T param becomes a typed argument: postUsers(input: NewUser).
  • Non-2xx responses throw a WeldError carrying the status and body.
  • The .js output is identical but plain JavaScript with JSDoc @param / @returns, so types show up in editors with zero build step.

Hub clients

Every hub also generates a typed WebSocket client — a <Name>Hub class whose constructor takes the connect-time query params, whose methods are typed request/response calls correlated by id, and with on(event, cb) for server-pushed events:

ts
import { ChatHub } from "./api.ts";

const chat = new ChatHub("ws://localhost:8080", { token, room: "general" });
await chat.ready();
chat.on<ChatMsg>("ChatMsg", (m) => console.log(m.from, m.text));
await chat.say("general", "hi");        // typed method call

Verified end-to-end against a live hub with the generated JS client.