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,313 @@
|
|
|
1
|
+
# API Reference — Monitoring (Layer 5: Observability)
|
|
2
|
+
|
|
3
|
+
The **Monitoring** layer collects system metrics, detects anomalies, monitors memory growth, and exposes health status. It feeds data to the Supervision layer so restarts can be triggered proactively.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## MetricsCollector
|
|
8
|
+
|
|
9
|
+
Collects event loop lag, CPU, memory, GC frequency, and per-service circuit/bulkhead statistics.
|
|
10
|
+
|
|
11
|
+
### `new MetricsCollector()`
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { MetricsCollector } from '@immortal/core';
|
|
15
|
+
|
|
16
|
+
const collector = new MetricsCollector();
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The collector starts tracking automatically on construction (GC observers, CPU sampling).
|
|
20
|
+
|
|
21
|
+
### `collector.getSnapshot()`
|
|
22
|
+
|
|
23
|
+
Returns the latest metrics snapshot:
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
const snapshot = collector.getSnapshot();
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Returns:** `SystemMetrics`
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
interface SystemMetrics {
|
|
33
|
+
timestamp: number;
|
|
34
|
+
eventLoopLag: {
|
|
35
|
+
p50: number; // ms
|
|
36
|
+
p95: number;
|
|
37
|
+
p99: number;
|
|
38
|
+
max: number;
|
|
39
|
+
};
|
|
40
|
+
memory: {
|
|
41
|
+
heapUsedMb: number;
|
|
42
|
+
heapTotalMb: number;
|
|
43
|
+
externalMb: number;
|
|
44
|
+
rssMb: number;
|
|
45
|
+
heapGrowthRate: number; // MB/sample
|
|
46
|
+
gcFrequency: number; // GC events/sec
|
|
47
|
+
};
|
|
48
|
+
cpu: {
|
|
49
|
+
percentUser: number;
|
|
50
|
+
percentSystem: number;
|
|
51
|
+
};
|
|
52
|
+
handles: {
|
|
53
|
+
active: number;
|
|
54
|
+
};
|
|
55
|
+
requests: {
|
|
56
|
+
total: number;
|
|
57
|
+
active: number;
|
|
58
|
+
perSecond: number;
|
|
59
|
+
errorRate: number;
|
|
60
|
+
};
|
|
61
|
+
circuits: Record<string, CircuitBreakerStatus>;
|
|
62
|
+
bulkheads: Record<string, BulkheadStatus>;
|
|
63
|
+
workers: WorkerStatus[];
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `collector.recordRequestStart()` / `collector.recordRequestEnd(success)`
|
|
68
|
+
|
|
69
|
+
Track in-flight requests for RPS and error rate calculation.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
collector.recordRequestStart();
|
|
73
|
+
try {
|
|
74
|
+
await handler(req, res);
|
|
75
|
+
collector.recordRequestEnd(true);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
collector.recordRequestEnd(false);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `collector.getHeapGrowthRate()`
|
|
82
|
+
|
|
83
|
+
Returns heap growth rate in MB/sample.
|
|
84
|
+
|
|
85
|
+
### `collector.getGcFrequency()`
|
|
86
|
+
|
|
87
|
+
Returns GC events per second.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## HealthMonitor
|
|
92
|
+
|
|
93
|
+
Periodically evaluates system health and emits warning/critical events on the event bus.
|
|
94
|
+
|
|
95
|
+
### `new HealthMonitor(config, bus, metricsCollector)`
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { HealthMonitor } from '@immortal/core';
|
|
99
|
+
|
|
100
|
+
const monitor = new HealthMonitor(
|
|
101
|
+
{
|
|
102
|
+
checkIntervalMs: 30_000,
|
|
103
|
+
eventLoopLagWarningMs: 50,
|
|
104
|
+
eventLoopLagCriticalMs: 100,
|
|
105
|
+
heapUsageWarningPercent: 80,
|
|
106
|
+
heapUsageCriticalPercent: 90,
|
|
107
|
+
cpuWarningPercent: 80,
|
|
108
|
+
cpuCriticalPercent: 95,
|
|
109
|
+
},
|
|
110
|
+
getEventBus(),
|
|
111
|
+
metricsCollector
|
|
112
|
+
);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Config:**
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
interface HealthMonitorConfig {
|
|
119
|
+
enabled?: boolean;
|
|
120
|
+
checkIntervalMs?: number; // default: 30_000
|
|
121
|
+
eventLoopLagWarningMs?: number; // default: 50
|
|
122
|
+
eventLoopLagCriticalMs?: number; // default: 100
|
|
123
|
+
heapUsageWarningPercent?: number; // default: 80
|
|
124
|
+
heapUsageCriticalPercent?: number;// default: 90
|
|
125
|
+
cpuWarningPercent?: number; // default: 80
|
|
126
|
+
cpuCriticalPercent?: number; // default: 95
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `monitor.start()` / `monitor.stop()`
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
monitor.start();
|
|
134
|
+
// ...
|
|
135
|
+
monitor.stop();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Events emitted:**
|
|
139
|
+
|
|
140
|
+
| Event | Trigger |
|
|
141
|
+
|-------|---------|
|
|
142
|
+
| `eventloop:lag-warning` | Event loop lag > `eventLoopLagWarningMs` |
|
|
143
|
+
| `eventloop:lag-critical` | Event loop lag > `eventLoopLagCriticalMs` |
|
|
144
|
+
| `cpu:warning` | CPU usage > `cpuWarningPercent` |
|
|
145
|
+
| `cpu:critical` | CPU usage > `cpuCriticalPercent` |
|
|
146
|
+
| `memory:warning` | Heap usage > `heapUsageWarningPercent` |
|
|
147
|
+
| `memory:critical` | Heap usage > `heapUsageCriticalPercent` |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## MemoryLeakGuard
|
|
152
|
+
|
|
153
|
+
Detects memory leaks by tracking heap growth over a sliding window. Can trigger proactive restarts before OOM.
|
|
154
|
+
|
|
155
|
+
### `new MemoryLeakGuard(config, bus)`
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { MemoryLeakGuard } from '@immortal/core';
|
|
159
|
+
|
|
160
|
+
const guard = new MemoryLeakGuard(
|
|
161
|
+
{
|
|
162
|
+
checkIntervalMs: 30_000,
|
|
163
|
+
warningThresholdMb: 400,
|
|
164
|
+
restartThresholdMb: 600,
|
|
165
|
+
growthWindowCount: 5,
|
|
166
|
+
adaptive: true,
|
|
167
|
+
},
|
|
168
|
+
getEventBus()
|
|
169
|
+
);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Config:**
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
interface MemoryGuardConfig {
|
|
176
|
+
enabled?: boolean;
|
|
177
|
+
checkIntervalMs?: number; // default: 60_000
|
|
178
|
+
warningThresholdMb?: number; // default: 400
|
|
179
|
+
restartThresholdMb?: number; // default: 600
|
|
180
|
+
growthWindowCount?: number; // default: 5 — samples for growth detection
|
|
181
|
+
adaptive?: boolean; // default: true — adjust thresholds based on baseline
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### `guard.start()` / `guard.stop()`
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
guard.start();
|
|
189
|
+
guard.stop();
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `guard.forceSample()`
|
|
193
|
+
|
|
194
|
+
Force an immediate memory check (used in tests and admin endpoints).
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
guard.forceSample();
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Events emitted:**
|
|
201
|
+
|
|
202
|
+
| Event | Trigger |
|
|
203
|
+
|-------|---------|
|
|
204
|
+
| `memory:warning` | Heap above warning threshold or growth detected |
|
|
205
|
+
| `memory:critical` | Heap above restart threshold |
|
|
206
|
+
| `memory:leak-detected` | Sustained heap growth over `growthWindowCount` samples |
|
|
207
|
+
| `memory:proactive-restart` | Guard is requesting a process restart |
|
|
208
|
+
|
|
209
|
+
### Adaptive mode
|
|
210
|
+
|
|
211
|
+
With `adaptive: true`, the guard establishes a baseline during the first few samples and adjusts thresholds relative to the baseline. This prevents false positives in processes with legitimately large heaps (e.g. image processing).
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## DiagnosticsChannel
|
|
216
|
+
|
|
217
|
+
OpenTelemetry-compatible tracing and metrics export via OTLP.
|
|
218
|
+
|
|
219
|
+
### `new DiagnosticsChannel(options?)`
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { DiagnosticsChannel } from '@immortal/core';
|
|
223
|
+
|
|
224
|
+
const dc = new DiagnosticsChannel({
|
|
225
|
+
otlpEndpoint: 'http://otel-collector:4318',
|
|
226
|
+
metricsIntervalMs: 60_000,
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Options:**
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
interface DiagnosticsChannelOptions {
|
|
234
|
+
otlpEndpoint?: string;
|
|
235
|
+
metricsIntervalMs?: number; // default: 60_000
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### `dc.recordRequest(info)`
|
|
240
|
+
|
|
241
|
+
Record an HTTP request for metrics aggregation.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
dc.recordRequest({
|
|
245
|
+
route: '/api/orders',
|
|
246
|
+
method: 'POST',
|
|
247
|
+
statusCode: 201,
|
|
248
|
+
durationMs: 145,
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### `dc.startSpan(name, attributes?)` / `dc.endSpan(spanId)`
|
|
253
|
+
|
|
254
|
+
Create and close trace spans.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
const spanId = dc.startSpan('db.query', { 'db.statement': sql });
|
|
258
|
+
try {
|
|
259
|
+
await db.query(sql);
|
|
260
|
+
} finally {
|
|
261
|
+
dc.endSpan(spanId);
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### `dc.destroy()`
|
|
266
|
+
|
|
267
|
+
Stop the metrics export interval.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Event Bus
|
|
272
|
+
|
|
273
|
+
The monitoring layer communicates through the Immortal event bus. Subscribe to any event from any layer.
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { getEventBus } from '@immortal/core';
|
|
277
|
+
|
|
278
|
+
const bus = getEventBus();
|
|
279
|
+
|
|
280
|
+
bus.on('memory:critical', (event) => {
|
|
281
|
+
console.error('Memory critical!', event.data);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
bus.on('circuit:opened', (event) => {
|
|
285
|
+
console.warn(`Circuit ${event.data?.name} opened`);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Get history
|
|
289
|
+
const recentEvents = bus.getHistory({
|
|
290
|
+
type: 'worker:crashed',
|
|
291
|
+
since: Date.now() - 60_000,
|
|
292
|
+
limit: 10,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Event counts
|
|
296
|
+
const counts = bus.getEventCounts(300_000); // last 5 minutes
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**All event types:**
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
error:captured error:escalated
|
|
303
|
+
retry:attempt retry:exhausted retry:success
|
|
304
|
+
circuit:opened circuit:closed circuit:half-open circuit:rejected
|
|
305
|
+
bulkhead:queued bulkhead:rejected bulkhead:timeout
|
|
306
|
+
worker:started worker:restarted worker:escalated worker:crashed
|
|
307
|
+
memory:warning memory:critical memory:leak-detected memory:proactive-restart
|
|
308
|
+
eventloop:lag-warning eventloop:lag-critical
|
|
309
|
+
cpu:warning cpu:critical
|
|
310
|
+
shutdown:initiated shutdown:complete shutdown:forced
|
|
311
|
+
fallback:cache-hit fallback:default-used
|
|
312
|
+
chaos:latency-injected chaos:error-injected chaos:worker-killed
|
|
313
|
+
```
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# API Reference — Plugin System
|
|
2
|
+
|
|
3
|
+
Immortal.js has a first-class plugin system that lets you extend the runtime with cross-cutting concerns: logging pipelines, distributed tracing, alerting, anomaly detection, and more.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Plugin Interface
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
interface ImmortalPlugin {
|
|
11
|
+
/** Unique plugin name (for logging and deduplication) */
|
|
12
|
+
name: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Called during ImmortalRuntime.initialize().
|
|
16
|
+
* Use to connect to external services, register listeners, etc.
|
|
17
|
+
*/
|
|
18
|
+
onInit?(ctx: PluginContext): Promise<void> | void;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Called during ImmortalRuntime.shutdown().
|
|
22
|
+
* Flush buffers, close connections, etc.
|
|
23
|
+
*/
|
|
24
|
+
onShutdown?(signal: string): Promise<void> | void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PluginContext {
|
|
28
|
+
config: Required<ImmortalConfig>;
|
|
29
|
+
emit: (event: Partial<ImmortalEvent>) => void;
|
|
30
|
+
getMetrics: () => SystemMetrics;
|
|
31
|
+
logger: ImmortalLogger;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Built-in Plugins
|
|
38
|
+
|
|
39
|
+
### `ConsoleLogPlugin`
|
|
40
|
+
|
|
41
|
+
Structured console logging. Prints JSON lines to stdout.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { ConsoleLogPlugin } from '@immortal/core';
|
|
45
|
+
|
|
46
|
+
const immortal = await createImmortal({
|
|
47
|
+
plugins: [ConsoleLogPlugin],
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Subscribes to all Immortal events and logs them with contextual metadata.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### `RequestTracingPlugin`
|
|
56
|
+
|
|
57
|
+
Propagates request IDs through async context using `AsyncLocalStorage`.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { RequestTracingPlugin } from '@immortal/core';
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
After initialization, every async operation can retrieve the current request ID:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { AsyncBoundary } from '@immortal/core';
|
|
67
|
+
|
|
68
|
+
const requestId = AsyncBoundary.getRequestId();
|
|
69
|
+
// → 'req-abc-123' | undefined
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Pairs well with `asyncBoundary()`:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const handler = asyncBoundary(async (req) => {
|
|
76
|
+
const reqId = AsyncBoundary.getRequestId();
|
|
77
|
+
logger.info('Handling request', { reqId });
|
|
78
|
+
return processRequest(req);
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### `createAnomalyDetectionPlugin(options)`
|
|
85
|
+
|
|
86
|
+
Monitors metrics for statistical anomalies and emits `error:escalated` events.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { createAnomalyDetectionPlugin } from '@immortal/core';
|
|
90
|
+
|
|
91
|
+
const plugin = createAnomalyDetectionPlugin({
|
|
92
|
+
threshold: 0.1, // anomaly score threshold (0–1)
|
|
93
|
+
windowMs: 300_000, // 5-minute rolling window
|
|
94
|
+
metrics: ['errorRate', 'eventLoopLag', 'heapGrowthRate'],
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Options:**
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
interface AnomalyDetectionOptions {
|
|
102
|
+
threshold?: number; // default: 0.2
|
|
103
|
+
windowMs?: number; // default: 300_000
|
|
104
|
+
metrics?: string[]; // which metrics to monitor
|
|
105
|
+
onAnomaly?: (info: AnomalyInfo) => void;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### `createSlackAlertPlugin(options)`
|
|
112
|
+
|
|
113
|
+
Sends Slack webhook notifications for critical Immortal events.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { createSlackAlertPlugin } from '@immortal/core';
|
|
117
|
+
|
|
118
|
+
const plugin = createSlackAlertPlugin({
|
|
119
|
+
webhookUrl: process.env.SLACK_WEBHOOK!,
|
|
120
|
+
events: ['worker:escalated', 'circuit:opened', 'memory:critical'],
|
|
121
|
+
throttleMs: 60_000, // max 1 alert per event type per minute
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Options:**
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
interface SlackAlertOptions {
|
|
129
|
+
webhookUrl: string;
|
|
130
|
+
events?: ImmortalEventType[]; // default: all critical events
|
|
131
|
+
throttleMs?: number; // default: 60_000
|
|
132
|
+
channel?: string; // override default channel
|
|
133
|
+
username?: string; // default: 'Immortal.js'
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### `BUILTIN_PLUGINS`
|
|
140
|
+
|
|
141
|
+
All built-in plugins as an array, for convenience:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { BUILTIN_PLUGINS } from '@immortal/core';
|
|
145
|
+
|
|
146
|
+
const immortal = await createImmortal({
|
|
147
|
+
plugins: [...BUILTIN_PLUGINS],
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Writing a Custom Plugin
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import type { ImmortalPlugin } from '@immortal/core';
|
|
157
|
+
import { getEventBus } from '@immortal/core';
|
|
158
|
+
|
|
159
|
+
export const DatadogPlugin: ImmortalPlugin = {
|
|
160
|
+
name: 'datadog',
|
|
161
|
+
|
|
162
|
+
async onInit(ctx) {
|
|
163
|
+
const bus = getEventBus();
|
|
164
|
+
|
|
165
|
+
// Subscribe to all events
|
|
166
|
+
bus.onAny((event) => {
|
|
167
|
+
dogstatsd.increment(`immortal.event.${event.type}`, 1, {
|
|
168
|
+
env: ctx.config.logger.prefix ?? 'default',
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Push metrics every 30s
|
|
173
|
+
const interval = setInterval(() => {
|
|
174
|
+
const metrics = ctx.getMetrics();
|
|
175
|
+
dogstatsd.gauge('immortal.heap_mb', metrics.memory.heapUsedMb);
|
|
176
|
+
dogstatsd.gauge('immortal.eventloop_lag_p99', metrics.eventLoopLag.p99);
|
|
177
|
+
}, 30_000).unref();
|
|
178
|
+
|
|
179
|
+
ctx.logger.info('Datadog plugin initialized');
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
async onShutdown() {
|
|
183
|
+
await dogstatsd.close();
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Plugin registration
|
|
191
|
+
|
|
192
|
+
Plugins are registered in `createImmortal()`:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const immortal = await createImmortal({
|
|
196
|
+
plugins: [
|
|
197
|
+
ConsoleLogPlugin,
|
|
198
|
+
RequestTracingPlugin,
|
|
199
|
+
createAnomalyDetectionPlugin({ threshold: 0.15 }),
|
|
200
|
+
createSlackAlertPlugin({ webhookUrl: process.env.SLACK_WEBHOOK! }),
|
|
201
|
+
DatadogPlugin,
|
|
202
|
+
],
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Or added dynamically after initialization:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
immortal.addPlugin(DatadogPlugin);
|
|
210
|
+
// → onInit is called immediately
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Plugin execution order
|
|
216
|
+
|
|
217
|
+
Plugins are initialized in **array order** during `initialize()`, and shut down in **reverse order** during `shutdown()`. Place lower-level plugins (logging, tracing) before higher-level ones (alerting, metrics export).
|