@api-core/client
v0.0.2
Published
Transport-agnostic, type-safe API contract and execution engine core
Maintainers
Readme
@api-core
Transport-agnostic, type-safe API contract and execution engine core for JavaScript/TypeScript.
API-Core serves as the core engine to build type-safe HTTP clients for Axios, Playwright, k6, or custom executors. It provides a clean separation of concerns between declarative configuration, behavioral hooks, and request-specific definitions.
Installation
npm install @api-coreQuick Start
import { createApi, createClient, createApiRegistry } from '@api-core/client';
// 1. Define your API contract
const getUser = createApi({
method: 'GET',
endpoint: '/users/{id}',
});
// 2. Register your APIs
const registry = createApiRegistry({
users: { getUser }
});
// 3. Instantiate the client
const client = createClient({
config: { baseURL: 'https://api.example.com' },
registry
});
// 4. Perform type-safe requests
const response = await client.call('users.getUser', {
path: { id: 42 },
});Features
- Declarative API Contracts: Define your API surfaces as type-safe schema contracts.
- Unified Request Pipeline: Features a 3-pass hierarchical configuration resolution (
Client->Registry->API->Call). - Flexible Lifecycles: Support for global and local request hooks (
beforeCall,afterCall,onError). - Extensible Architecture: Core runtime accepts custom transport executors (e.g. Axios, Playwright, k6).
- TypeScript First: Exceptional type inference for parameters, query variables, paths, and response bodies.
API-CORE Architecture Documentation
Calling APIs
Use the HTTP verb methods as the primary client interface. Each method accepts only endpoints declared with its matching HTTP method, while retaining the request and response types declared by the contract.
const user = await client.get('users.getUser', {
path: { id: 42 },
query: { includeDetails: true },
});
const created = await client.post('users.createUser', {
body: { name: 'Ada' },
});Available methods are get, post, put, patch, delete, head, and
options. client.call() remains available for backward compatibility and is the
low-level API for dynamic contract execution.
GraphQL operations also use the same client pipeline, including client plugins, hooks, authentication, retry, timeout, metadata, and inherited configuration:
const result = await client.graphql<{ user: User }>({
query: 'query GetUser($id: ID!) { user(id: $id) { id } }',
variables: { id: 42 },
operationName: 'GetUser',
});Complete Resolution Model
The following centerpiece diagram illustrates how config, hooks, defaults, and call-time parameters resolve into a single Resolved Request consumed by the runtime:
Client
│
├── config
├── hooks
│
▼
Registry
│
├── config
├── hooks
│
▼
API
│
├── defaults
├── config
├── hooks
│
▼
Call
│
├── config
├── headers
├── body
│
▼
Resolved Request1. Declarative Configuration (config)
config contains declarative properties that participate in uniform 3-pass hierarchical resolution (Client -> Registry -> API -> Call).
Inheritable Configuration Interface (ApiConfig)
interface ApiConfig {
baseURL?: string;
basePath?: string;
headers?: Record<string, string>;
cookies?: Record<string, string>;
timeout?: number;
retry?: RetryConfig;
serializer?: string | unknown;
tags?: string[];
metadata?: Record<string, unknown>;
}Deep Merge Rules
- Primitives: Override
- Objects: Recursive deep merge
- Arrays: Replace (never concatenate)
- Special Objects: Replace immediately without deep merging (
Date,Blob,File,FormData,URLSearchParams,ArrayBuffer,Buffer, etc.) - URL Resolution & Normalization: Cleanly concatenates
baseURL+basePath+endpointwhile stripping duplicate slashes (/v1/+/users->/v1/users).
2. Behavioral Hooks (hooks)
hooks control lifecycle execution behavior:
interface ApiHooks {
beforeCall?: BeforeCallCallback | BeforeCallCallback[];
afterCall?: AfterCallCallback | AfterCallCallback[];
onError?: OnErrorCallback | OnErrorCallback[];
}Execution Order
beforeCall: Parent -> Child order (Client.hooks->Registry.hooks->API.hooks->Call.hooks)afterCall: Child -> Parent order (Call.hooks->API.hooks->Registry.hooks->Client.hooks)onError: Child -> Parent order (Call.hooks->API.hooks->Registry.hooks->Client.hooks)
3. Strongly Typed Retry Configuration (RetryConfig)
interface RetryConfig {
readonly strategy: 'constant' | 'linear' | 'exponential' | 'custom';
readonly attempts: number;
readonly delay: number; // in ms
readonly maxDelay?: number;
readonly backoffFactor?: number;
readonly when?: (error: Error, response?: unknown) => boolean;
}Architecture Example
// 1. Client Level
const client = createClient({
runtime,
registry,
config: {
baseURL: 'https://api.company.com/',
basePath: '/v1/',
timeout: 30000,
retry: {
attempts: 3,
delay: 1000,
strategy: 'exponential',
},
},
hooks: {
beforeCall: [
(ctx) => console.log(`[Logger] Request to: ${ctx.request.url}`),
(ctx) => console.log(`[Metrics] Starting timer`),
],
},
});
// 2. Registry Level
const registry = createApiRegistry(
{
users: {
getUser,
},
},
{
config: {
basePath: '/users-service/',
headers: { region: 'EU' },
},
}
);
// 3. API Contract Level
const getUser = createApi({
method: 'GET',
endpoint: '//users/{id}',
config: {
timeout: 10000,
},
});Example
For a complete and runnable example showing how to set up registries, configure multiple transport-specific clients (Axios, Fetch, Playwright, k6), and implement lifecycle hooks, please refer to the examples/src/index.ts file.
API Overview
createApi(config): Defines a type-safe HTTP contract endpoint.createApiRegistry(config): Groups multiple contracts under a structured namespace registry.createClient(options): Creates a unified client wrapper backed by an HTTP runtime executor.Runtime: The internal core executor orchestrator.AxiosExecutor: Standard transport implementation for Axios.
License
MIT © Shanmuka Chandra Teja Anem
Contributing
Contributions are welcome! Please read the root contributing guidelines, open an issue, or submit a pull request.
