@angsana_consulting/utils
v1.0.5
Published
Shared utilities for Angsana platform
Readme
@angsana_consulting/utils
Shared utility layer for every Angsana service. Bundles logging, retry orchestration, TTL helpers, and validation primitives so projects behave consistently with the router.
1. Purpose
This package centralises infrastructure-grade helpers:
- Logging primitives that mirror
logger-servicebehaviour when you need inline logging. - Retry + circuit breaker flows for resilient external calls.
- TTL helpers to honour platform retention policies.
- Validation utilities for Retool requests and Cloud Function payloads.
Use it anywhere you write TypeScript/Node inside the Angsana ecosystem.
2. Modules
| Module | Description |
|--------|-------------|
| logger | Console + Firestore logging with collection routing (SystemLogs, ErrorLogs, UsageLogs). |
| retry | withRetry, withSimpleRetry, request deduplication, circuit breaker states, DLQ helpers. |
| ttl | TTL/retention calculators, date helpers. |
| validation | Request validators, schema loaders, sanitisation helpers. |
| index | Barrel export that ties everything together. |
3. Directory Structure
utils/
├── src/
│ ├── logger.ts
│ ├── retry.ts
│ ├── ttl.ts
│ ├── validation.ts
│ └── index.ts
├── dist/
└── package.json4. Installation
npm install @angsana_consulting/utilsPeer packages:
@angsana_consulting/config– settings + Firebase handlesfirebase-adminlibphonenumber-jsfor validation
5. Logger Module
5.1 Collections
| Collection | Purpose | Routed When |
|------------|---------|-------------|
| SystemLogs | Technical + lifecycle events | logSystem, info/warn/debug |
| ErrorLogs | Errors, failed operations | logError, logFatal, level === ERROR/FATAL |
| UsageLogs | Business activity | logUsage or explicit usage payloads |
Logs include expiresAt (from Settings retention), filtered sensitive fields, truncated contexts, and optional console mirroring.
5.2 Log Levels
enum LogLevel {
TRACE = 0,
DEBUG = 1,
INFO = 2,
WARN = 3,
ERROR = 4,
FATAL = 5,
OFF = 6
}5.3 Quick Start
import { logInfo, logError, logUsage } from '@angsana_consulting/utils';
await logInfo('myService', 'operation', 'Something happened', {
userId: '[email protected]',
sessionId: 'session-123'
});
await logError('myService', 'operationFailed', 'DB connection failed', {}, new Error('timeout'));
await logUsage('callOrchestration', '[email protected]', 'session-123', {
clientId: 'client-abc',
dialCount: 3,
successfulConnect: true
});Convenience helpers: logTrace, logDebug, logInfo, logWarn, logError, logFatal, logUsage, logSystem.
5.4 Context Contract
interface LogContext {
userId?: string;
clientId?: string;
sessionId?: string;
targetId?: string;
callerNumber?: string;
dialedNumber?: string;
callSid?: string;
duration?: number;
[key: string]: any;
}5.5 Logging Settings Sample
{
"logging": {
"level": "INFO",
"enableConsole": true,
"enableFirestore": true,
"maxContextSize": 1000,
"services": {
"callOrchestration": "DEBUG",
"callback": "INFO",
"twilio": "WARN"
}
}
}6. Retry Module
6.1 Quick Start
import { withRetry, withSimpleRetry } from '@angsana_consulting/utils';
const result = await withRetry(() => someFlakyOp(), {
service: 'myService',
operation: 'flakyOperation',
userId: '[email protected]',
sessionId: 'session-123',
metadata: { targetId: 'target-456' }
});
const data = await withSimpleRetry(() => fetchData(), 'myService', 'fetchData', {
url: 'https://api.example.com'
});6.2 Error Classification
| Type | Examples | Default Strategy |
|------|----------|------------------|
| network | ETIMEDOUT, ECONNRESET, ECONNREFUSED | 5 attempts, 500 ms base, ×2 backoff |
| rateLimit | HTTP 429, Twilio 20429 | 2 attempts, 2000 ms base, ×3 backoff |
| serverError | HTTP 5xx, Twilio 20500/20503 | 3 attempts, 1000 ms base, ×2 backoff |
| other | Validation / client errors | No retry |
6.3 Circuit Breaker
CLOSED → (failures >= threshold) → OPEN → (timeout) → HALF-OPEN
↑ ↓
└────────────── success ───────────────┘Config (from Settings):
{
"retryPolicy": {
"maxAttempts": 3,
"baseDelayMs": 1000,
"maxDelayMs": 10000,
"jitterMs": 500,
"backoffMultiplier": 2,
"errorSpecificPolicies": { ... },
"circuitBreaker": {
"failureThreshold": 5,
"timeoutMs": 60000,
"halfOpenTimeoutMs": 30000,
"healthCheckIntervalMs": 15000
}
}
}6.4 Dead Letter Queue
- Failed operations push to
DeadLetterQueue. - Document shape records timestamps, error type, attempt count, priority, original payload, and status.
- Automatic daily caps: 100 per operation, 500 per service, 2000 overall.
6.5 Request Deduplication
const [a, b] = await Promise.all([
withRetry(() => fetchUser('123'), { service: 'users', operation: 'fetch', requestId: 'user-123' }),
withRetry(() => fetchUser('123'), { service: 'users', operation: 'fetch', requestId: 'user-123' })
]);
// a === b (same promise)7. TTL Module
7.1 Helpers
import { calculateTTL, getTodayDateString, calculateDaysSince } from '@angsana_consulting/utils';
const expiresAt = calculateTTL(settings, 'ErrorLogs', 'default');
const today = getTodayDateString(); // "2025-12-11"
const days = calculateDaysSince(someTimestamp);7.2 Lookup Order
settings.retention[collection][context]settings.retention[collection]- Fallback: 30 days
Supported units: seconds, minutes, hours, days, weeks, months (~30d), years (~365d).
Example retention block:
{
"retention": {
"SystemLogs": { "value": 7, "unit": "days" },
"ErrorLogs": { "value": 30, "unit": "days" },
"UsageLogs": { "value": 90, "unit": "days" },
"DeadLetterQueue": { "value": 14, "unit": "days" }
}
}8. Validation Module
8.1 Quick Start
import { validateRequest, validateInput, validateSimple } from '@angsana_consulting/utils';
const result = await validateRequest('callOrchestration', requestBody);
if (!result.isValid) {
return res.status(400).json({ errors: result.errors });
}
const cleanData = result.sanitized;8.2 Result Shape
interface ValidationResult {
isValid: boolean;
data?: any;
errors: ValidationError[];
warnings: ValidationWarning[];
sanitized: any;
}Validators integrate with the Settings provider to fetch schemas and sanitisation rules so Retool + router flows stay aligned.
9. Publishing
cd packages/utils
npm run build
npm publish --access publicAlways publish utils before packages that depend on its new APIs (logger-service, router functions, etc.).
