@axiomify/sdk-runtime
v6.3.2
Published
Runtime library for Axiomify-generated TypeScript SDKs — HTTP client, auth injection, retry engine, interceptors, caching, typed errors.
Maintainers
Readme
@axiomify/sdk-runtime
The @axiomify/sdk-runtime is the foundational HTTP networking library that powers all TypeScript clients generated by the axiomify sdk generate command.
Rather than bloating every single generated SDK file with complex logic for retries, interceptors, and authentication injection, the generated code outsources those responsibilities to this zero-dependency library.
Philosophy
- Zero Dependencies: Uses the native
fetchAPI. It does not wrap or bundle Axios, Got, or Node-Fetch. This keeps your client bundle size incredibly small. - Pluggable: Exposes an
InterceptorManagerto tap into the request lifecycle. - Resilient: Features an advanced
withRetryengine with exponential backoff and jitter designed specifically for server-to-server and high-latency edge environments.
Installation
Normally, you don't need to install this manually. If you used axiomify sdk generate -t typescript, this package is included as a dependency of the generated SDK.
However, if you generated only the types, you can install the runtime manually:
npm install @axiomify/sdk-runtimeBasic Usage
The runtime is initialized via the BaseClient. Your generated SDK will extend this class, but you will configure it using ClientConfig.
import { MyGeneratedSDK } from './generated-sdks';
const client = new MyGeneratedSDK({
baseUrl: 'https://api.example.com/v1',
timeoutMs: 10000, // default is 5000ms
});
const users = await client.getUsers({ limit: 100 });Interceptors
Interceptors allow you to mutate requests before they are sent, or intercept responses before they are returned to the caller.
// 1. Add a Request Interceptor
client.interceptors.request.use(async (req) => {
req.headers.set('X-Request-Start', Date.now().toString());
return req;
});
// 2. Add a Response Interceptor
client.interceptors.response.use(async (res) => {
if (res.status === 401) {
console.error('Unauthorized! Redirecting to login...');
}
return res;
});Retry Engine
Network requests fail. @axiomify/sdk-runtime handles retries automatically using exponential backoff and full jitter.
By default, the client retries 3 times on 429 Too Many Requests and 5xx server errors.
You can override the retry config at initialization:
const client = new MyGeneratedSDK({
baseUrl: 'https://api.example.com/v1',
retries: {
maxAttempts: 5, // Retry up to 5 times
initialDelayMs: 200, // Start with a 200ms delay
maxDelayMs: 5000, // Cap the delay at 5000ms
retryableStatuses: [429, 500, 502, 503, 504],
},
});Authentication
Injecting tokens into every request is tedious. Use the integrated AuthProvider to automatically manage tokens.
OAuth2 / Bearer Tokens
import { OAuth2BearerProvider } from '@axiomify/sdk-runtime';
const authProvider = new OAuth2BearerProvider({
getToken: async () => {
// Fetch from memory, local storage, or a secure credential store
return localStorage.getItem('access_token');
},
});
const client = new MyGeneratedSDK({
baseUrl: 'https://api.example.com/v1',
authProvider,
});The OAuth2BearerProvider automatically intercepts requests and attaches the Authorization: Bearer <token> header before the request hits the network.
Advanced: Raw Fetch Configuration
Because the runtime uses the native fetch API, you can pass arbitrary native fetch initialization options into the client.
const client = new MyGeneratedSDK({
baseUrl: 'https://api.example.com/v1',
fetchOptions: {
cache: 'no-store',
keepalive: true,
},
});Circuit Breaker
To prevent cascading failures in high-volume microservices or distributed systems, the SDK client wraps all outgoing requests in a circuit breaker. By default, it operates with standard thresholds, but you can configure the behavior to suit your system:
const client = new MyGeneratedSDK({
baseUrl: 'https://api.example.com/v1',
circuitBreakerConfig: {
failureThreshold: 5, // Number of failures before tripping the circuit
cooldownPeriodMs: 10000, // Time to wait (in ms) before attempting probe requests in HALF_OPEN state
halfOpenMaxProbeRequests: 3, // Max probe requests allowed in HALF_OPEN to check system health
},
});If the breaker trips, the client throws a CircuitBreakerError directly, shielding downstream dependencies until the cooldown period expires.
LRU TTL Caching
The runtime provides an in-memory Least Recently Used (LRU) cache with Time-To-Live (TTL) expiration. If enabled, it automatically caches incoming GET responses and returns them for matching paths and query configurations:
const client = new MyGeneratedSDK({
baseUrl: 'https://api.example.com/v1',
enableCache: true,
cacheTtlMs: 60000, // Cache GET responses for 60 seconds
});Additionally, the runtime performs Request Deduplication for identical in-flight GET requests, resolving multiple concurrent requests to the same endpoint with a single network round-trip.
Telemetry Hooks
Provide hooks to intercept and log request metadata, responses, or failures for auditing and APM integration:
const client = new MyGeneratedSDK({
baseUrl: 'https://api.example.com/v1',
telemetry: {
onBeforeRequest: (req) => {
console.log(`Sending ${req.method} request to ${req.path}`);
},
onAfterResponse: (res) => {
console.log(`Received status ${res.status} from ${res.request.path}`);
},
onError: (err) => {
console.error(`Request failed: ${err.message}`);
},
},
});Server-Sent Events (SSE) Client
The client runtime provides a dedicated SseClient to consume Server-Sent Events. It implements automatic reconnection logic with exponential backoff:
import { SseClient } from '@axiomify/sdk-runtime';
const sse = new SseClient('https://api.example.com/v1/live-feed', {
headers: {
Authorization: 'Bearer token123',
},
maxRetries: 5,
baseDelayMs: 1000,
onOpen: () => console.log('SSE connection opened'),
onMessage: (event, data) => console.log(`Received event: ${event}`, data),
onError: (err) => console.error('SSE Error:', err),
});
// Start listening
sse.connect();
// Stop listening
sse.disconnect();WebSocket Client
For full-duplex communication, use the WebSocketClient class. It manages standard browser/node WebSockets with customizable ping-pong heartbeats and auto-reconnection:
import { WebSocketClient } from '@axiomify/sdk-runtime';
const ws = new WebSocketClient('wss://api.example.com/v1/chat', {
heartbeatIntervalMs: 30000, // Send ping every 30 seconds
maxRetries: 10,
onOpen: () => {
console.log('WS connection active');
ws.send(JSON.stringify({ event: 'join', channel: 'general' }));
},
onMessage: (data) => console.log('WS Message:', data),
onClose: () => console.log('WS closed'),
});
ws.connect();Paginators
The Paginator handles cursor-based paginated endpoints seamlessly:
import { Paginator } from '@axiomify/sdk-runtime';
const paginator = new Paginator<User, { cursor?: string }>({
fetchPage: async (params) => {
const response = await client.listUsers(params.cursor);
return {
items: response.users,
nextCursor: response.nextCursor,
hasMore: !!response.nextCursor,
};
},
initialParams: {},
cursorParamName: 'cursor',
});
// Fetch pages sequentially
if (paginator.hasNext()) {
const users = await paginator.nextPage();
console.log('Fetched users:', users);
}Client Offline Queuing
For mobile or edge clients, the OfflineQueue caches requests during offline periods and flushes them automatically when the connection returns:
import { OfflineQueue } from '@axiomify/sdk-runtime';
const offlineQueue = new OfflineQueue();
// In offline state, queue operations
offlineQueue.enqueue({
path: '/users',
method: 'POST',
body: { name: 'Jane Doe', email: '[email protected]' },
});
// Flush automatically on navigator/window online event
// or call manually with a custom processor:
await offlineQueue.flush(async (queuedRequest) => {
await client.request({
path: queuedRequest.path,
method: queuedRequest.method as any,
body: queuedRequest.body,
});
});Environment Switcher
Manage target environments programmatically using the EnvironmentSwitcher:
import { EnvironmentSwitcher } from '@axiomify/sdk-runtime';
const environments = {
production: 'https://api.example.com/v1',
staging: 'https://api-staging.example.com/v1',
development: 'http://localhost:3000',
};
const switcher = new EnvironmentSwitcher(environments, 'development');
console.log(switcher.getUrl()); // http://localhost:3000
switcher.setEnvironment('production');
console.log(switcher.getUrl()); // https://api.example.com/v1