533 lines
16 KiB
Markdown
533 lines
16 KiB
Markdown
# @lilith/client-base Implementation Summary
|
|
|
|
## Package Overview
|
|
|
|
`@lilith/client-base` provides abstract interfaces for HTTP and WebSocket clients with middleware composition, enabling implementation-agnostic code that follows the Dependency Inversion Principle.
|
|
|
|
**Version**: 1.0.0
|
|
**Location**: `/var/home/lilith/Code/@packages/@ts/client-base`
|
|
**Build Status**: ✅ Successful
|
|
**Type Check**: ✅ Passed
|
|
|
|
## Implementation Files
|
|
|
|
### Core Abstractions
|
|
|
|
#### HTTP Client (`src/http/`)
|
|
- **types.ts**: HTTP types (request/response configs, methods, interceptors)
|
|
- **abstract-http-client.ts**: `AbstractHttpClient` interface with method signatures
|
|
- **request-builder.ts**: Fluent API for building HTTP requests
|
|
|
|
#### WebSocket Client (`src/websocket/`)
|
|
- **types.ts**: WebSocket types (states, lifecycle hooks, messages)
|
|
- **abstract-websocket-client.ts**: `AbstractWebSocketClient` interface
|
|
|
|
#### Error Handling (`src/errors/`)
|
|
- **client-error.ts**: Error hierarchy
|
|
- `ClientError` (base)
|
|
- `HttpError` (statusCode, response)
|
|
- `WebSocketError` (code)
|
|
- `TimeoutError` (timeoutMs)
|
|
- `AbortError`
|
|
- `MiddlewareError` (middlewareName)
|
|
|
|
### Middleware System
|
|
|
|
#### Core Middleware (`src/middleware/`)
|
|
- **types.ts**: Middleware types and execution context
|
|
- **interceptor-manager.ts**: Generic interceptor chain executor
|
|
- **http-middleware.ts**: HTTP-specific middleware chain
|
|
- **websocket-middleware.ts**: WebSocket-specific middleware chain
|
|
|
|
#### Built-in Middleware (`src/middleware/builtin/`)
|
|
- **auth-middleware.ts**: JWT/token injection
|
|
- `createAuthMiddleware()` - Header-based auth
|
|
- `createQueryAuthMiddleware()` - Query param auth
|
|
- **retry-middleware.ts**: Retry configuration (integrates with `@lilith/retry`)
|
|
- **logging-middleware.ts**: Request/response logging
|
|
- `createRequestLoggingMiddleware()`
|
|
- `createResponseLoggingMiddleware()`
|
|
- `createErrorLoggingMiddleware()`
|
|
- **timeout-middleware.ts**: Timeout management
|
|
- URL-specific timeouts
|
|
- Method-specific timeouts
|
|
- `createTimeoutSignal()` utility
|
|
|
|
### Factory System
|
|
|
|
#### Factories (`src/factory/`)
|
|
- **http-client-factory.ts**: HTTP client factory and builder
|
|
- `registerHttpClientImplementation()` - Registry
|
|
- `createHttpClient()` - Factory function
|
|
- `HttpClientBuilder` - Fluent builder
|
|
- **websocket-client-factory.ts**: WebSocket client factory and builder
|
|
- `registerWebSocketClientImplementation()` - Registry
|
|
- `createWebSocketClient()` - Factory function
|
|
- `WebSocketClientBuilder` - Fluent builder
|
|
- **client-composition.ts**: Composed clients
|
|
- `ComposedClient` - HTTP + WebSocket
|
|
- `createClientFactory()` - Generic factory
|
|
- `createHttpOnlyClientFactory()`
|
|
- `createWebSocketOnlyClientFactory()`
|
|
|
|
## Key Interfaces
|
|
|
|
### AbstractHttpClient
|
|
|
|
```typescript
|
|
interface AbstractHttpClient {
|
|
// Core execution
|
|
execute<T>(config: HttpRequestConfig): Promise<HttpResponse<T>>;
|
|
|
|
// Convenience methods
|
|
get<T>(url: string, config?: Partial<HttpRequestConfig>): Promise<HttpResponse<T>>;
|
|
post<T>(url: string, data?: unknown, config?: Partial<HttpRequestConfig>): Promise<HttpResponse<T>>;
|
|
put<T>(url: string, data?: unknown, config?: Partial<HttpRequestConfig>): Promise<HttpResponse<T>>;
|
|
patch<T>(url: string, data?: unknown, config?: Partial<HttpRequestConfig>): Promise<HttpResponse<T>>;
|
|
delete<T>(url: string, config?: Partial<HttpRequestConfig>): Promise<HttpResponse<T>>;
|
|
head(url: string, config?: Partial<HttpRequestConfig>): Promise<HttpResponse<void>>;
|
|
|
|
// Interceptors
|
|
readonly interceptors: {
|
|
request: InterceptorManager<HttpRequestInterceptor, HttpRequestErrorInterceptor>;
|
|
response: InterceptorManager<HttpResponseInterceptor, HttpResponseErrorInterceptor>;
|
|
};
|
|
|
|
// Configuration
|
|
getBaseURL(): string | undefined;
|
|
setBaseURL(baseURL: string): void;
|
|
getDefaultHeaders(): Record<string, string>;
|
|
setDefaultHeaders(headers: Record<string, string>): void;
|
|
getTimeout(): number | undefined;
|
|
setTimeout(timeout: number): void;
|
|
}
|
|
```
|
|
|
|
### AbstractWebSocketClient
|
|
|
|
```typescript
|
|
interface AbstractWebSocketClient {
|
|
// Lifecycle
|
|
connect(): void;
|
|
disconnect(): void;
|
|
reconnect(): void;
|
|
|
|
// Messaging
|
|
send<T>(message: WebSocketMessage<T>): void;
|
|
sendWithAck<T, R>(message: WebSocketMessage<T>, timeout?: number): Promise<R>;
|
|
|
|
// Events
|
|
subscribe<T>(event: string, listener: EventListener<T>): UnsubscribeFunction;
|
|
subscribeWithAck<T, R>(event: string, listener: EventListenerWithAck<T, R>): UnsubscribeFunction;
|
|
once<T>(event: string, listener: EventListener<T>): UnsubscribeFunction;
|
|
unsubscribe(event: string, listener?: EventListener): void;
|
|
unsubscribeAll(): void;
|
|
|
|
// State
|
|
getStatus(): WebSocketStatus;
|
|
isConnected(): boolean;
|
|
getRawSocket(): unknown;
|
|
waitForConnection(timeout?: number): Promise<void>;
|
|
}
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### HTTP Client with Middleware
|
|
|
|
```typescript
|
|
import {
|
|
registerHttpClientImplementation,
|
|
HttpClientBuilder,
|
|
createAuthMiddleware,
|
|
createTimeoutMiddleware,
|
|
createRequestLoggingMiddleware,
|
|
} from '@lilith/client-base';
|
|
|
|
// Register implementation (once, at app bootstrap)
|
|
registerHttpClientImplementation('axios', (config) => new AxiosHttpClient(config));
|
|
|
|
// Create configured client
|
|
const client = HttpClientBuilder.create('axios')
|
|
.baseURL('https://api.example.com')
|
|
.timeout(10000)
|
|
.header('X-API-Version', 'v1')
|
|
.requestMiddleware(createAuthMiddleware({
|
|
getToken: () => localStorage.getItem('auth_token'),
|
|
}))
|
|
.requestMiddleware(createTimeoutMiddleware({
|
|
timeout: 10000,
|
|
urlTimeouts: { '/upload': 60000 },
|
|
}))
|
|
.requestMiddleware(createRequestLoggingMiddleware())
|
|
.build();
|
|
|
|
// Use client
|
|
const response = await client.get<User[]>('/users');
|
|
console.log(response.data);
|
|
```
|
|
|
|
### WebSocket Client
|
|
|
|
```typescript
|
|
import {
|
|
registerWebSocketClientImplementation,
|
|
WebSocketClientBuilder,
|
|
} from '@lilith/client-base';
|
|
|
|
// Register implementation
|
|
registerWebSocketClientImplementation('socket.io', (config) => new SocketIOClient(config));
|
|
|
|
// Create client
|
|
const wsClient = WebSocketClientBuilder.create('socket.io')
|
|
.url('wss://api.example.com')
|
|
.token('auth-token')
|
|
.reconnection(true)
|
|
.reconnectionAttempts(5)
|
|
.onConnect(() => {
|
|
console.log('Connected');
|
|
})
|
|
.build();
|
|
|
|
// Subscribe to events
|
|
const unsubscribe = wsClient.subscribe<UserUpdate>('user.updated', (data) => {
|
|
console.log('User updated:', data);
|
|
});
|
|
|
|
// Send messages
|
|
wsClient.send({ event: 'chat.message', data: { text: 'Hello!' } });
|
|
```
|
|
|
|
### Service with Dependency Injection
|
|
|
|
```typescript
|
|
import { AbstractHttpClient } from '@lilith/client-base';
|
|
|
|
class UserService {
|
|
constructor(private readonly http: AbstractHttpClient) {}
|
|
|
|
async getUsers(): Promise<User[]> {
|
|
const response = await this.http.get<User[]>('/users');
|
|
return response.data;
|
|
}
|
|
|
|
async createUser(user: CreateUserDto): Promise<User> {
|
|
const response = await this.http.post<User>('/users', user);
|
|
return response.data;
|
|
}
|
|
}
|
|
|
|
// Inject any implementation
|
|
const service = new UserService(axiosClient);
|
|
// OR
|
|
const service = new UserService(fetchClient);
|
|
// OR
|
|
const service = new UserService(mockClient); // for tests
|
|
```
|
|
|
|
## Testing Strategy
|
|
|
|
### Mock HTTP Client
|
|
|
|
```typescript
|
|
import { AbstractHttpClient, HttpRequestConfig, HttpResponse } from '@lilith/client-base';
|
|
import { createInterceptorManager } from '@lilith/client-base/middleware';
|
|
|
|
export class MockHttpClient implements AbstractHttpClient {
|
|
public requests: HttpRequestConfig[] = [];
|
|
public responses = new Map<string, any>();
|
|
|
|
interceptors = {
|
|
request: createInterceptorManager(),
|
|
response: createInterceptorManager(),
|
|
};
|
|
|
|
mockResponse<T>(url: string, data: T, status = 200): void {
|
|
this.responses.set(url, { data, status });
|
|
}
|
|
|
|
async execute<T>(config: HttpRequestConfig): Promise<HttpResponse<T>> {
|
|
this.requests.push(config);
|
|
|
|
const mock = this.responses.get(config.url);
|
|
if (!mock) {
|
|
throw new Error(`No mock response for ${config.url}`);
|
|
}
|
|
|
|
return {
|
|
data: mock.data,
|
|
status: mock.status,
|
|
statusText: 'OK',
|
|
headers: {},
|
|
config,
|
|
};
|
|
}
|
|
|
|
// ... implement other methods
|
|
}
|
|
|
|
// Usage in tests
|
|
describe('UserService', () => {
|
|
it('fetches users', async () => {
|
|
const mockClient = new MockHttpClient();
|
|
mockClient.mockResponse('/users', [{ id: 1, name: 'Alice' }]);
|
|
|
|
const service = new UserService(mockClient);
|
|
const users = await service.getUsers();
|
|
|
|
expect(users).toHaveLength(1);
|
|
expect(mockClient.requests).toHaveLength(1);
|
|
});
|
|
});
|
|
```
|
|
|
|
## Migration Strategy
|
|
|
|
### Phase 1: Register Implementation
|
|
|
|
```typescript
|
|
// main.ts
|
|
import { registerHttpClientImplementation } from '@lilith/client-base';
|
|
import { AxiosHttpClient } from '@lilith/http-client-axios';
|
|
|
|
registerHttpClientImplementation('axios', (config) => new AxiosHttpClient(config));
|
|
```
|
|
|
|
### Phase 2: Update Services
|
|
|
|
```typescript
|
|
// Before
|
|
class UserService {
|
|
async getUsers() {
|
|
return axios.get('/users');
|
|
}
|
|
}
|
|
|
|
// After
|
|
class UserService {
|
|
constructor(private readonly http: AbstractHttpClient) {}
|
|
|
|
async getUsers() {
|
|
return this.http.get('/users');
|
|
}
|
|
}
|
|
```
|
|
|
|
### Phase 3: Migrate Interceptors to Middleware
|
|
|
|
```typescript
|
|
// Before
|
|
axiosInstance.interceptors.request.use((config) => {
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
return config;
|
|
});
|
|
|
|
// After
|
|
client.interceptors.request.use(createAuthMiddleware({
|
|
getToken: () => token,
|
|
}));
|
|
```
|
|
|
|
## Integration with @lilith/retry
|
|
|
|
Retry functionality integrates through middleware:
|
|
|
|
```typescript
|
|
import { retry } from '@lilith/retry';
|
|
import { createRetryMiddleware } from '@lilith/client-base/middleware';
|
|
|
|
const retryMiddleware = createRetryMiddleware({
|
|
attempts: 3,
|
|
delay: 1000,
|
|
backoff: 'exponential',
|
|
shouldRetry: (error, response) => {
|
|
return response?.status === 429 || response?.status >= 500;
|
|
},
|
|
});
|
|
|
|
client.interceptors.response.use(undefined, retryMiddleware);
|
|
```
|
|
|
|
## Implementation Checklist
|
|
|
|
- [x] HTTP client abstractions
|
|
- [x] WebSocket client abstractions
|
|
- [x] Error hierarchy
|
|
- [x] Interceptor manager
|
|
- [x] HTTP middleware chain
|
|
- [x] WebSocket middleware chain
|
|
- [x] Built-in auth middleware
|
|
- [x] Built-in logging middleware
|
|
- [x] Built-in timeout middleware
|
|
- [x] Built-in retry middleware
|
|
- [x] HTTP client factory and registry
|
|
- [x] WebSocket client factory and registry
|
|
- [x] Builder patterns
|
|
- [x] Client composition
|
|
- [x] Request builder (fluent API)
|
|
- [x] Type-safe generics
|
|
- [x] Comprehensive documentation
|
|
- [x] Build configuration
|
|
- [x] Successful build
|
|
- [x] Type checking
|
|
|
|
## Next Steps
|
|
|
|
### Immediate (Required for Task #3)
|
|
|
|
1. **Create axios implementation**: `@lilith/http-client-axios`
|
|
- Implement `AbstractHttpClient` using axios
|
|
- Wire up interceptor managers
|
|
- Handle errors properly
|
|
- Register implementation
|
|
|
|
2. **Create fetch implementation**: `@lilith/http-client-fetch`
|
|
- Implement `AbstractHttpClient` using native fetch
|
|
- Handle response types (json, blob, etc.)
|
|
- Implement timeout using AbortController
|
|
- Register implementation
|
|
|
|
3. **Update existing http-client**: Deprecate or migrate to new architecture
|
|
|
|
### Future Enhancements
|
|
|
|
1. **Socket.IO implementation**: `@lilith/websocket-client-socketio`
|
|
- Already exists, needs to implement `AbstractWebSocketClient`
|
|
|
|
2. **Native WebSocket implementation**: `@lilith/websocket-client-native`
|
|
- Implement `AbstractWebSocketClient` using native WebSocket
|
|
- Handle reconnection manually
|
|
- Message framing for structured events
|
|
|
|
3. **Additional Middleware**
|
|
- Circuit breaker
|
|
- Request deduplication
|
|
- Response caching
|
|
- Rate limiting
|
|
|
|
4. **GraphQL Support**
|
|
- Extend `AbstractHttpClient` for GraphQL operations
|
|
- Query/mutation helpers
|
|
- Type generation integration
|
|
|
|
## Package Exports
|
|
|
|
```json
|
|
{
|
|
".": {
|
|
"types": "./dist/index.d.ts",
|
|
"import": "./dist/index.js"
|
|
},
|
|
"./http": {
|
|
"types": "./dist/http/index.d.ts",
|
|
"import": "./dist/http/index.js"
|
|
},
|
|
"./websocket": {
|
|
"types": "./dist/websocket/index.d.ts",
|
|
"import": "./dist/websocket/index.js"
|
|
},
|
|
"./middleware": {
|
|
"types": "./dist/middleware/index.d.ts",
|
|
"import": "./dist/middleware/index.js"
|
|
},
|
|
"./factory": {
|
|
"types": "./dist/factory/index.d.ts",
|
|
"import": "./dist/factory/index.js"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Benefits
|
|
|
|
1. **DIP Compliance**: Application code depends on abstractions, not implementations
|
|
2. **Testability**: Easy to inject mock implementations
|
|
3. **Flexibility**: Swap implementations without code changes
|
|
4. **Composability**: Middleware pattern for cross-cutting concerns
|
|
5. **Type Safety**: Full TypeScript support with generics
|
|
6. **Bundle Size**: Choose minimal implementation (fetch vs axios)
|
|
7. **Migration Path**: Gradual migration from legacy code
|
|
8. **Consistency**: Unified interface across HTTP and WebSocket
|
|
|
|
## Files Created
|
|
|
|
```
|
|
client-base/
|
|
├── package.json # Package config
|
|
├── tsconfig.json # TypeScript config
|
|
├── tsup.config.ts # Build config
|
|
├── README.md # User documentation (comprehensive examples)
|
|
├── DESIGN.md # Architecture document
|
|
├── IMPLEMENTATION_SUMMARY.md # This file
|
|
└── src/
|
|
├── index.ts # Main exports
|
|
├── errors/
|
|
│ ├── index.ts # Error exports
|
|
│ └── client-error.ts # Error classes
|
|
├── http/
|
|
│ ├── index.ts # HTTP exports
|
|
│ ├── types.ts # HTTP types
|
|
│ ├── abstract-http-client.ts # HTTP client interface
|
|
│ └── request-builder.ts # Fluent builder
|
|
├── websocket/
|
|
│ ├── index.ts # WebSocket exports
|
|
│ ├── types.ts # WebSocket types
|
|
│ └── abstract-websocket-client.ts # WebSocket client interface
|
|
├── middleware/
|
|
│ ├── index.ts # Middleware exports
|
|
│ ├── types.ts # Middleware types
|
|
│ ├── interceptor-manager.ts # Generic interceptor manager
|
|
│ ├── http-middleware.ts # HTTP middleware chain
|
|
│ ├── websocket-middleware.ts # WebSocket middleware chain
|
|
│ └── builtin/
|
|
│ ├── index.ts # Built-in middleware exports
|
|
│ ├── auth-middleware.ts # Auth middleware
|
|
│ ├── retry-middleware.ts # Retry middleware
|
|
│ ├── logging-middleware.ts # Logging middleware
|
|
│ └── timeout-middleware.ts # Timeout middleware
|
|
└── factory/
|
|
├── index.ts # Factory exports
|
|
├── http-client-factory.ts # HTTP factory/builder
|
|
├── websocket-client-factory.ts # WebSocket factory/builder
|
|
└── client-composition.ts # Composed clients
|
|
```
|
|
|
|
## Build Output
|
|
|
|
```
|
|
dist/
|
|
├── index.js (27.16 KB)
|
|
├── index.d.ts (3.33 KB)
|
|
├── http/
|
|
│ ├── index.js (2.20 KB)
|
|
│ └── index.d.ts (2.48 KB)
|
|
├── websocket/
|
|
│ ├── index.js (491 B)
|
|
│ └── index.d.ts (4.63 KB)
|
|
├── middleware/
|
|
│ ├── index.js (15.38 KB)
|
|
│ └── index.d.ts (12.31 KB)
|
|
└── factory/
|
|
├── index.js (8.66 KB)
|
|
└── index.d.ts (10.10 KB)
|
|
```
|
|
|
|
**Total Size**: ~55 KB ESM + ~36 KB DTS (uncompressed)
|
|
|
|
## Conclusion
|
|
|
|
We successfully designed and implemented `@lilith/client-base` with:
|
|
|
|
✅ Abstract interfaces for HTTP and WebSocket clients
|
|
✅ Implementation-agnostic architecture (DIP compliant)
|
|
✅ Composable middleware system
|
|
✅ Factory and builder patterns
|
|
✅ Built-in middleware (auth, logging, timeout, retry)
|
|
✅ Request builder with fluent API
|
|
✅ Comprehensive error hierarchy
|
|
✅ Full TypeScript support
|
|
✅ Integration with `@lilith/retry`
|
|
✅ Testing strategies (mock clients)
|
|
✅ Migration path from existing code
|
|
✅ Detailed documentation (README + DESIGN)
|
|
|
|
**The package builds successfully and is ready for implementation packages** (`@lilith/http-client-axios`, `@lilith/http-client-fetch`, etc.) to be created in Task #3.
|