immortal-js 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +577 -0
- package/docs/CHANGELOG.md +21 -0
- package/docs/CONTRIBUTING.md +41 -0
- package/docs/api/chaos.md +179 -0
- package/docs/api/dashboard.md +109 -0
- package/docs/api/isolation.md +191 -0
- package/docs/api/lifecycle.md +187 -0
- package/docs/api/monitoring.md +313 -0
- package/docs/api/plugins.md +217 -0
- package/docs/api/recovery.md +267 -0
- package/docs/api/safe-zone.md +236 -0
- package/docs/api/supervision.md +285 -0
- package/docs/guides/configuration.md +168 -0
- package/docs/guides/express.md +171 -0
- package/docs/guides/fastify.md +188 -0
- package/docs/guides/koa.md +102 -0
- package/docs/guides/nestjs.md +182 -0
- package/docs/guides/testing.md +91 -0
- package/examples/express-basic/index.ts +462 -0
- package/examples/express-basic/package.json +21 -0
- package/examples/express-basic/tsconfig.json +24 -0
- package/examples/fastify-microservice/index.ts +342 -0
- package/examples/fastify-microservice/package.json +22 -0
- package/examples/fastify-microservice/tsconfig.json +24 -0
- package/examples/invoice-service/data/invoices.db +0 -0
- package/examples/invoice-service/data/invoices.db-shm +0 -0
- package/examples/invoice-service/data/invoices.db-wal +0 -0
- package/examples/invoice-service/package.json +25 -0
- package/examples/invoice-service/public/index.html +5025 -0
- package/examples/invoice-service/src/db.ts +608 -0
- package/examples/invoice-service/src/pdf.ts +358 -0
- package/examples/invoice-service/src/server.ts +527 -0
- package/examples/invoice-service/src/store.ts +159 -0
- package/examples/invoice-service/src/types.ts +193 -0
- package/examples/invoice-service/tsconfig.json +23 -0
- package/examples/nestjs-enterprise/app.module.ts +561 -0
- package/examples/nestjs-enterprise/main.ts +67 -0
- package/examples/nestjs-enterprise/package.json +26 -0
- package/examples/nestjs-enterprise/tsconfig.json +27 -0
- package/immortal-js-1.0.0.tgz +0 -0
- package/package.json +33 -0
- package/packages/adapter-express/package.json +34 -0
- package/packages/adapter-express/src/index.ts +349 -0
- package/packages/adapter-express/tsconfig.json +14 -0
- package/packages/adapter-fastify/package.json +56 -0
- package/packages/adapter-fastify/src/plugin.ts +226 -0
- package/packages/adapter-fastify/tsconfig.json +14 -0
- package/packages/adapter-koa/package.json +55 -0
- package/packages/adapter-koa/src/index.ts +207 -0
- package/packages/adapter-koa/tsconfig.json +14 -0
- package/packages/adapter-nestjs/package.json +61 -0
- package/packages/adapter-nestjs/src/immortal.module.ts +313 -0
- package/packages/adapter-nestjs/src/index.ts +14 -0
- package/packages/adapter-nestjs/tsconfig.json +16 -0
- package/packages/core/package.json +56 -0
- package/packages/core/src/chaos/ChaosEngine.ts +249 -0
- package/packages/core/src/config/defaults.ts +200 -0
- package/packages/core/src/config/schema.ts +199 -0
- package/packages/core/src/event-bus.ts +168 -0
- package/packages/core/src/index.ts +164 -0
- package/packages/core/src/isolation/BulkheadPool.ts +279 -0
- package/packages/core/src/isolation/WorkerSandbox.ts +306 -0
- package/packages/core/src/isolation/index.ts +8 -0
- package/packages/core/src/lifecycle/GracefulShutdown.ts +161 -0
- package/packages/core/src/logger.ts +104 -0
- package/packages/core/src/monitoring/DiagnosticsChannel.ts +248 -0
- package/packages/core/src/monitoring/HealthMonitor.ts +191 -0
- package/packages/core/src/monitoring/MemoryLeakGuard.ts +340 -0
- package/packages/core/src/monitoring/MetricsCollector.ts +219 -0
- package/packages/core/src/monitoring/index.ts +10 -0
- package/packages/core/src/plugins/BuiltinPlugins.ts +269 -0
- package/packages/core/src/recovery/CircuitBreaker.ts +334 -0
- package/packages/core/src/recovery/FallbackCache.ts +328 -0
- package/packages/core/src/recovery/RetryEngine.ts +225 -0
- package/packages/core/src/recovery/Timeout.ts +97 -0
- package/packages/core/src/recovery/index.ts +11 -0
- package/packages/core/src/runtime.ts +242 -0
- package/packages/core/src/safe-zone/AsyncBoundary.ts +114 -0
- package/packages/core/src/safe-zone/ErrorTrap.ts +347 -0
- package/packages/core/src/safe-zone/SafeWrapper.ts +317 -0
- package/packages/core/src/safe-zone/index.ts +23 -0
- package/packages/core/src/supervision/ClusterManager.ts +243 -0
- package/packages/core/src/supervision/RestartStrategy.ts +68 -0
- package/packages/core/src/supervision/Supervisor.ts +311 -0
- package/packages/core/src/supervision/index.ts +11 -0
- package/packages/core/src/types.ts +470 -0
- package/packages/core/test/bulkhead.test.ts +310 -0
- package/packages/core/test/circuit-breaker.test.ts +153 -0
- package/packages/core/test/memory-guard.test.ts +213 -0
- package/packages/core/test/retry.test.ts +110 -0
- package/packages/core/test/safe-zone.test.ts +271 -0
- package/packages/core/test/supervisor.test.ts +310 -0
- package/packages/core/tsconfig.json +13 -0
- package/packages/dashboard/package.json +56 -0
- package/packages/dashboard/server/DashboardServer.ts +454 -0
- package/packages/dashboard/tsconfig.json +14 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file index.ts
|
|
3
|
+
* @description Immortal.js Core — Public API Entry Point
|
|
4
|
+
*
|
|
5
|
+
* "We don't promise you won't fall — we guarantee you stand back up
|
|
6
|
+
* before anyone notices you fell."
|
|
7
|
+
*
|
|
8
|
+
* @module @immortal/core
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
// TYPE EXPORTS
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
export type {
|
|
16
|
+
ImmortalConfig,
|
|
17
|
+
SafeZoneConfig,
|
|
18
|
+
RetryConfig,
|
|
19
|
+
CircuitBreakerConfig,
|
|
20
|
+
BulkheadConfig,
|
|
21
|
+
SupervisorConfig,
|
|
22
|
+
HealthMonitorConfig,
|
|
23
|
+
MemoryGuardConfig,
|
|
24
|
+
GracefulShutdownConfig,
|
|
25
|
+
DashboardConfig,
|
|
26
|
+
ChaosConfig,
|
|
27
|
+
LoggerConfig,
|
|
28
|
+
ImmortalPlugin,
|
|
29
|
+
PluginContext,
|
|
30
|
+
FallbackGenerator,
|
|
31
|
+
RequestContext,
|
|
32
|
+
ShutdownHook,
|
|
33
|
+
LogTransport,
|
|
34
|
+
ImmortalEvent,
|
|
35
|
+
ImmortalEventType,
|
|
36
|
+
SystemMetrics,
|
|
37
|
+
CircuitBreakerStatus,
|
|
38
|
+
BulkheadStatus,
|
|
39
|
+
WorkerStatus,
|
|
40
|
+
SerializedError,
|
|
41
|
+
Priority,
|
|
42
|
+
PriorityTask,
|
|
43
|
+
RestartStrategy,
|
|
44
|
+
} from "./types.js";
|
|
45
|
+
|
|
46
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
47
|
+
// ERROR CLASS EXPORTS
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
export {
|
|
50
|
+
ImmortalError,
|
|
51
|
+
BulkheadRejectedError,
|
|
52
|
+
CircuitOpenError,
|
|
53
|
+
RetryExhaustedError,
|
|
54
|
+
WorkerEscalatedError,
|
|
55
|
+
TimeoutError,
|
|
56
|
+
} from "./types.js";
|
|
57
|
+
|
|
58
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
// CONFIG EXPORTS
|
|
60
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
61
|
+
export {
|
|
62
|
+
mergeWithDefaults,
|
|
63
|
+
IMMORTAL_DEFAULTS,
|
|
64
|
+
DEFAULT_RETRY,
|
|
65
|
+
DEFAULT_CIRCUIT_BREAKER,
|
|
66
|
+
DEFAULT_BULKHEAD,
|
|
67
|
+
DEFAULT_SUPERVISOR,
|
|
68
|
+
DEFAULT_HEALTH_MONITOR,
|
|
69
|
+
DEFAULT_MEMORY_GUARD,
|
|
70
|
+
DEFAULT_GRACEFUL_SHUTDOWN,
|
|
71
|
+
} from "./config/defaults.js";
|
|
72
|
+
|
|
73
|
+
export { validateConfig } from "./config/schema.js";
|
|
74
|
+
export type { ValidationResult } from "./config/schema.js";
|
|
75
|
+
|
|
76
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
77
|
+
// INFRASTRUCTURE EXPORTS
|
|
78
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
79
|
+
export { createLogger, getLogger } from "./logger.js";
|
|
80
|
+
export { getEventBus, resetEventBus, ImmortalEventBus } from "./event-bus.js";
|
|
81
|
+
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
// SAFE ZONE EXPORTS
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
85
|
+
export { wrapHandler, createSafeZone } from "./safe-zone/SafeWrapper.js";
|
|
86
|
+
export { ErrorTrap } from "./safe-zone/ErrorTrap.js";
|
|
87
|
+
export { AsyncBoundary, asyncBoundary } from "./safe-zone/AsyncBoundary.js";
|
|
88
|
+
|
|
89
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
90
|
+
// RECOVERY EXPORTS
|
|
91
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
92
|
+
export { withRetry, RetryEngine } from "./recovery/RetryEngine.js";
|
|
93
|
+
export { CircuitBreaker } from "./recovery/CircuitBreaker.js";
|
|
94
|
+
export { FallbackCache } from "./recovery/FallbackCache.js";
|
|
95
|
+
export { withTimeout } from "./recovery/Timeout.js";
|
|
96
|
+
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
// ISOLATION EXPORTS
|
|
99
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
100
|
+
export { BulkheadPool } from "./isolation/BulkheadPool.js";
|
|
101
|
+
export { WorkerSandbox } from "./isolation/WorkerSandbox.js";
|
|
102
|
+
|
|
103
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
104
|
+
// SUPERVISION EXPORTS
|
|
105
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
106
|
+
export { Supervisor } from "./supervision/Supervisor.js";
|
|
107
|
+
export { ClusterManager } from "./supervision/ClusterManager.js";
|
|
108
|
+
|
|
109
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
110
|
+
// MONITORING EXPORTS
|
|
111
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
112
|
+
export { HealthMonitor } from "./monitoring/HealthMonitor.js";
|
|
113
|
+
export { MemoryLeakGuard } from "./monitoring/MemoryLeakGuard.js";
|
|
114
|
+
export { MetricsCollector } from "./monitoring/MetricsCollector.js";
|
|
115
|
+
export { DiagnosticsChannel } from "./monitoring/DiagnosticsChannel.js";
|
|
116
|
+
|
|
117
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
118
|
+
// LIFECYCLE EXPORTS
|
|
119
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
120
|
+
export { GracefulShutdown } from "./lifecycle/GracefulShutdown.js";
|
|
121
|
+
|
|
122
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
123
|
+
// CHAOS ENGINE EXPORTS
|
|
124
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
125
|
+
export { ChaosEngine } from "./chaos/ChaosEngine.js";
|
|
126
|
+
|
|
127
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
128
|
+
// PLUGIN EXPORTS
|
|
129
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
130
|
+
export {
|
|
131
|
+
ConsoleLogPlugin,
|
|
132
|
+
RequestTracingPlugin,
|
|
133
|
+
createAnomalyDetectionPlugin,
|
|
134
|
+
createSlackAlertPlugin,
|
|
135
|
+
BUILTIN_PLUGINS,
|
|
136
|
+
} from "./plugins/BuiltinPlugins.js";
|
|
137
|
+
|
|
138
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
139
|
+
// ADDITIONAL RECOVERY EXPORTS
|
|
140
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
141
|
+
export { CircuitBreakerRegistry } from "./recovery/CircuitBreaker.js";
|
|
142
|
+
export { RouteFallbackCache } from "./recovery/FallbackCache.js";
|
|
143
|
+
export { calculateBackoff } from "./recovery/RetryEngine.js";
|
|
144
|
+
export { withSimpleTimeout } from "./recovery/Timeout.js";
|
|
145
|
+
|
|
146
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
// ADDITIONAL ISOLATION EXPORTS
|
|
148
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
149
|
+
export { BulkheadRegistry } from "./isolation/BulkheadPool.js";
|
|
150
|
+
|
|
151
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
152
|
+
// ADDITIONAL SUPERVISION EXPORTS
|
|
153
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
154
|
+
export { computeRestartDecision } from "./supervision/RestartStrategy.js";
|
|
155
|
+
|
|
156
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
157
|
+
// UTILITY EXPORTS
|
|
158
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
159
|
+
export { classifyError, serializeError } from "./safe-zone/ErrorTrap.js";
|
|
160
|
+
|
|
161
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
162
|
+
// IMMORTAL RUNTIME — Main orchestrator class
|
|
163
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
164
|
+
export { ImmortalRuntime, createImmortal, getImmortal } from "./runtime.js";
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file BulkheadPool.ts
|
|
3
|
+
* @description Bulkhead isolation — prevents cascade failure between services
|
|
4
|
+
*
|
|
5
|
+
* The bulkhead pattern (named after ship compartments) ensures that:
|
|
6
|
+
* - Each external service gets its own resource pool
|
|
7
|
+
* - Pool saturation for one service CANNOT affect other services
|
|
8
|
+
* - Priority queuing allows critical operations to preempt lower-priority ones
|
|
9
|
+
* - Fail-fast rejection prevents queue buildup from degrading system response time
|
|
10
|
+
*
|
|
11
|
+
* Without bulkheads: a slow payment API fills the shared thread pool → search, orders,
|
|
12
|
+
* notifications all slow down → entire system collapses
|
|
13
|
+
* With bulkheads: payment pool fills → payments queue/reject → everything else runs fine
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { BulkheadConfig, BulkheadStatus, Priority, PriorityTask } from "../types.js";
|
|
17
|
+
import { BulkheadRejectedError } from "../types.js";
|
|
18
|
+
import { DEFAULT_BULKHEAD } from "../config/defaults.js";
|
|
19
|
+
import { getEventBus } from "../event-bus.js";
|
|
20
|
+
import { getLogger } from "../logger.js";
|
|
21
|
+
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// PRIORITY QUEUE
|
|
24
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const PRIORITY_ORDER: Priority[] = ["critical", "high", "medium", "low"];
|
|
27
|
+
|
|
28
|
+
interface QueuedItem<T> {
|
|
29
|
+
task: () => Promise<T>;
|
|
30
|
+
priority: Priority;
|
|
31
|
+
enqueueTime: number;
|
|
32
|
+
resolve: (value: T) => void;
|
|
33
|
+
reject: (reason: unknown) => void;
|
|
34
|
+
timeoutId?: ReturnType<typeof setTimeout>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class PriorityQueue<T> {
|
|
38
|
+
private buckets: Map<Priority, QueuedItem<T>[]>;
|
|
39
|
+
|
|
40
|
+
constructor() {
|
|
41
|
+
this.buckets = new Map();
|
|
42
|
+
for (const p of PRIORITY_ORDER) {
|
|
43
|
+
this.buckets.set(p, []);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
enqueue(item: QueuedItem<T>): void {
|
|
48
|
+
this.buckets.get(item.priority)!.push(item);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
dequeue(): QueuedItem<T> | undefined {
|
|
52
|
+
for (const priority of PRIORITY_ORDER) {
|
|
53
|
+
const bucket = this.buckets.get(priority)!;
|
|
54
|
+
if (bucket.length > 0) {
|
|
55
|
+
return bucket.shift();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get size(): number {
|
|
62
|
+
let total = 0;
|
|
63
|
+
for (const bucket of this.buckets.values()) {
|
|
64
|
+
total += bucket.length;
|
|
65
|
+
}
|
|
66
|
+
return total;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
clear(): void {
|
|
70
|
+
for (const p of PRIORITY_ORDER) {
|
|
71
|
+
this.buckets.set(p, []);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
77
|
+
// BULKHEAD POOL
|
|
78
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
export class BulkheadPool {
|
|
81
|
+
private active = 0;
|
|
82
|
+
private readonly queue: PriorityQueue<unknown>;
|
|
83
|
+
private readonly config: Required<BulkheadConfig>;
|
|
84
|
+
private totalRejected = 0;
|
|
85
|
+
private totalExecuted = 0;
|
|
86
|
+
|
|
87
|
+
constructor(
|
|
88
|
+
public readonly name: string,
|
|
89
|
+
config: BulkheadConfig = {}
|
|
90
|
+
) {
|
|
91
|
+
this.config = { ...DEFAULT_BULKHEAD, ...config };
|
|
92
|
+
this.queue = new PriorityQueue<unknown>();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Execute a task through the bulkhead pool.
|
|
97
|
+
*
|
|
98
|
+
* If the pool has capacity → execute immediately
|
|
99
|
+
* If pool is full but queue has space → queue with optional priority
|
|
100
|
+
* If queue is also full → fail fast with BulkheadRejectedError
|
|
101
|
+
*
|
|
102
|
+
* @param task - Async task to execute
|
|
103
|
+
* @param priority - Task priority (default: 'medium')
|
|
104
|
+
* @param timeoutMs - Optional per-task timeout override
|
|
105
|
+
*/
|
|
106
|
+
async run<T>(
|
|
107
|
+
task: () => Promise<T>,
|
|
108
|
+
priority: Priority = "medium",
|
|
109
|
+
timeoutMs?: number
|
|
110
|
+
): Promise<T> {
|
|
111
|
+
const bus = getEventBus();
|
|
112
|
+
|
|
113
|
+
// Pool has capacity → execute immediately
|
|
114
|
+
if (this.active < this.config.maxConcurrent) {
|
|
115
|
+
return this.executeTask(task);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Pool full → try to queue
|
|
119
|
+
if (this.queue.size >= this.config.maxQueueSize) {
|
|
120
|
+
this.totalRejected++;
|
|
121
|
+
|
|
122
|
+
bus.emit("bulkhead:rejected", {
|
|
123
|
+
service: this.name,
|
|
124
|
+
data: {
|
|
125
|
+
active: this.active,
|
|
126
|
+
queued: this.queue.size,
|
|
127
|
+
maxConcurrent: this.config.maxConcurrent,
|
|
128
|
+
maxQueueSize: this.config.maxQueueSize,
|
|
129
|
+
priority,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
throw new BulkheadRejectedError(this.name, "saturated");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Queue the task
|
|
137
|
+
const effectiveTimeout = timeoutMs ?? this.config.queueTimeoutMs;
|
|
138
|
+
|
|
139
|
+
bus.emit("bulkhead:queued", {
|
|
140
|
+
service: this.name,
|
|
141
|
+
data: { queued: this.queue.size + 1, priority },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return new Promise<T>((resolve, reject) => {
|
|
145
|
+
const item: QueuedItem<T> = {
|
|
146
|
+
task,
|
|
147
|
+
priority,
|
|
148
|
+
enqueueTime: Date.now(),
|
|
149
|
+
resolve,
|
|
150
|
+
reject,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Queue timeout
|
|
154
|
+
item.timeoutId = setTimeout(() => {
|
|
155
|
+
// Remove from queue by rejecting
|
|
156
|
+
item.reject(new BulkheadRejectedError(this.name, "timeout"));
|
|
157
|
+
bus.emit("bulkhead:timeout", {
|
|
158
|
+
service: this.name,
|
|
159
|
+
data: { queuedMs: effectiveTimeout, priority },
|
|
160
|
+
});
|
|
161
|
+
}, effectiveTimeout);
|
|
162
|
+
|
|
163
|
+
this.queue.enqueue(item as QueuedItem<unknown>);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Priority-aware run — convenience wrapper
|
|
169
|
+
*/
|
|
170
|
+
async runWithPriority<T>(priorityTask: PriorityTask<T>): Promise<T> {
|
|
171
|
+
return this.run(
|
|
172
|
+
priorityTask.task,
|
|
173
|
+
priorityTask.priority,
|
|
174
|
+
priorityTask.timeoutMs
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get current pool status for monitoring
|
|
180
|
+
*/
|
|
181
|
+
getStatus(): BulkheadStatus {
|
|
182
|
+
return {
|
|
183
|
+
name: this.name,
|
|
184
|
+
active: this.active,
|
|
185
|
+
queued: this.queue.size,
|
|
186
|
+
maxConcurrent: this.config.maxConcurrent,
|
|
187
|
+
maxQueueSize: this.config.maxQueueSize,
|
|
188
|
+
rejected: this.totalRejected,
|
|
189
|
+
totalExecuted: this.totalExecuted,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Drain all queued tasks (for graceful shutdown)
|
|
195
|
+
*/
|
|
196
|
+
drain(): void {
|
|
197
|
+
let item = this.queue.dequeue() as QueuedItem<unknown> | undefined;
|
|
198
|
+
while (item) {
|
|
199
|
+
if (item.timeoutId) clearTimeout(item.timeoutId);
|
|
200
|
+
item.reject(new BulkheadRejectedError(this.name, "saturated"));
|
|
201
|
+
item = this.queue.dequeue() as QueuedItem<unknown> | undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async executeTask<T>(task: () => Promise<T>): Promise<T> {
|
|
206
|
+
this.active++;
|
|
207
|
+
try {
|
|
208
|
+
const result = await task();
|
|
209
|
+
this.totalExecuted++;
|
|
210
|
+
return result;
|
|
211
|
+
} finally {
|
|
212
|
+
this.active--;
|
|
213
|
+
this.processQueue();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private processQueue(): void {
|
|
218
|
+
if (this.queue.size === 0) return;
|
|
219
|
+
if (this.active >= this.config.maxConcurrent) return;
|
|
220
|
+
|
|
221
|
+
const item = this.queue.dequeue() as QueuedItem<unknown> | undefined;
|
|
222
|
+
if (!item) return;
|
|
223
|
+
|
|
224
|
+
if (item.timeoutId) clearTimeout(item.timeoutId);
|
|
225
|
+
|
|
226
|
+
const waitMs = Date.now() - item.enqueueTime;
|
|
227
|
+
getLogger().debug(`Bulkhead '${this.name}': executing queued task (waited ${waitMs}ms)`);
|
|
228
|
+
|
|
229
|
+
this.executeTask(item.task)
|
|
230
|
+
.then((result) => (item.resolve as (v: unknown) => void)(result))
|
|
231
|
+
.catch((err) => item.reject(err));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
236
|
+
// BULKHEAD REGISTRY
|
|
237
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
export class BulkheadRegistry {
|
|
240
|
+
private pools = new Map<string, BulkheadPool>();
|
|
241
|
+
private readonly defaultConfig: BulkheadConfig;
|
|
242
|
+
|
|
243
|
+
constructor(defaultConfig: BulkheadConfig = {}) {
|
|
244
|
+
this.defaultConfig = defaultConfig;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get or create a named bulkhead pool
|
|
249
|
+
*/
|
|
250
|
+
get(name: string, config?: BulkheadConfig): BulkheadPool {
|
|
251
|
+
if (!this.pools.has(name)) {
|
|
252
|
+
this.pools.set(
|
|
253
|
+
name,
|
|
254
|
+
new BulkheadPool(name, { ...this.defaultConfig, ...config })
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return this.pools.get(name)!;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get statuses of all registered pools
|
|
262
|
+
*/
|
|
263
|
+
getAllStatuses(): Record<string, BulkheadStatus> {
|
|
264
|
+
const result: Record<string, BulkheadStatus> = {};
|
|
265
|
+
for (const [name, pool] of this.pools) {
|
|
266
|
+
result[name] = pool.getStatus();
|
|
267
|
+
}
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Drain all pools (graceful shutdown)
|
|
273
|
+
*/
|
|
274
|
+
drainAll(): void {
|
|
275
|
+
for (const pool of this.pools.values()) {
|
|
276
|
+
pool.drain();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|