UNPKG

@ritas-inc/hanaqueryapi-client

Version:

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

611 lines (476 loc) 16.7 kB
# HANA Query API Client A fully-featured TypeScript client for the HANA Query API with comprehensive type safety, error handling, retry logic, and logging capabilities. ## ⚠️ Breaking Change in v1.0.0 **Important:** Starting with v1.0.0, the `baseUrl` configuration is now **required** when creating a client instance. The default client has been removed to ensure explicit configuration. ```typescript // ❌ This no longer works const client = new HanaQueryClient(); // ✅ This is now required const client = new HanaQueryClient({ baseUrl: 'http://localhost:3001' }); // ✅ Or use helper functions const client = createClient({ baseUrl: 'http://localhost:3001' }); const client = createClientFromEnvironment('development'); ``` ## Features - 🔷 **Full TypeScript Support** - Complete type definitions for all API responses - 🔄 **Automatic Retry Logic** - Configurable retry with exponential backoff - 📝 **Request/Response Logging** - Detailed logging with configurable levels - 🛡️ **Error Handling** - Custom error types with detailed context - ⏱️ **Configurable Timeouts** - Per-endpoint timeout configuration - 🌍 **Multiple Environments** - Built-in support for dev/test/staging/production - 🔗 **Fluent API** - Optional request builder for complex queries - ✅ **Type Safety** - Runtime type checking and validation - 📊 **Header Support** - Access to response timing and authorization headers ## Installation ```bash npm install @ritas-inc/hanaqueryapi-client ``` > **Automated Publishing**: This package is automatically published to npm when changes are pushed to the master branch. See [NPM Publishing Documentation](../../docs/npm-publishing.md) for details. ### Requirements - Node.js 24.0.0 or higher (uses native TypeScript support) ### Development ```bash # Run examples npm run example:basic npm run example:advanced npm run example:errors # Type checking npm run typecheck # Linting npm run lint npm run lint:fix ``` ## Quick Start ### Basic Usage ```typescript import { createClient } from '@ritas-inc/hanaqueryapi-client'; // Create client with required baseUrl const client = createClient({ baseUrl: 'http://localhost:3001' }); // Get API health status const health = await client.getHealth(); console.log('API Status:', health.data.status); // Get items with date filtering const items = await client.getItemsStatus({ from: '2025-01-01', to: '2025-12-31' }); console.log(`Found ${items.metadata.count} items`); // Get production plans const plans = await client.getPlans(); for (const plan of plans.data.plans) { console.log(`Plan ${plan.plan_id}: ${plan.plan_status}`); } ``` ### Environment-Specific Clients ```typescript import { clients, createClient } from '@ritas-inc/hanaqueryapi-client'; // Development client (localhost with debug logging) const devClient = clients.development(); // Production client (production URL with minimal logging) const prodClient = clients.production(); // Custom client with explicit baseUrl const customClient = createClient({ baseUrl: 'https://my-api.example.com' }, { timeout: 60000, enableLogging: true, logLevel: 'info' }); ``` ## API Reference ### Client Methods #### Health and Documentation ```typescript // Get API health status const health = await client.getHealth(); // Response: { success: true, data: { status: "ok", timestamp: "...", uptime: 123 } } // Get API documentation const docs = await client.getDocs(); // Response: { success: true, data: { name: "HANA Query API", version: "1.0.0", endpoints: {...} } } ``` #### Items ```typescript // Get all items status const allItems = await client.getItemsStatus(); // Get items with date range const filteredItems = await client.getItemsStatus({ from: '2025-01-01', to: '2025-06-30' }); // Get item hierarchies const hierarchies = await client.getItemHierarchies(); ``` #### Production Plans ```typescript // Get all production plans const plans = await client.getPlans(); // Get specific plan details const plan = await client.getPlan(1); // Get plan products const products = await client.getPlanProducts(1); // Get plan work orders (may return 404 if no work orders exist) try { const workOrders = await client.getPlanWorkOrders(1); console.log(`Plan has ${workOrders.metadata.count} work orders`); } catch (error) { if (error instanceof NotFoundError) { console.log('Plan has no work orders'); } } // Get sector summaries for a specific plan const planSectors = await client.getPlanSectorsSummary(1); console.log(`Plan has ${planSectors.metadata.sector_count} sectors`); console.log(`Completion rate: ${planSectors.metadata.completion_rate}%`); // Get sector summaries for all plans const allSectors = await client.getAllPlansSectorsSummary(); console.log(`${allSectors.metadata.total_sectors} sectors across ${allSectors.metadata.unique_plans} plans`); console.log(`Overall completion: ${allSectors.metadata.overall_completion_rate}%`); ``` #### Users ```typescript // Get user by username const user = await client.getUser('USER001'); console.log(`User ID: ${user.data.userId}`); ``` ### Configuration #### Basic Configuration ```typescript import { HanaQueryClient } from '@company/hana-query-client'; const client = new HanaQueryClient({ baseUrl: 'https://api.example.com', timeout: 30000, // 30 seconds retries: 3, // Retry up to 3 times retryDelay: 1000, // 1 second base delay enableLogging: true, // Enable request logging logLevel: 'info', // Log level: debug, info, warn, error headers: { // Additional headers 'Custom-Header': 'value' } }); ``` #### Environment Configurations ```typescript // Built-in environment configurations const configs = { development: { baseUrl: 'http://localhost:3001', enableLogging: true, logLevel: 'debug', timeout: 10000 }, production: { baseUrl: 'https://api.example.com', enableLogging: false, logLevel: 'error', timeout: 30000 } }; // Use environment configuration const client = new HanaQueryClient({}, 'development'); ``` ### Error Handling The client provides detailed error types for different scenarios: ```typescript import { NetworkError, TimeoutError, ValidationError, AuthorizationError, NotFoundError, ServerError } from '@company/hana-query-client'; try { const items = await client.getItemsStatus(); } catch (error) { if (error instanceof NetworkError) { console.error('Network connection failed:', error.message); } else if (error instanceof TimeoutError) { console.error('Request timed out after', error.context?.duration, 'ms'); } else if (error instanceof ValidationError) { console.error('Invalid parameters:', error.message); } else if (error instanceof NotFoundError) { console.error('Resource not found:', error.message); } else if (error instanceof ServerError) { console.error('Server error:', error.statusCode, error.message); } } ``` ### Fluent Request Builder API For advanced usage, you can use the fluent request builder: ```typescript // Complex request with custom options const items = await client .request('/items/status') .query({ from: '2025-01-01', to: '2025-06-30' }) .timeout(60000) .retries(5) .execute(); // With abort signal const controller = new AbortController(); const promise = client .request('/plans') .signal(controller.signal) .execute(); // Cancel the request after 5 seconds setTimeout(() => controller.abort(), 5000); ``` ### Utility Functions #### Convenience Functions ```typescript import { createClient, utils } from '@ritas-inc/hanaqueryapi-client'; // Create a client instance (required for utility functions) const client = createClient({ baseUrl: 'http://localhost:3001' }); // Test API connection const isOnline = await utils.testConnection(client); // Get all plan IDs const planIds = await utils.getPlanIds(client); // Get complete plan data (plan + products + work orders) const completePlan = await utils.getCompletePlanData(client, 1); console.log({ plan: completePlan.plan, products: completePlan.products, workOrders: completePlan.workOrders, errors: completePlan.errors // Any errors that occurred }); // Search items by keyword const searchResults = await utils.searchItems(client, 'pump'); // Get low stock items const lowStock = await utils.getLowStockItems(client); ``` #### Static Endpoint Functions ```typescript // Use endpoints with a client instance import { createClient, endpoints } from '@ritas-inc/hanaqueryapi-client'; const client = createClient({ baseUrl: 'http://localhost:3001' }); const health = await endpoints.health(client); const plans = await endpoints.plans(client); const items = await endpoints.itemsStatus(client, { from: '2025-01-01' }); ``` ## TypeScript Integration ### Type-Safe Responses All API responses are fully typed: ```typescript import type { ItemsStatusResponse, PlansResponse, Plan, ItemStatus } from '@ritas-inc/hanaqueryapi-client'; const items: ItemsStatusResponse = await client.getItemsStatus(); // TypeScript knows the exact structure items.data.items.forEach((item: ItemStatus) => { console.log(`${item.itemcode}: ${item.onhand} in stock`); }); const plans: PlansResponse = await client.getPlans(); plans.data.plans.forEach((plan: Plan) => { console.log(`Plan ${plan.plan_id} created by ${plan.plan_username}`); }); ``` ### Type Guards ```typescript import { isSuccessResponse, isErrorResponse } from '@ritas-inc/hanaqueryapi-client'; const response = await client.getPlans(); if (isSuccessResponse(response)) { // TypeScript knows this is a success response console.log(response.data.plans); } else if (isErrorResponse(response)) { // TypeScript knows this is an error response console.error(response.problem.detail); } ``` ## Advanced Examples ### Complete Production Planning Workflow ```typescript import { clients, utils, isNotFoundError } from '@ritas-inc/hanaqueryapi-client'; async function analyzeProductionPlanning() { const client = clients.production(); try { // 1. Check API health const health = await client.getHealth(); console.log(`API is ${health.data.status}, uptime: ${health.data.uptime}s`); // 2. Get all production plans const plans = await client.getPlans(); console.log(`Found ${plans.metadata.count} production plans`); // 3. Analyze each plan for (const plan of plans.data.plans) { console.log(`\\nAnalyzing Plan ${plan.plan_id}:`); console.log(` Status: ${plan.plan_status}`); console.log(` Created by: ${plan.plan_username}`); console.log(` Products: ${plan.products_total} total, ${plan.products_released} released`); // 4. Get plan details try { const products = await client.getPlanProducts(plan.plan_id); console.log(` Product details: ${products.metadata.count} items`); const workOrders = await client.getPlanWorkOrders(plan.plan_id); console.log(` Work orders: ${workOrders.metadata.count} orders`); // Calculate completion rate const totalPlanned = workOrders.data.workOrders.reduce( (sum, order) => sum + order.order_plannedqty, 0 ); const totalCompleted = workOrders.data.workOrders.reduce( (sum, order) => sum + order.order_completedqty, 0 ); const completionRate = totalPlanned > 0 ? (totalCompleted / totalPlanned * 100) : 0; console.log(` Completion rate: ${completionRate.toFixed(1)}%`); } catch (error) { if (isNotFoundError(error)) { console.log(` No additional data available for plan ${plan.plan_id}`); } else { console.error(` Error fetching plan details:`, error.message); } } } // 5. Get low stock items that need attention const lowStockItems = await utils.getLowStockItems(client); if (lowStockItems.length > 0) { console.log(`\\n⚠️ Low stock items requiring attention:`); lowStockItems.forEach(item => { console.log(` ${item.itemcode}: ${item.onhand}/${item.min} (${item.description})`); }); } } catch (error) { console.error('Production planning analysis failed:', error); } } analyzeProductionPlanning(); ``` ### Custom Error Handling Strategy ```typescript import { HanaQueryClient, HanaQueryClientError, isRetryableError, getRetryDelay } from '@company/hana-query-client'; class RobustAPIClient { private client: HanaQueryClient; constructor() { this.client = new HanaQueryClient({ enableLogging: true, logLevel: 'info' }); } async safeRequest<T>(operation: () => Promise<T>, maxRetries = 3): Promise<T | null> { let lastError: HanaQueryClientError | null = null; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { if (error instanceof HanaQueryClientError) { lastError = error; console.warn(`Attempt ${attempt} failed:`, { type: error.type, message: error.message, statusCode: error.statusCode }); // Don't retry non-retryable errors if (!isRetryableError(error)) { break; } // Wait before retrying if (attempt < maxRetries) { const delay = getRetryDelay(attempt, 1000, 10000); console.log(`Waiting ${delay}ms before retry...`); await new Promise(resolve => setTimeout(resolve, delay)); } } else { // Unexpected error console.error('Unexpected error:', error); break; } } } console.error('All retry attempts failed:', lastError?.message); return null; } async getPlansWithFallback() { const plans = await this.safeRequest(() => this.client.getPlans()); return plans?.data.plans || []; } } ``` ## Best Practices ### 1. Environment Configuration **Important:** This client library does NOT access environment variables directly. Environment handling should be done by the consuming application. ```typescript // ✅ Good: Let your application handle environment detection const getClientConfig = () => { const isDevelopment = process.env.NODE_ENV === 'development'; const apiBaseUrl = process.env.API_BASE_URL || 'http://localhost:3001'; return { baseUrl: apiBaseUrl, enableLogging: isDevelopment, logLevel: isDevelopment ? 'debug' as const : 'error' as const, timeout: isDevelopment ? 10000 : 30000 }; }; const client = createClient(getClientConfig()); // ✅ Or use environment-specific presets const client = process.env.NODE_ENV === 'production' ? clients.production() : clients.development(); // ❌ Bad: The client doesn't read environment variables itself // This ensures your library consumers have full control over configuration ``` ### 2. Error Handling ```typescript // Always handle specific error types try { const data = await client.getItemsStatus(); } catch (error) { if (isNotFoundError(error)) { // Handle gracefully - this might be expected return []; } else if (isNetworkError(error) || isTimeoutError(error)) { // Retry or show user-friendly message throw new Error('Unable to connect to server. Please try again.'); } else { // Log unexpected errors console.error('Unexpected API error:', error); throw error; } } ``` ### 3. Performance Optimization ```typescript // Use appropriate timeouts for different endpoints const client = new HanaQueryClient({ timeout: 30000 // Default timeout }); // Override for slow endpoints const hierarchies = await client.getItemHierarchies({ timeout: 90000 // Longer timeout for large dataset }); // Use parallel requests when possible const [plans, items] = await Promise.all([ client.getPlans(), client.getItemsStatus() ]); ``` ### 4. Request Cancellation ```typescript // Cancel long-running requests const controller = new AbortController(); const promise = client.getItemHierarchies({ signal: controller.signal }); // Cancel after 30 seconds setTimeout(() => controller.abort(), 30000); try { const result = await promise; } catch (error) { if (error.name === 'AbortError') { console.log('Request was cancelled'); } } ``` ## Contributing To contribute to this client: 1. Clone the repository 2. Install dependencies: `npm install` 3. Make your changes 4. Run tests: `npm test` 5. Build: `npm run build` 6. Submit a pull request