Lightweight REST client for Block65 services. Pairs with @block65/openapi-codegen, which generates Command classes that this client knows how to dispatch.
Runs in Node and browsers — uses platform globalThis.fetch and standard Web APIs (Request, Response, ReadableStream, AbortController).
pnpm add @block65/rest-clientResponse validation runs on any Standard Schema validator. valibot is an optional peer dependency — install it, or another spec-compliant validator, only if you opt in:
pnpm add valibotimport { RestServiceClient } from "@block65/rest-client";
import { GetAccountCommand } from "./generated/commands.ts";
const client = new RestServiceClient("https://api.example.com", {
headers: {
"x-build-id": "abc123",
authorization: () => Promise.resolve(`Bearer ${await getToken()}`),
},
});
const account = await client.json(new GetAccountCommand({ accountId: "1234" }));client.json(command)— setscontent-type: application/json, returns the parsed body. ThrowsServiceErroron>=400.client.send(command)— same as above but inherits the command's content type.client.stream(command)— returns aReadableStream<Uint8Array>for non-JSON / streaming responses.
Header values can be functions or async functions, resolved per-request:
new RestServiceClient(url, {
headers: {
authorization: async () => `Bearer ${await refreshToken()}`,
},
});Swap the underlying fetch implementation, or replace the whole fetcher pipeline:
new RestServiceClient(url, { fetch: customFetch });
new RestServiceClient(url, {
fetcher: createIsomorphicNativeFetcher({ retry: { retries: 5 } }),
});The default fetcher retries idempotent (GET) requests and supports timeouts and merged abort signals.
When a generated command class exposes a static responseSchema (any Standard Schema validator, such as valibot), the client automatically runs the schema against successful responses — useful for coercing JSON-unsafe types like int64 strings into BigInt.
Schema presence on the command is the sole trigger; there is no client-level flag. Consumers opt in by importing from the codegen's validated commands file (lean imports skip schema attachment, so no validator loads and there's no bundle cost).
import { RestServiceClient } from "@block65/rest-client";
const client = new RestServiceClient(url, { fetcher });
// GetAccountCommand.responseSchema coerces { id: "123" } → { id: 123n }
const account = await client.json(new GetAccountCommand());
account.id; // bigintCommands without a responseSchema pass the body through untouched. Validation failures throw ResponseValidationError, carrying the command, the url, and the schema's issues.
Exported helper that serializes BigInt values as strings (since JSON has no native bigint):
import { jsonStringify } from "@block65/rest-client";
jsonStringify({ amount: 123n });
// '{"amount":"123"}'This closes the int64 round-trip when paired with a coerced response schema:
server → "123" (JSON string)
client → 123n (after responseSchema parse)
client → "123" (jsonStringify back to wire)
import { ResponseValidationError, ServiceError } from "@block65/rest-client";
try {
await client.json(cmd);
} catch (err) {
if (err instanceof ServiceError) {
err.code; // status code from @block65/custom-error
err.response; // original Response
}
}MIT