Tiny, dependency-free runtime for typed OpenAPI clients. Pair it with an operations table (e.g. from @akashnetwork/console-api-types) and an OpenAPI paths type (from openapi-typescript) to get a fully typed fetch-based client with no codegen on the consumer side.
This is an internal workspace package. Add to a consumer's package.json:
"@akashnetwork/openapi-sdk": "*"import { createApi, ApiError } from "@akashnetwork/openapi-sdk";
import { operations, type paths } from "@akashnetwork/console-api-types";
const api = createApi<paths, typeof operations>(operations, {
baseUrl: "/api/proxy", // optional; "" by default (relative URL)
fetch: customFetch, // optional; defaults to global fetch
defaultHeaders: { "x-api-key": "..." } // optional; merged into every request
});
// Fully typed: path/query/body fields inferred from `paths`, flat at the top level
const alerts = await api.v1.getAlerts({ page: 1, limit: 10 });
const alert = await api.v1.getAlert({ id: "abc" });
const channel = await api.v1.createNotificationChannel({
data: { name: "Default", type: "email", config: { addresses: ["alice@example.com"] } }
});createApi returns a TypedClient<paths, operations> — a tree of typed async functions, grouped by whatever operations table you pass in (the console-api-types table groups by URL version prefix).
Non-2xx responses throw ApiError:
import { ApiError } from "@akashnetwork/openapi-sdk";
try {
await api.v1.getAlert({ id: "missing" });
} catch (err) {
if (err instanceof ApiError) {
err.status; // number
err.body; // unknown — parsed JSON or text
err.message; // e.g. "GET /v1/alerts/{id} → 404"
}
}function createApi<TPaths, TOps>(operations: TOps, config?: ClientConfig): TypedClient<TPaths, TOps>;
class ApiError extends Error { status: number; body: unknown }
type ClientConfig = {
baseUrl?: string;
fetch?: typeof fetch;
defaultHeaders?: Record<string, string>;
};
type CallOptions = {
headers?: Record<string, string>;
signal?: AbortSignal;
};
type TypedClient<TPaths, TOps>;
type Operation;
type OperationsTable;The runtime returns plain async functions. To get a tree of useQuery/useMutation hooks, wrap with @akashnetwork/react-query-proxy:
import { createApi } from "@akashnetwork/openapi-sdk";
import { createProxy } from "@akashnetwork/react-query-proxy";
import { operations, type paths } from "@akashnetwork/console-api-types";
const api = createApi<paths, typeof operations>(operations, { baseUrl: "/api/proxy" });
const apiHooks = createProxy(api);
// In a React component:
const { data, isLoading } = apiHooks.v1.getAlerts.useQuery({ page: 1 });
// Direct (non-hook) calls still work — the proxy keeps each operation node callable:
const alerts = await apiHooks.v1.getAlerts({ page: 1 });- No dependencies. The runtime uses native
fetchand JSProxy. The whole package is ~150 lines. - Symbol-safe proxies. Accessing unknown string keys, symbols (
Symbol.iterator,Symbol.toPrimitive), and notablythenreturnsundefined— so an operation node and the client itself are not thenable, and accidentalawait client.v1resolves to the proxy itself instead of throwing. - JSON in, JSON out by default. Bodies are
JSON.stringify'd when present; responses are parsed as JSON when the responsecontent-typeincludesapplication/json(including+jsonsubtypes), otherwise read as text. Empty bodies (e.g. 204 No Content) come back asnull. AbortSignalis forwarded to the underlyingfetchcall viaoptions.signal.- Headers are merged in this order:
defaultHeadersfromClientConfig, then per-calloptions.headers. Later entries win. The implicitcontent-type: application/jsonis added only when a body is sent and the caller hasn't supplied acontent-typealready.