client-base/IMPLEMENTATION_SUMMARY.md

534 lines
16 KiB
Markdown
Raw Normal View History

# @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.