@ritas-inc/hanaqueryapi-client
v1.0.75
Published
TypeScript client for HANA Query API with full type safety and error handling
Maintainers
Readme
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.
// ❌ 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
npm install @ritas-inc/hanaqueryapi-clientAutomated Publishing: This package is automatically published to npm when changes are pushed to the master branch. See NPM Publishing Documentation for details.
Requirements
- Node.js 24.0.0 or higher (uses native TypeScript support)
Development
# 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:fixQuick Start
Basic Usage
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
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
// 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
// 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
// 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
// Get user by username
const user = await client.getUser('USER001');
console.log(`User ID: ${user.data.userId}`);Configuration
Basic Configuration
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
// 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:
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:
// 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
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
// 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:
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
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
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
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.
// ✅ 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 configuration2. Error Handling
// 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
// 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
// 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:
- Clone the repository
- Install dependencies:
npm install - Make your changes
- Run tests:
npm test - Build:
npm run build - Submit a pull request
