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.
npm install @egintegrations/core-utilsFoundational TypeScript utilities for retry logic, error handling, health checks, and idempotency. Designed for use across API, BOT, and service applications with zero external dependencies.
- 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
``bash`
npm install @egintegrations/core-utils
The retry utility provides exponential backoff retry logic with configurable parameters.
`typescript
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
Pre-defined error classes with HTTP status codes for common scenarios.
`typescript
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 |
Framework-agnostic health and readiness checks for services.
`typescript
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:
`typescript
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);
});
`
Pluggable storage interface for implementing idempotent operations.
`typescript
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:
`typescript
import { IdempotencyStore, IdempotencyManager } from '@egintegrations/core-utils';
import Redis from 'ioredis';
class RedisIdempotencyStore implements IdempotencyStore {
constructor(private redis: Redis) {}
async get(key: string): Promise
const value = await this.redis.get(key);
return value ? JSON.parse(value) : null;
}
async set(key: string, value: any, ttlMs: number): Promise
await this.redis.set(key, JSON.stringify(value), 'PX', ttlMs);
}
async delete(key: string): Promise
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):
`typescript
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);
`
#### retryWithBackoff
Executes a function with exponential backoff retry logic.
#### RetryableError
Error class that can be used to mark errors as retryable.
All error classes extend BaseError with the following properties:
- message: string - Error messagestatusCode: number
- - HTTP status codecode: string
- - Error codename: string
- - Error name
#### 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
- - Run all health checksrunReadinessChecks(): Promise
- - Run all readiness checks
#### runHealthChecks(checks: HealthCheck[]): Promise
Convenience function to run health checks without creating a HealthChecker instance.
#### runReadinessChecks(checks: HealthCheck[]): Promise
Convenience function to run readiness checks without creating a HealthChecker instance.
#### IdempotencyManager
Class for managing idempotent operations with a pluggable storage backend.
Constructor:
- constructor(store: IdempotencyStore, defaultTtlMs?: number)
Methods:
- check(key: string): Promise - Check for existing resultsave(key: string, result: any, ttlMs?: number): Promise
- - Save resultdelete(key: string): Promise
- - Delete resultexecuteWithIdempotency
- - Execute operation with idempotency
#### InMemoryIdempotencyStore
In-memory implementation of IdempotencyStore interface.
Constructor:
- constructor(defaultTtlMs?: number) - Default: 24 hours
Methods:
- get(key: string): Promiseset(key: string, value: any, ttlMs: number): Promise
- delete(key: string): Promise
- 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.
`bashInstall dependencies
npm install
Contributing
Contributions are welcome! Please ensure:
1. All tests pass (
npm test)
2. Code coverage remains ≥80%
3. TypeScript types are properly defined
4. Code follows the existing style (run npm run lint`)MIT © EGI Integrations
This package was extracted from the egi-botnet project's bot-sdk-node package and refactored to be framework-agnostic with zero external dependencies.
- @egintegrations/observability - Logging, metrics, and telemetry
- @egintegrations/api-client - Generic HTTP client with retry and interceptors
For issues and questions, please open an issue on GitHub.