Advanced Usage
This guide covers advanced patterns, customization options, and best practices for using the My Dashboard TypeScript SDK.
Custom HTTP Clientโ
You can extend the BaseClient
to create custom HTTP clients with additional functionality.
import { BaseClient, SDKConfig, RequestOptions } from '@my-dashboard/sdk';
class CustomClient extends BaseClient {
private requestCount = 0;
constructor(config: SDKConfig) {
super(config);
}
// Override request method to add custom logic
protected async request<T>(url: string, options: RequestOptions = {}): Promise<T> {
this.requestCount++;
console.log(`Request #${this.requestCount}: ${options.method || 'GET'} ${url}`);
// Add custom headers
const customOptions = {
...options,
headers: {
'X-Request-ID': `req-${this.requestCount}`,
'X-Client-Version': '1.0.0',
...options.headers
}
};
// Call parent request method
const startTime = Date.now();
try {
const result = await super.request<T>(url, customOptions);
const duration = Date.now() - startTime;
console.log(`Request completed in ${duration}ms`);
return result;
} catch (error) {
const duration = Date.now() - startTime;
console.error(`Request failed after ${duration}ms:`, error);
throw error;
}
}
// Add custom methods
public getRequestCount(): number {
return this.requestCount;
}
public resetRequestCount(): void {
this.requestCount = 0;
}
}
// Usage
const customClient = new CustomClient({
baseUrl: process.env.MY_DASHBOARD_BASE_URL!,
apiKey: process.env.MY_DASHBOARD_API_KEY!
});
Request Interceptorsโ
Implement request and response interceptors for logging, authentication, or data transformation.
import { MyDashboardAPI, RequestOptions } from '@my-dashboard/sdk';
class InterceptedAPI extends MyDashboardAPI {
private requestInterceptors: Array<(url: string, options: RequestOptions) => RequestOptions> = [];
private responseInterceptors: Array<(response: any) => any> = [];
// Add request interceptor
public addRequestInterceptor(interceptor: (url: string, options: RequestOptions) => RequestOptions) {
this.requestInterceptors.push(interceptor);
}
// Add response interceptor
public addResponseInterceptor(interceptor: (response: any) => any) {
this.responseInterceptors.push(interceptor);
}
// Override request method
protected async request<T>(url: string, options: RequestOptions = {}): Promise<T> {
// Apply request interceptors
let modifiedOptions = options;
for (const interceptor of this.requestInterceptors) {
modifiedOptions = interceptor(url, modifiedOptions);
}
// Make request
let response = await super.request<T>(url, modifiedOptions);
// Apply response interceptors
for (const interceptor of this.responseInterceptors) {
response = interceptor(response);
}
return response;
}
}
// Usage
const api = new InterceptedAPI({
baseUrl: process.env.MY_DASHBOARD_BASE_URL!,
apiKey: process.env.MY_DASHBOARD_API_KEY!
});
// Add logging interceptor
api.addRequestInterceptor((url, options) => {
console.log(`๐ ${options.method || 'GET'} ${url}`);
return options;
});
// Add response transformation interceptor
api.addResponseInterceptor((response) => {
if (Array.isArray(response)) {
console.log(`๐ฆ Received ${response.length} items`);
}
return response;
});
Connection Pooling and Cachingโ
Implement caching and connection pooling for better performance.
import { MyDashboardAPI } from '@my-dashboard/sdk';
interface CacheEntry<T> {
data: T;
timestamp: number;
ttl: number;
}
class CachedAPI extends MyDashboardAPI {
private cache = new Map<string, CacheEntry<any>>();
private defaultTTL = 5 * 60 * 1000; // 5 minutes
// Override request method to add caching
protected async request<T>(url: string, options: RequestOptions = {}): Promise<T> {
const method = options.method || 'GET';
// Only cache GET requests
if (method === 'GET') {
const cacheKey = this.getCacheKey(url, options);
const cached = this.getFromCache<T>(cacheKey);
if (cached) {
console.log(`๐ Cache hit for ${url}`);
return cached;
}
}
// Make request
const response = await super.request<T>(url, options);
// Cache GET responses
if (method === 'GET') {
const cacheKey = this.getCacheKey(url, options);
this.setCache(cacheKey, response, this.defaultTTL);
console.log(`๐พ Cached response for ${url}`);
}
return response;
}
private getCacheKey(url: string, options: RequestOptions): string {
const params = options.params ? JSON.stringify(options.params) : '';
return `${url}:${params}`;
}
private getFromCache<T>(key: string): T | null {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() > entry.timestamp + entry.ttl) {
this.cache.delete(key);
return null;
}
return entry.data;
}
private setCache<T>(key: string, data: T, ttl: number): void {
this.cache.set(key, {
data,
timestamp: Date.now(),
ttl
});
}
// Public cache management methods
public clearCache(): void {
this.cache.clear();
console.log('๐๏ธ Cache cleared');
}
public setCacheTTL(ttl: number): void {
this.defaultTTL = ttl;
}
public getCacheStats(): { size: number; keys: string[] } {
return {
size: this.cache.size,
keys: Array.from(this.cache.keys())
};
}
}
// Usage
const cachedAPI = new CachedAPI({
baseUrl: process.env.MY_DASHBOARD_BASE_URL!,
apiKey: process.env.MY_DASHBOARD_API_KEY!
});
// Set cache TTL to 10 minutes
cachedAPI.setCacheTTL(10 * 60 * 1000);
// First call - hits API
const reports1 = await cachedAPI.getE2EReports();
// Second call - hits cache
const reports2 = await cachedAPI.getE2EReports();
// Check cache stats
console.log('Cache stats:', cachedAPI.getCacheStats());
Rate Limiting and Throttlingโ
Implement client-side rate limiting to avoid hitting API limits.
import { MyDashboardAPI, RequestOptions } from '@my-dashboard/sdk';
class ThrottledAPI extends MyDashboardAPI {
private requestQueue: Array<() => Promise<any>> = [];
private isProcessing = false;
private requestsPerSecond: number;
private lastRequestTime = 0;
constructor(config: any, requestsPerSecond = 10) {
super(config);
this.requestsPerSecond = requestsPerSecond;
}
protected async request<T>(url: string, options: RequestOptions = {}): Promise<T> {
return new Promise((resolve, reject) => {
this.requestQueue.push(async () => {
try {
const result = await super.request<T>(url, options);
resolve(result);
} catch (error) {
reject(error);
}
});
this.processQueue();
});
}
private async processQueue(): Promise<void> {
if (this.isProcessing || this.requestQueue.length === 0) {
return;
}
this.isProcessing = true;
while (this.requestQueue.length > 0) {
const now = Date.now();
const timeSinceLastRequest = now - this.lastRequestTime;
const minInterval = 1000 / this.requestsPerSecond;
if (timeSinceLastRequest < minInterval) {
const delay = minInterval - timeSinceLastRequest;
await new Promise(resolve => setTimeout(resolve, delay));
}
const request = this.requestQueue.shift();
if (request) {
this.lastRequestTime = Date.now();
await request();
}
}
this.isProcessing = false;
}
public getQueueLength(): number {
return this.requestQueue.length;
}
public setRateLimit(requestsPerSecond: number): void {
this.requestsPerSecond = requestsPerSecond;
}
}
// Usage
const throttledAPI = new ThrottledAPI(
{
baseUrl: process.env.MY_DASHBOARD_BASE_URL!,
apiKey: process.env.MY_DASHBOARD_API_KEY!
},
5 // 5 requests per second
);
// These requests will be automatically throttled
const promises = [
throttledAPI.getE2EReports(),
throttledAPI.getApplications(),
throttledAPI.getNotifications(),
throttledAPI.getPullRequests()
];
const results = await Promise.all(promises);
Custom Error Handlingโ
Implement custom error handling strategies.
import { MyDashboardAPI, APIError, NetworkError } from '@my-dashboard/sdk';
class ResilientAPI extends MyDashboardAPI {
private circuitBreakerState: 'closed' | 'open' | 'half-open' = 'closed';
private failureCount = 0;
private lastFailureTime = 0;
private failureThreshold = 5;
private recoveryTimeout = 60000; // 1 minute
protected async request<T>(url: string, options: RequestOptions = {}): Promise<T> {
// Check circuit breaker
if (this.circuitBreakerState === 'open') {
if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
this.circuitBreakerState = 'half-open';
console.log('๐ Circuit breaker: half-open');
} else {
throw new Error('Circuit breaker is open - service unavailable');
}
}
try {
const result = await super.request<T>(url, options);
// Reset circuit breaker on success
if (this.circuitBreakerState === 'half-open') {
this.circuitBreakerState = 'closed';
this.failureCount = 0;
console.log('โ
Circuit breaker: closed');
}
return result;
} catch (error) {
this.handleFailure(error);
throw error;
}
}
private handleFailure(error: any): void {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.failureThreshold) {
this.circuitBreakerState = 'open';
console.log('๐จ Circuit breaker: open');
}
// Log different error types
if (error instanceof APIError) {
console.error(`API Error (${error.status}): ${error.message}`);
if (error.status >= 500) {
console.error('Server error detected - may affect circuit breaker');
}
} else if (error instanceof NetworkError) {
console.error('Network error detected - may affect circuit breaker');
}
}
public getCircuitBreakerState(): string {
return this.circuitBreakerState;
}
public resetCircuitBreaker(): void {
this.circuitBreakerState = 'closed';
this.failureCount = 0;
this.lastFailureTime = 0;
console.log('๐ Circuit breaker manually reset');
}
}
// Usage
const resilientAPI = new ResilientAPI({
baseUrl: process.env.MY_DASHBOARD_BASE_URL!,
apiKey: process.env.MY_DASHBOARD_API_KEY!
});
// Monitor circuit breaker state
setInterval(() => {
console.log('Circuit breaker state:', resilientAPI.getCircuitBreakerState());
}, 10000);
Batch Operationsโ
Implement efficient batch operations with concurrency control.
import { MyDashboardAPI } from '@my-dashboard/sdk';
class BatchAPI extends MyDashboardAPI {
async batchOperation<T, R>(
items: T[],
operation: (item: T) => Promise<R>,
options: {
concurrency?: number;
batchSize?: number;
delayBetweenBatches?: number;
onProgress?: (completed: number, total: number) => void;
} = {}
): Promise<Array<{ item: T; result?: R; error?: Error }>> {
const {
concurrency = 3,
batchSize = 10,
delayBetweenBatches = 1000,
onProgress
} = options;
const results: Array<{ item: T; result?: R; error?: Error }> = [];
// Split items into batches
const batches: T[][] = [];
for (let i = 0; i < items.length; i += batchSize) {
batches.push(items.slice(i, i + batchSize));
}
console.log(`๐ Processing ${items.length} items in ${batches.length} batches`);
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
const batch = batches[batchIndex];
// Process batch with concurrency limit
const batchPromises = this.limitConcurrency(
batch.map(item => () => this.executeWithErrorHandling(item, operation)),
concurrency
);
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
// Report progress
if (onProgress) {
onProgress(results.length, items.length);
}
// Delay between batches (except for the last batch)
if (batchIndex < batches.length - 1 && delayBetweenBatches > 0) {
await new Promise(resolve => setTimeout(resolve, delayBetweenBatches));
}
}
const successful = results.filter(r => !r.error).length;
const failed = results.filter(r => r.error).length;
console.log(`โ
Batch operation complete: ${successful} successful, ${failed} failed`);
return results;
}
private async limitConcurrency<T>(
tasks: Array<() => Promise<T>>,
limit: number
): Promise<T[]> {
const results: T[] = [];
const executing: Promise<void>[] = [];
for (const task of tasks) {
const promise = task().then(result => {
results.push(result);
});
executing.push(promise);
if (executing.length >= limit) {
await Promise.race(executing);
executing.splice(executing.findIndex(p => p === promise), 1);
}
}
await Promise.all(executing);
return results;
}
private async executeWithErrorHandling<T, R>(
item: T,
operation: (item: T) => Promise<R>
): Promise<{ item: T; result?: R; error?: Error }> {
try {
const result = await operation(item);
return { item, result };
} catch (error) {
return { item, error: error as Error };
}
}
}
// Usage
const batchAPI = new BatchAPI({
baseUrl: process.env.MY_DASHBOARD_BASE_URL!,
apiKey: process.env.MY_DASHBOARD_API_KEY!
});
// Batch update applications
const applicationUpdates = [
{ id: 1, changes: { description: 'Updated 1' } },
{ id: 2, changes: { description: 'Updated 2' } },
{ id: 3, changes: { description: 'Updated 3' } }
];
const results = await batchAPI.batchOperation(
applicationUpdates,
async (update) => {
return await batchAPI.updateApplication(update.id, update.changes);
},
{
concurrency: 2,
batchSize: 5,
delayBetweenBatches: 2000,
onProgress: (completed, total) => {
console.log(`Progress: ${completed}/${total} (${Math.round(completed/total*100)}%)`);
}
}
);
// Process results
const successful = results.filter(r => !r.error);
const failed = results.filter(r => r.error);
console.log(`Successful updates: ${successful.length}`);
console.log(`Failed updates: ${failed.length}`);
if (failed.length > 0) {
console.log('Failed items:', failed.map(f => ({ item: f.item, error: f.error?.message })));
}
Plugin Systemโ
Create a plugin system for extending SDK functionality.
interface Plugin {
name: string;
beforeRequest?(url: string, options: RequestOptions): RequestOptions;
afterRequest?(response: any, url: string, options: RequestOptions): any;
onError?(error: Error, url: string, options: RequestOptions): void;
}
class PluggableAPI extends MyDashboardAPI {
private plugins: Plugin[] = [];
public addPlugin(plugin: Plugin): void {
this.plugins.push(plugin);
console.log(`๐ Plugin "${plugin.name}" added`);
}
public removePlugin(name: string): void {
const index = this.plugins.findIndex(p => p.name === name);
if (index !== -1) {
this.plugins.splice(index, 1);
console.log(`๐ Plugin "${name}" removed`);
}
}
protected async request<T>(url: string, options: RequestOptions = {}): Promise<T> {
let modifiedOptions = options;
// Apply beforeRequest hooks
for (const plugin of this.plugins) {
if (plugin.beforeRequest) {
modifiedOptions = plugin.beforeRequest(url, modifiedOptions);
}
}
try {
let response = await super.request<T>(url, modifiedOptions);
// Apply afterRequest hooks
for (const plugin of this.plugins) {
if (plugin.afterRequest) {
response = plugin.afterRequest(response, url, modifiedOptions);
}
}
return response;
} catch (error) {
// Apply onError hooks
for (const plugin of this.plugins) {
if (plugin.onError) {
plugin.onError(error as Error, url, modifiedOptions);
}
}
throw error;
}
}
}
// Example plugins
const loggingPlugin: Plugin = {
name: 'logging',
beforeRequest: (url, options) => {
console.log(`๐ค ${options.method || 'GET'} ${url}`);
return options;
},
afterRequest: (response, url, options) => {
console.log(`๐ฅ ${options.method || 'GET'} ${url} - Success`);
return response;
},
onError: (error, url, options) => {
console.error(`โ ${options.method || 'GET'} ${url} - Error: ${error.message}`);
}
};
const metricsPlugin: Plugin = {
name: 'metrics',
beforeRequest: (url, options) => {
(options as any).startTime = Date.now();
return options;
},
afterRequest: (response, url, options) => {
const duration = Date.now() - (options as any).startTime;
console.log(`โฑ๏ธ Request took ${duration}ms`);
return response;
}
};
// Usage
const pluggableAPI = new PluggableAPI({
baseUrl: process.env.MY_DASHBOARD_BASE_URL!,
apiKey: process.env.MY_DASHBOARD_API_KEY!
});
pluggableAPI.addPlugin(loggingPlugin);
pluggableAPI.addPlugin(metricsPlugin);
// Requests will now use both plugins
const reports = await pluggableAPI.getE2EReports();
Best Practicesโ
1. Configuration Managementโ
// Use environment-specific configurations
const configs = {
development: {
baseUrl: 'http://localhost:3000',
timeout: 10000,
retries: 1
},
staging: {
baseUrl: 'https://staging-api.example.com',
timeout: 30000,
retries: 3
},
production: {
baseUrl: 'https://api.example.com',
timeout: 30000,
retries: 5
}
};
const env = process.env.NODE_ENV || 'development';
const config = configs[env];
const api = new MyDashboardAPI({
...config,
apiKey: process.env.MY_DASHBOARD_API_KEY!
});
2. Error Recovery Strategiesโ
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (attempt === maxRetries) break;
// Exponential backoff
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError!;
}
// Usage
const reports = await withRetry(() => api.getE2EReports());
3. Resource Cleanupโ
class ManagedAPI extends MyDashboardAPI {
private timers: NodeJS.Timeout[] = [];
private abortControllers: AbortController[] = [];
public startPeriodicSync(interval: number): void {
const timer = setInterval(async () => {
try {
await this.syncData();
} catch (error) {
console.error('Periodic sync failed:', error);
}
}, interval);
this.timers.push(timer);
}
public async syncData(): Promise<void> {
const controller = new AbortController();
this.abortControllers.push(controller);
try {
// Perform sync operations
await Promise.all([
this.getE2EReports(),
this.getApplications(),
this.getNotifications()
]);
} finally {
const index = this.abortControllers.indexOf(controller);
if (index !== -1) {
this.abortControllers.splice(index, 1);
}
}
}
public cleanup(): void {
// Clear all timers
this.timers.forEach(timer => clearInterval(timer));
this.timers = [];
// Abort all pending requests
this.abortControllers.forEach(controller => controller.abort());
this.abortControllers = [];
console.log('๐งน API resources cleaned up');
}
}
// Usage
const managedAPI = new ManagedAPI(config);
managedAPI.startPeriodicSync(60000); // Every minute
// Cleanup on process exit
process.on('SIGINT', () => {
managedAPI.cleanup();
process.exit(0);
});
Next Stepsโ
- Examples - Real-world usage examples
- Service Reference - Detailed API documentation
- API Reference - Complete REST API reference