UNPKG

@ritas-inc/hanaqueryapi-client

Version:

TypeScript client for HANA Query API with full type safety and error handling

313 lines (276 loc) 7.68 kB
/** * HANA Query API Client - Configuration * * Default configuration and environment-specific settings for the API client. */ import type { ClientConfig } from './types.ts'; /** * Default client configuration (excluding baseUrl which is required) */ export const DEFAULT_CONFIG = { timeout: 30000, // 30 seconds retries: 3, retryDelay: 1000, // 1 second base delay enableLogging: false, logLevel: 'info' as const, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }; /** * Environment-specific configurations */ export const ENVIRONMENT_CONFIGS: Record<string, ClientConfig> = { development: { baseUrl: 'http://localhost:3001', enableLogging: true, logLevel: 'debug', timeout: 10000 }, testing: { baseUrl: 'http://localhost:3001', enableLogging: true, logLevel: 'warn', timeout: 5000, retries: 1 }, staging: { baseUrl: 'https://api-staging.example.com', enableLogging: true, logLevel: 'info', timeout: 20000 }, production: { baseUrl: 'https://api.example.com', enableLogging: false, logLevel: 'error', timeout: 30000 } }; /** * API version and paths */ export const API_VERSION = 'v1'; export const API_BASE_PATH = `/api/${API_VERSION}`; /** * Endpoint paths */ export const ENDPOINTS = { HEALTH: '/health', DOCS: '/docs', ITEMS: '/items', ITEMS_GROUPS: '/items/groups', ITEMS_HIERARCHIES: '/items/hierarchies', ITEMS_TREES: '/items/trees', SALES: '/sales', PRODUCTION_SECTORS: '/production-sectors', PLANS: '/plans', ALL_PLANS_SECTORS: '/plans/sectors', PLAN_DETAIL: '/plans/{planId}', PLAN_PRODUCTS: '/plans/{planId}/products', PLAN_WORK_ORDERS: '/plans/{planId}/work-orders', PLAN_SECTORS: '/plans/{planId}/sectors', USER: '/users/{username}' } as const; /** * HTTP status codes */ export const HTTP_STATUS = { OK: 200, BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, NOT_FOUND: 404, INTERNAL_SERVER_ERROR: 500, BAD_GATEWAY: 502, SERVICE_UNAVAILABLE: 503, GATEWAY_TIMEOUT: 504 } as const; /** * Request timeout configuration for different endpoint types */ export const ENDPOINT_TIMEOUTS = { // Fast endpoints [ENDPOINTS.HEALTH]: 5000, [ENDPOINTS.DOCS]: 5000, [ENDPOINTS.PLANS]: 15000, [ENDPOINTS.ALL_PLANS_SECTORS]: 30000, [ENDPOINTS.PLAN_DETAIL]: 10000, [ENDPOINTS.PLAN_PRODUCTS]: 10000, [ENDPOINTS.PLAN_WORK_ORDERS]: 10000, [ENDPOINTS.PLAN_SECTORS]: 15000, [ENDPOINTS.USER]: 10000, // Slower endpoints with large datasets [ENDPOINTS.ITEMS]: 60000, // Items can be large [ENDPOINTS.SALES]: 60000, // Sales can be large with date ranges [ENDPOINTS.ITEMS_HIERARCHIES]: 90000, // Hierarchies are the largest [ENDPOINTS.ITEMS_TREES]: 90000 // Trees can also be large } as const; /** * Retry configuration for different error types */ export const RETRY_CONFIG = { // Network errors - always retry network: { enabled: true, maxAttempts: 3, baseDelay: 1000, maxDelay: 10000, backoffMultiplier: 2 }, // Timeout errors - retry with increased timeout timeout: { enabled: true, maxAttempts: 2, baseDelay: 2000, maxDelay: 15000, backoffMultiplier: 2 }, // Server errors (5xx) - limited retry server: { enabled: true, maxAttempts: 2, baseDelay: 3000, maxDelay: 20000, backoffMultiplier: 2 }, // Client errors (4xx) - generally don't retry client: { enabled: false, maxAttempts: 1, baseDelay: 0, maxDelay: 0, backoffMultiplier: 1 } } as const; /** * Header names used by the API */ export const HEADERS = { RESPONSE_TIME: 'x-response-time', AUTHORIZATION_RESPONSE: 'x-authorization-response', CONTENT_TYPE: 'content-type' } as const; /** * Authorization response values */ export const AUTH_STATUS = { OK: 'ok', UNAUTHORIZED: 'unauthorized' } as const; /** * Create a configuration by merging defaults with environment-specific and custom options */ export function createConfig( baseConfig: ClientConfig, customConfig?: Partial<Omit<ClientConfig, 'baseUrl'>> ): Required<ClientConfig> { return { ...DEFAULT_CONFIG, ...baseConfig, ...customConfig, headers: { ...DEFAULT_CONFIG.headers, ...baseConfig.headers, ...customConfig?.headers } }; } /** * Create a configuration from environment and custom options */ export function createConfigFromEnvironment( environment: string, customConfig?: Partial<ClientConfig> ): Required<ClientConfig> { const envConfig = ENVIRONMENT_CONFIGS[environment]; if (!envConfig) { throw new Error(`Unknown environment: ${environment}. Available environments: ${Object.keys(ENVIRONMENT_CONFIGS).join(', ')}`); } return { ...DEFAULT_CONFIG, ...envConfig, ...customConfig, headers: { ...DEFAULT_CONFIG.headers, ...envConfig.headers, ...customConfig?.headers } }; } /** * Get timeout for a specific endpoint */ export function getEndpointTimeout(endpoint: string, defaultTimeout: number): number { // Remove path parameters for matching const normalizedEndpoint = endpoint.replace(/\/\d+/g, '/{id}').replace(/\/[^/]+$/g, '/{param}'); return ENDPOINT_TIMEOUTS[normalizedEndpoint as keyof typeof ENDPOINT_TIMEOUTS] || defaultTimeout; } /** * Validate configuration */ export function validateConfig(config: ClientConfig): string[] { const errors: string[] = []; if (config.baseUrl && !isValidUrl(config.baseUrl)) { errors.push('baseUrl must be a valid URL'); } if (config.timeout && (config.timeout <= 0 || config.timeout > 300000)) { errors.push('timeout must be between 1ms and 300s (5 minutes)'); } if (config.retries && (config.retries < 0 || config.retries > 10)) { errors.push('retries must be between 0 and 10'); } if (config.retryDelay && (config.retryDelay < 0 || config.retryDelay > 60000)) { errors.push('retryDelay must be between 0ms and 60s'); } if (config.logLevel && !['debug', 'info', 'warn', 'error'].includes(config.logLevel)) { errors.push('logLevel must be one of: debug, info, warn, error'); } return errors; } /** * Check if a string is a valid URL */ function isValidUrl(urlString: string): boolean { try { const url = new URL(urlString); return url.protocol === 'http:' || url.protocol === 'https:'; } catch { return false; } } /** * Get the full API URL for an endpoint */ export function getApiUrl(baseUrl: string, endpoint: string): string { const cleanBaseUrl = baseUrl.replace(/\/$/, ''); const cleanEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; return `${cleanBaseUrl}${API_BASE_PATH}${cleanEndpoint}`; } /** * Replace path parameters in endpoint URLs */ export function replacePathParams(endpoint: string, params: Record<string, string | number>): string { let result = endpoint; for (const [key, value] of Object.entries(params)) { result = result.replace(`{${key}}`, String(value)); } return result; } /** * Build query string from parameters */ export function buildQueryString(params: Record<string, string | number | boolean | undefined>): string { const filtered = Object.entries(params) .filter(([, value]) => value !== undefined && value !== null) .map(([key, value]) => [key, String(value)]); if (filtered.length === 0) { return ''; } const searchParams = new URLSearchParams(); filtered.forEach(([key, value]) => { searchParams.append(key, value); }); return `?${searchParams.toString()}`; }