npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ritas-inc/hanaqueryapi-client

v1.0.75

Published

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

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-client

Automated 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:fix

Quick 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 configuration

2. 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:

  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