# @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(config: HttpRequestConfig): Promise>; // Convenience methods get(url: string, config?: Partial): Promise>; post(url: string, data?: unknown, config?: Partial): Promise>; put(url: string, data?: unknown, config?: Partial): Promise>; patch(url: string, data?: unknown, config?: Partial): Promise>; delete(url: string, config?: Partial): Promise>; head(url: string, config?: Partial): Promise>; // Interceptors readonly interceptors: { request: InterceptorManager; response: InterceptorManager; }; // Configuration getBaseURL(): string | undefined; setBaseURL(baseURL: string): void; getDefaultHeaders(): Record; setDefaultHeaders(headers: Record): void; getTimeout(): number | undefined; setTimeout(timeout: number): void; } ``` ### AbstractWebSocketClient ```typescript interface AbstractWebSocketClient { // Lifecycle connect(): void; disconnect(): void; reconnect(): void; // Messaging send(message: WebSocketMessage): void; sendWithAck(message: WebSocketMessage, timeout?: number): Promise; // Events subscribe(event: string, listener: EventListener): UnsubscribeFunction; subscribeWithAck(event: string, listener: EventListenerWithAck): UnsubscribeFunction; once(event: string, listener: EventListener): UnsubscribeFunction; unsubscribe(event: string, listener?: EventListener): void; unsubscribeAll(): void; // State getStatus(): WebSocketStatus; isConnected(): boolean; getRawSocket(): unknown; waitForConnection(timeout?: number): Promise; } ``` ## 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('/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('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 { const response = await this.http.get('/users'); return response.data; } async createUser(user: CreateUserDto): Promise { const response = await this.http.post('/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(); interceptors = { request: createInterceptorManager(), response: createInterceptorManager(), }; mockResponse(url: string, data: T, status = 200): void { this.responses.set(url, { data, status }); } async execute(config: HttpRequestConfig): Promise> { 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.