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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +577 -0
  3. package/docs/CHANGELOG.md +21 -0
  4. package/docs/CONTRIBUTING.md +41 -0
  5. package/docs/api/chaos.md +179 -0
  6. package/docs/api/dashboard.md +109 -0
  7. package/docs/api/isolation.md +191 -0
  8. package/docs/api/lifecycle.md +187 -0
  9. package/docs/api/monitoring.md +313 -0
  10. package/docs/api/plugins.md +217 -0
  11. package/docs/api/recovery.md +267 -0
  12. package/docs/api/safe-zone.md +236 -0
  13. package/docs/api/supervision.md +285 -0
  14. package/docs/guides/configuration.md +168 -0
  15. package/docs/guides/express.md +171 -0
  16. package/docs/guides/fastify.md +188 -0
  17. package/docs/guides/koa.md +102 -0
  18. package/docs/guides/nestjs.md +182 -0
  19. package/docs/guides/testing.md +91 -0
  20. package/examples/express-basic/index.ts +462 -0
  21. package/examples/express-basic/package.json +21 -0
  22. package/examples/express-basic/tsconfig.json +24 -0
  23. package/examples/fastify-microservice/index.ts +342 -0
  24. package/examples/fastify-microservice/package.json +22 -0
  25. package/examples/fastify-microservice/tsconfig.json +24 -0
  26. package/examples/invoice-service/data/invoices.db +0 -0
  27. package/examples/invoice-service/data/invoices.db-shm +0 -0
  28. package/examples/invoice-service/data/invoices.db-wal +0 -0
  29. package/examples/invoice-service/package.json +25 -0
  30. package/examples/invoice-service/public/index.html +5025 -0
  31. package/examples/invoice-service/src/db.ts +608 -0
  32. package/examples/invoice-service/src/pdf.ts +358 -0
  33. package/examples/invoice-service/src/server.ts +527 -0
  34. package/examples/invoice-service/src/store.ts +159 -0
  35. package/examples/invoice-service/src/types.ts +193 -0
  36. package/examples/invoice-service/tsconfig.json +23 -0
  37. package/examples/nestjs-enterprise/app.module.ts +561 -0
  38. package/examples/nestjs-enterprise/main.ts +67 -0
  39. package/examples/nestjs-enterprise/package.json +26 -0
  40. package/examples/nestjs-enterprise/tsconfig.json +27 -0
  41. package/immortal-js-1.0.0.tgz +0 -0
  42. package/package.json +33 -0
  43. package/packages/adapter-express/package.json +34 -0
  44. package/packages/adapter-express/src/index.ts +349 -0
  45. package/packages/adapter-express/tsconfig.json +14 -0
  46. package/packages/adapter-fastify/package.json +56 -0
  47. package/packages/adapter-fastify/src/plugin.ts +226 -0
  48. package/packages/adapter-fastify/tsconfig.json +14 -0
  49. package/packages/adapter-koa/package.json +55 -0
  50. package/packages/adapter-koa/src/index.ts +207 -0
  51. package/packages/adapter-koa/tsconfig.json +14 -0
  52. package/packages/adapter-nestjs/package.json +61 -0
  53. package/packages/adapter-nestjs/src/immortal.module.ts +313 -0
  54. package/packages/adapter-nestjs/src/index.ts +14 -0
  55. package/packages/adapter-nestjs/tsconfig.json +16 -0
  56. package/packages/core/package.json +56 -0
  57. package/packages/core/src/chaos/ChaosEngine.ts +249 -0
  58. package/packages/core/src/config/defaults.ts +200 -0
  59. package/packages/core/src/config/schema.ts +199 -0
  60. package/packages/core/src/event-bus.ts +168 -0
  61. package/packages/core/src/index.ts +164 -0
  62. package/packages/core/src/isolation/BulkheadPool.ts +279 -0
  63. package/packages/core/src/isolation/WorkerSandbox.ts +306 -0
  64. package/packages/core/src/isolation/index.ts +8 -0
  65. package/packages/core/src/lifecycle/GracefulShutdown.ts +161 -0
  66. package/packages/core/src/logger.ts +104 -0
  67. package/packages/core/src/monitoring/DiagnosticsChannel.ts +248 -0
  68. package/packages/core/src/monitoring/HealthMonitor.ts +191 -0
  69. package/packages/core/src/monitoring/MemoryLeakGuard.ts +340 -0
  70. package/packages/core/src/monitoring/MetricsCollector.ts +219 -0
  71. package/packages/core/src/monitoring/index.ts +10 -0
  72. package/packages/core/src/plugins/BuiltinPlugins.ts +269 -0
  73. package/packages/core/src/recovery/CircuitBreaker.ts +334 -0
  74. package/packages/core/src/recovery/FallbackCache.ts +328 -0
  75. package/packages/core/src/recovery/RetryEngine.ts +225 -0
  76. package/packages/core/src/recovery/Timeout.ts +97 -0
  77. package/packages/core/src/recovery/index.ts +11 -0
  78. package/packages/core/src/runtime.ts +242 -0
  79. package/packages/core/src/safe-zone/AsyncBoundary.ts +114 -0
  80. package/packages/core/src/safe-zone/ErrorTrap.ts +347 -0
  81. package/packages/core/src/safe-zone/SafeWrapper.ts +317 -0
  82. package/packages/core/src/safe-zone/index.ts +23 -0
  83. package/packages/core/src/supervision/ClusterManager.ts +243 -0
  84. package/packages/core/src/supervision/RestartStrategy.ts +68 -0
  85. package/packages/core/src/supervision/Supervisor.ts +311 -0
  86. package/packages/core/src/supervision/index.ts +11 -0
  87. package/packages/core/src/types.ts +470 -0
  88. package/packages/core/test/bulkhead.test.ts +310 -0
  89. package/packages/core/test/circuit-breaker.test.ts +153 -0
  90. package/packages/core/test/memory-guard.test.ts +213 -0
  91. package/packages/core/test/retry.test.ts +110 -0
  92. package/packages/core/test/safe-zone.test.ts +271 -0
  93. package/packages/core/test/supervisor.test.ts +310 -0
  94. package/packages/core/tsconfig.json +13 -0
  95. package/packages/dashboard/package.json +56 -0
  96. package/packages/dashboard/server/DashboardServer.ts +454 -0
  97. package/packages/dashboard/tsconfig.json +14 -0
  98. 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).