@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
8
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.
