Adapters
One of APIHive's most powerful features is its extensible adapter architecture. Adapters allow you to add functionality like caching, authentication, logging, and API integration in isolation from your application's business logic.
What are Adapters?
Adapters are plugins that extend APIHive's functionality through:
- Interceptors - Modify/monitor/guard requests and responses
- Response Body Transformers - Transform response bodies at runtime
- Factory Defaults - Apply configuration to all requests
- Lifecycle Hooks - React to adapter attachment/detachment
Community Adapters
If you create a general purpose adapter, please share it with the community by submitting it to APIHive. Check this page for more information where you can also find other developed adapters.
Creating Custom Adapters
Basic Adapter Structure
typescript
import { Adapter, RequestInterceptor, ResponseInterceptor, ErrorInterceptor } from '@apihive/core';
export class MyCustomAdapter implements Adapter {
readonly name = 'my-custom-adapter';
readonly use = ['retry'];
readonly priority = {
requestInterceptor: 100, // Lower numbers run first
responseInterceptor: 100,
errorInterceptor: 100,
responseBodyTransformer: 100,
};
// Called when adapter is attached
onAttach(factory: HTTPRequestFactory) {
console.log('Adapter attached to factory');
}
// Called when adapter is detached
onDetach(factory: HTTPRequestFactory) {
console.log('Adapter detached from factory');
}
// Request interceptors run before the request is sent
getRequestInterceptors(): RequestInterceptor[] {
return [
async ({ config, controls, factory }) => {
// Do stuff here
}
];
}
// Response interceptors run after successful responses
getResponseInterceptors(): ResponseInterceptor[] {
return [
async ({ response, controls, config, factory }) => {
// Do stuff here
}
];
}
// Error interceptors run when requests fail
getErrorInterceptors(): ErrorInterceptor[] {
return [
async (error) => {
// Do stuff here
return false; // Let other error handlers run
}
];
}
getResponseBodyTransformers(): ResponseBodyTransformer[] {
return [
async ({ body, config, controls, factory }) => {
// transform response body
return body;
}
];
}
// Factory defaults applied to all requests
getFactoryDefaults() {
return [
(request: HTTPRequest) => {
request.withHeader('X-Adapter-Applied', this.name);
}
];
}
}
Adapter Priority
Control execution order with priorities:
typescript
export class HighPriorityAdapter implements Adapter {
readonly name = 'high-priority';
readonly priority = {
requestInterceptor: 10, // Runs early
responseInterceptor: 10,
errorInterceptor: 10
};
//...
}
export class LowPriorityAdapter implements Adapter {
readonly name = 'low-priority';
readonly priority = {
requestInterceptor: 500, // Runs late
responseInterceptor: 500,
errorInterceptor: 500
};
//...
}
Testing Adapters
typescript
import { describe, it, expect, vi } from 'vitest';
import { HTTPRequestFactory } from '@apihive/core';
import { MyCustomAdapter } from './MyCustomAdapter';
describe('MyCustomAdapter', () => {
it('should_add_custom_header_to_requests', async () => {
const factory = new HTTPRequestFactory()
.withAdapter(new MyCustomAdapter());
// Mock fetch to capture the request
const mockFetch = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ success: true }))
);
global.fetch = mockFetch;
await factory.createGETRequest('https://api.example.com/test').execute();
expect(mockFetch).toHaveBeenCalledWith(
'https://api.example.com/test',
expect.objectContaining({
headers: expect.objectContaining({
'X-Custom-Header': 'CustomValue'
})
})
);
});
it('should_transform_responses', async () => {
const factory = new HTTPRequestFactory()
.withAdapter(new MyCustomAdapter());
global.fetch = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ data: 'test' }))
);
const result = await factory
.createGETRequest('https://api.example.com/test')
.execute();
expect(result).toMatchObject({
data: 'test',
_interceptedBy: 'my-custom-adapter',
_timestamp: expect.any(String)
});
});
});
Documentation
Include comprehensive documentation:
- Installation instructions
- Configuration options
- Usage examples
- TypeScript types
- Migration guides
Best Practices
1. Single Responsibility
Each adapter should focus on one concern:
typescript
// Good - focused on caching
class CacheAdapter implements Adapter { }
// Bad - doing too much
class CacheAuthLoggingAdapter implements Adapter { }
2. Configuration Validation
Validate adapter configuration:
typescript
export class MyAdapter implements Adapter {
constructor(private config: MyAdapterConfig) {
if (!config.apiKey) {
throw new Error('MyAdapter requires apiKey in configuration');
}
}
}
3. Graceful Degradation
Handle failures gracefully:
typescript
getRequestInterceptors(): RequestInterceptor[] {
return [
async (config, controls) => {
try {
await this.enhanceRequest(config, controls);
} catch (error) {
// Log error but don't break the request
console.warn('Adapter enhancement failed:', error);
}
}
];
}
4. TypeScript Support
Provide full TypeScript definitions:
typescript
export interface MyAdapterConfig {
apiKey: string;
timeout?: number;
retries?: number;
}
export class MyAdapter implements Adapter {
constructor(config: MyAdapterConfig) { }
}