@egintegrations/core-utils
v0.1.0
Published
Foundational TypeScript utilities for retry logic, error handling, health checks, and idempotency. Designed for use across API, BOT, and service applications with zero external dependencies.
Downloads
10
Maintainers
Readme
@egintegrations/core-utils
Foundational TypeScript utilities for retry logic, error handling, health checks, and idempotency. Designed for use across API, BOT, and service applications with zero external dependencies.
Features
- Retry Logic: Exponential backoff retry with configurable parameters
- Error Handling: Comprehensive error classes for common scenarios
- Health Checks: Framework-agnostic health and readiness checks
- Idempotency: Pluggable storage interface for idempotent operations
- Zero Dependencies: No external runtime dependencies
- TypeScript: Full TypeScript support with type definitions
- Dual Module: ESM and CommonJS support
Installation
npm install @egintegrations/core-utilsUsage
Retry Logic
The retry utility provides exponential backoff retry logic with configurable parameters.
import { retryWithBackoff, RetryableError } from '@egintegrations/core-utils';
// Basic usage with default configuration
const result = await retryWithBackoff(async () => {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('Request failed');
return response.json();
});
// Custom configuration
const result = await retryWithBackoff(
async () => {
return await performOperation();
},
{
maxAttempts: 5,
initialDelay: 2000, // 2 seconds
maxDelay: 60000, // 60 seconds max
backoffMultiplier: 2,
onRetry: (attempt, error, delay) => {
console.log(`Retry attempt ${attempt}, waiting ${delay}ms`);
},
}
);
// Only retry specific errors
const result = await retryWithBackoff(
async () => {
return await performOperation();
},
{
retryableErrors: [RetryableError, NetworkError],
}
);Configuration Options:
maxAttempts(default: 3): Maximum number of retry attemptsinitialDelay(default: 1000ms): Initial delay before first retrymaxDelay(default: 30000ms): Maximum delay between retriesbackoffMultiplier(default: 2): Multiplier for exponential backoffretryableErrors(default: []): Array of error classes to retry (empty = retry all)onRetry(optional): Callback function called on each retry attempt
Error Handling
Pre-defined error classes with HTTP status codes for common scenarios.
import {
BaseError,
ValidationError,
AuthenticationError,
AuthorizationError,
NotFoundError,
RateLimitError,
ToolExecutionError,
} from '@egintegrations/core-utils';
// Validation errors (400)
throw new ValidationError('Invalid email format');
// Authentication errors (401)
throw new AuthenticationError('Invalid credentials');
// Authorization errors (403)
throw new AuthorizationError('Admin access required');
// Not found errors (404)
throw new NotFoundError('User not found');
// Rate limit errors (429)
throw new RateLimitError('Too many requests', 60); // retryAfter: 60 seconds
// Tool execution errors (500)
const cause = new Error('Database connection failed');
throw new ToolExecutionError('Failed to execute tool', cause);
// Custom errors
throw new BaseError('Custom error', 418, 'CUSTOM_ERROR_CODE');
// Error handling
try {
await performOperation();
} catch (error) {
if (error instanceof BaseError) {
console.log(`Status: ${error.statusCode}, Code: ${error.code}`);
}
}Error Classes:
| Class | Status Code | Code | Description |
|-------|-------------|------|-------------|
| BaseError | 500 | BASE_ERROR | Base error class for all errors |
| ValidationError | 400 | VALIDATION_ERROR | Input validation failures |
| AuthenticationError | 401 | AUTHENTICATION_ERROR | Authentication failures |
| AuthorizationError | 403 | AUTHORIZATION_ERROR | Permission/authorization failures |
| NotFoundError | 404 | NOT_FOUND_ERROR | Resource not found |
| RateLimitError | 429 | RATE_LIMIT_ERROR | Rate limit exceeded |
| ToolExecutionError | 500 | TOOL_EXECUTION_ERROR | Tool execution failures |
Health Checks
Framework-agnostic health and readiness checks for services.
import {
HealthChecker,
runHealthChecks,
runReadinessChecks,
} from '@egintegrations/core-utils';
// Using HealthChecker class
const checker = new HealthChecker();
// Add health checks
checker.addHealthCheck({
name: 'database',
check: async () => {
try {
await db.ping();
return true;
} catch {
return false;
}
},
});
checker.addHealthCheck({
name: 'cache',
check: async () => {
try {
await redis.ping();
return true;
} catch {
return false;
}
},
});
// Run health checks
const healthStatus = await checker.runHealthChecks();
console.log(healthStatus);
// {
// healthy: true,
// timestamp: '2026-01-21T12:00:00.000Z',
// checks: { database: true, cache: true },
// uptime: 3600
// }
// Add readiness checks
checker.addReadinessCheck({
name: 'migrations',
check: async () => {
const pending = await db.getPendingMigrations();
return pending.length === 0;
},
});
// Run readiness checks
const readinessStatus = await checker.runReadinessChecks();
console.log(readinessStatus);
// {
// ready: true,
// timestamp: '2026-01-21T12:00:00.000Z',
// checks: { migrations: true }
// }
// Convenience functions for simple use cases
const healthStatus = await runHealthChecks([
{ name: 'database', check: async () => true },
{ name: 'api', check: async () => true },
]);
const readinessStatus = await runReadinessChecks([
{ name: 'config', check: async () => true },
]);Express Integration Example:
import express from 'express';
import { HealthChecker } from '@egintegrations/core-utils';
const app = express();
const checker = new HealthChecker();
// Add your checks
checker.addHealthCheck({
name: 'database',
check: async () => {
/* ... */
},
});
// Health endpoint
app.get('/health', async (req, res) => {
const status = await checker.runHealthChecks();
res.status(status.healthy ? 200 : 503).json(status);
});
// Readiness endpoint
app.get('/ready', async (req, res) => {
const status = await checker.runReadinessChecks();
res.status(status.ready ? 200 : 503).json(status);
});Idempotency
Pluggable storage interface for implementing idempotent operations.
import {
IdempotencyManager,
InMemoryIdempotencyStore,
generateIdempotencyKey,
} from '@egintegrations/core-utils';
// Using in-memory store (for development/testing)
const store = new InMemoryIdempotencyStore(24 * 60 * 60 * 1000); // 24 hour TTL
const manager = new IdempotencyManager(store);
// Execute with idempotency
const orderId = await manager.executeWithIdempotency(
'create-order-user123-item456',
async () => {
return await createOrder({ userId: 123, itemId: 456 });
}
);
// Subsequent calls with same key return cached result without re-execution
const sameOrderId = await manager.executeWithIdempotency(
'create-order-user123-item456',
async () => {
return await createOrder({ userId: 123, itemId: 456 }); // Not executed
}
);
// Generate unique idempotency keys
const key = generateIdempotencyKey('payment'); // "payment_timestamp_randomhex"
// Manual operations
await manager.save('operation-key', result, 3600000); // 1 hour TTL
const cached = await manager.check('operation-key');
await manager.delete('operation-key');Custom Storage Implementation:
import { IdempotencyStore, IdempotencyManager } from '@egintegrations/core-utils';
import Redis from 'ioredis';
class RedisIdempotencyStore implements IdempotencyStore {
constructor(private redis: Redis) {}
async get(key: string): Promise<any | null> {
const value = await this.redis.get(key);
return value ? JSON.parse(value) : null;
}
async set(key: string, value: any, ttlMs: number): Promise<void> {
await this.redis.set(key, JSON.stringify(value), 'PX', ttlMs);
}
async delete(key: string): Promise<void> {
await this.redis.del(key);
}
}
// Use custom store
const redis = new Redis();
const store = new RedisIdempotencyStore(redis);
const manager = new IdempotencyManager(store);Convenience Functions (using default in-memory store):
import {
checkIdempotency,
saveIdempotencyResult,
} from '@egintegrations/core-utils';
// Check for existing result
const cached = await checkIdempotency('operation-key');
if (cached) {
return cached;
}
// Perform operation and save result
const result = await performOperation();
await saveIdempotencyResult('operation-key', result);API Reference
Retry
retryWithBackoff<T>(fn: () => Promise<T>, config?: RetryConfig): Promise<T>
Executes a function with exponential backoff retry logic.
RetryableError
Error class that can be used to mark errors as retryable.
Errors
All error classes extend BaseError with the following properties:
message: string- Error messagestatusCode: number- HTTP status codecode: string- Error codename: string- Error name
Health Checks
HealthChecker
Class for managing health and readiness checks.
Methods:
addHealthCheck(check: HealthCheck): void- Add a health checkaddReadinessCheck(check: HealthCheck): void- Add a readiness checkrunHealthChecks(): Promise<HealthStatus>- Run all health checksrunReadinessChecks(): Promise<ReadinessStatus>- Run all readiness checks
runHealthChecks(checks: HealthCheck[]): Promise<HealthStatus>
Convenience function to run health checks without creating a HealthChecker instance.
runReadinessChecks(checks: HealthCheck[]): Promise<ReadinessStatus>
Convenience function to run readiness checks without creating a HealthChecker instance.
Idempotency
IdempotencyManager
Class for managing idempotent operations with a pluggable storage backend.
Constructor:
constructor(store: IdempotencyStore, defaultTtlMs?: number)
Methods:
check(key: string): Promise<any | null>- Check for existing resultsave(key: string, result: any, ttlMs?: number): Promise<void>- Save resultdelete(key: string): Promise<void>- Delete resultexecuteWithIdempotency<T>(key: string, fn: () => Promise<T>, ttlMs?: number): Promise<T>- Execute operation with idempotency
InMemoryIdempotencyStore
In-memory implementation of IdempotencyStore interface.
Constructor:
constructor(defaultTtlMs?: number)- Default: 24 hours
Methods:
get(key: string): Promise<any | null>set(key: string, value: any, ttlMs: number): Promise<void>delete(key: string): Promise<void>destroy(): void- Cleanup and stop background cleanup interval
generateIdempotencyKey(prefix?: string): string
Generates a unique idempotency key with optional prefix.
hashIdempotencyKey(key: string): string
Hashes an idempotency key using SHA-256.
Development
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Run tests with coverage
npm test -- --coverage
# Type check
npm run typecheck
# Lint
npm run lint
# Watch mode
npm run devContributing
Contributions are welcome! Please ensure:
- All tests pass (
npm test) - Code coverage remains ≥80%
- TypeScript types are properly defined
- Code follows the existing style (run
npm run lint)
License
MIT © EGI Integrations
Extracted From
This package was extracted from the egi-botnet project's bot-sdk-node package and refactored to be framework-agnostic with zero external dependencies.
Related Packages
- @egintegrations/observability - Logging, metrics, and telemetry
- @egintegrations/api-client - Generic HTTP client with retry and interceptors
Support
For issues and questions, please open an issue on GitHub.
