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,179 @@
1
+ # API Reference — Chaos Engineering
2
+
3
+ Immortal.js ships a built-in chaos engine for fault injection in staging and pre-production environments. It is **permanently disabled in `NODE_ENV=production`**.
4
+
5
+ ---
6
+
7
+ ## ChaosEngine
8
+
9
+ ### `new ChaosEngine()`
10
+
11
+ ```typescript
12
+ import { ChaosEngine } from '@immortal/core';
13
+
14
+ const chaos = new ChaosEngine();
15
+ ```
16
+
17
+ ---
18
+
19
+ ### `chaos.addFault(config)`
20
+
21
+ Register a fault definition.
22
+
23
+ ```typescript
24
+ chaos.addFault({
25
+ type: 'latency',
26
+ probability: 0.1,
27
+ minMs: 100,
28
+ maxMs: 2_000,
29
+ targetRoutes: ['/api/orders', '/api/payments'],
30
+ });
31
+ ```
32
+
33
+ **Fault types:**
34
+
35
+ #### `type: 'latency'`
36
+
37
+ Inject random delay before calling the real handler.
38
+
39
+ ```typescript
40
+ interface LatencyOptions {
41
+ type: 'latency';
42
+ probability: number; // 0.0–1.0
43
+ minMs: number;
44
+ maxMs: number;
45
+ targetRoutes?: string[]; // if omitted, applies to all routes
46
+ }
47
+ ```
48
+
49
+ #### `type: 'error'`
50
+
51
+ Throw a simulated error instead of calling the handler.
52
+
53
+ ```typescript
54
+ interface ErrorOptions {
55
+ type: 'error';
56
+ probability: number;
57
+ errorMessage?: string; // default: 'Chaos: injected error'
58
+ statusCode?: number;
59
+ targetRoutes?: string[];
60
+ }
61
+ ```
62
+
63
+ #### `type: 'worker-kill'`
64
+
65
+ Simulate a worker process crash by throwing an uncaught error.
66
+
67
+ ```typescript
68
+ interface WorkerKillOptions {
69
+ type: 'worker-kill';
70
+ probability: number;
71
+ }
72
+ ```
73
+
74
+ ---
75
+
76
+ ### `chaos.removeFault(index)`
77
+
78
+ Remove a registered fault by index.
79
+
80
+ ```typescript
81
+ const idx = chaos.addFault(config);
82
+ chaos.removeFault(idx);
83
+ ```
84
+
85
+ ### `chaos.clearFaults()`
86
+
87
+ Remove all registered faults.
88
+
89
+ ```typescript
90
+ chaos.clearFaults();
91
+ ```
92
+
93
+ ### `chaos.enable()` / `chaos.disable()`
94
+
95
+ Toggle the engine. When disabled, `wrapHandler` passes through directly.
96
+
97
+ ```typescript
98
+ chaos.enable();
99
+ chaos.disable();
100
+
101
+ const isEnabled = chaos.isEnabled();
102
+ ```
103
+
104
+ ---
105
+
106
+ ### `chaos.wrapHandler(fn, route?)`
107
+
108
+ Wrap a route handler function with chaos injection.
109
+
110
+ ```typescript
111
+ const chaosHandler = chaos.wrapHandler(originalHandler, '/api/orders');
112
+
113
+ // Express / Fastify:
114
+ app.get('/api/orders', chaosHandler);
115
+ ```
116
+
117
+ The wrapped handler probabilistically applies registered faults before calling through to `fn`.
118
+
119
+ ---
120
+
121
+ ### `chaos.injectLatency(route?)`
122
+
123
+ Directly inject latency (for use outside of handler wrapping).
124
+
125
+ ```typescript
126
+ await chaos.injectLatency('/api/orders');
127
+ // Resolves after delay (or immediately if no latency fault matches)
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Events
133
+
134
+ | Event | Trigger |
135
+ |-------|---------|
136
+ | `chaos:latency-injected` | Latency fault was applied |
137
+ | `chaos:error-injected` | Error fault was applied |
138
+ | `chaos:worker-killed` | Worker-kill fault was applied |
139
+
140
+ ---
141
+
142
+ ## Safety rules
143
+
144
+ 1. **Production guard**: Attempting to `enable()` or `addFault()` when `NODE_ENV === 'production'` logs an error and is a no-op.
145
+ 2. **Rate limiting**: The chaos engine tracks injections per-route to prevent runaway fault amplification.
146
+ 3. **Deterministic testing**: Seed the random number generator for reproducible test scenarios.
147
+
148
+ ---
149
+
150
+ ## Integration example
151
+
152
+ ```typescript
153
+ import { ChaosEngine } from '@immortal/core';
154
+ import Fastify from 'fastify';
155
+
156
+ const chaos = new ChaosEngine();
157
+
158
+ if (process.env.CHAOS_ENABLED === 'true') {
159
+ chaos.enable();
160
+ chaos.addFault({
161
+ type: 'latency',
162
+ probability: parseFloat(process.env.CHAOS_LATENCY_PROB ?? '0.05'),
163
+ minMs: 100,
164
+ maxMs: 3_000,
165
+ });
166
+ chaos.addFault({
167
+ type: 'error',
168
+ probability: parseFloat(process.env.CHAOS_ERROR_PROB ?? '0.02'),
169
+ statusCode: 503,
170
+ });
171
+ }
172
+
173
+ const app = Fastify();
174
+
175
+ app.get('/api/products', chaos.wrapHandler(async (req, reply) => {
176
+ const products = await db.products.findAll();
177
+ reply.send(products);
178
+ }));
179
+ ```
@@ -0,0 +1,109 @@
1
+ # API Reference — Dashboard (`@immortal/dashboard`)
2
+
3
+ The `@immortal/dashboard` package provides a real-time web dashboard for visualizing the health, metrics, and event stream of your Immortal.js runtime.
4
+
5
+ ---
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @immortal/dashboard
11
+ ```
12
+
13
+ Optional dependency for WebSocket push:
14
+
15
+ ```bash
16
+ npm install ws
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Usage
22
+
23
+ ```typescript
24
+ import { createDashboard } from '@immortal/dashboard';
25
+ import { getImmortal } from '@immortal/core';
26
+
27
+ const dashboard = createDashboard({
28
+ port: 9090,
29
+ host: '0.0.0.0',
30
+ refreshIntervalMs: 2_000,
31
+ auth: {
32
+ username: 'admin',
33
+ password: process.env.DASHBOARD_PASSWORD!,
34
+ },
35
+ });
36
+
37
+ await dashboard.start();
38
+ console.log('Dashboard running at http://localhost:9090');
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Configuration
44
+
45
+ ```typescript
46
+ interface DashboardConfig {
47
+ port?: number; // default: 9090
48
+ host?: string; // default: '127.0.0.1'
49
+ refreshIntervalMs?: number; // default: 5_000
50
+ auth?: {
51
+ username: string;
52
+ password: string;
53
+ };
54
+ cors?: {
55
+ origin: string | string[];
56
+ };
57
+ websocket?: boolean; // enable WS push (requires 'ws' package)
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Dashboard pages
64
+
65
+ | Route | Description |
66
+ |-------|-------------|
67
+ | `/` | Overview — health, key metrics, recent events |
68
+ | `/metrics` | Full system metrics with time-series charts |
69
+ | `/circuits` | Circuit breaker states and history |
70
+ | `/bulkheads` | Bulkhead pool saturation |
71
+ | `/workers` | Supervisor worker states and restart history |
72
+ | `/events` | Live event stream (WebSocket if enabled) |
73
+
74
+ ---
75
+
76
+ ## REST API
77
+
78
+ The dashboard exposes a JSON API:
79
+
80
+ ```
81
+ GET /api/health → { status, timestamp, metrics }
82
+ GET /api/metrics → SystemMetrics
83
+ GET /api/circuits → CircuitBreakerStatus[]
84
+ GET /api/bulkheads → BulkheadStatus[]
85
+ GET /api/workers → WorkerStatus[]
86
+ GET /api/events → ImmortalEvent[] (last 1000)
87
+ ```
88
+
89
+ ---
90
+
91
+ ## WebSocket stream
92
+
93
+ When `websocket: true`, events are pushed in real time:
94
+
95
+ ```javascript
96
+ const ws = new WebSocket('ws://localhost:9090/ws');
97
+ ws.onmessage = (msg) => {
98
+ const event = JSON.parse(msg.data);
99
+ console.log(event.type, event.data);
100
+ };
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Security
106
+
107
+ - The dashboard is **not recommended for public exposure**. Bind to `127.0.0.1` and use a reverse proxy with TLS.
108
+ - Basic auth credentials are verified on every request.
109
+ - In production, prefer exporting metrics to Prometheus/Datadog and using their dashboards instead.
@@ -0,0 +1,191 @@
1
+ # API Reference — Isolation (Layer 3)
2
+
3
+ The **Isolation** layer prevents a misbehaving dependency or compute-heavy operation from consuming all available resources. It uses two mechanisms: **bulkheads** (concurrency limits) and **worker sandboxes** (thread isolation).
4
+
5
+ ---
6
+
7
+ ## BulkheadPool
8
+
9
+ A bulkhead limits the number of concurrent operations to a named pool, with an optional overflow queue.
10
+
11
+ ### `new BulkheadPool(name, config?)`
12
+
13
+ ```typescript
14
+ import { BulkheadPool } from '@immortal/core';
15
+
16
+ const dbPool = new BulkheadPool('database', {
17
+ maxConcurrent: 10,
18
+ maxQueueSize: 50,
19
+ defaultTimeoutMs: 5_000,
20
+ });
21
+ ```
22
+
23
+ **Config:**
24
+
25
+ ```typescript
26
+ interface BulkheadConfig {
27
+ maxConcurrent?: number; // default: 10
28
+ maxQueueSize?: number; // default: 100
29
+ defaultTimeoutMs?: number; // default: 30_000
30
+ }
31
+ ```
32
+
33
+ ### `pool.run(task, priority?, timeoutMs?)`
34
+
35
+ Execute a task within the bulkhead.
36
+
37
+ ```typescript
38
+ const result = await pool.run(
39
+ () => db.query('SELECT * FROM users'),
40
+ 'high', // 'high' | 'medium' | 'low'
41
+ 3_000 // per-call timeout override
42
+ );
43
+ ```
44
+
45
+ - If `active >= maxConcurrent` and `queued < maxQueueSize` → task is queued
46
+ - If queue is also full → throws `BulkheadRejectedError` immediately
47
+ - Priority ordering: `high` > `medium` > `low`
48
+
49
+ **Throws:** `BulkheadRejectedError` when the pool is saturated.
50
+
51
+ ### `pool.getStatus()`
52
+
53
+ Returns the current pool snapshot:
54
+
55
+ ```typescript
56
+ interface BulkheadStatus {
57
+ name: string;
58
+ active: number;
59
+ queued: number;
60
+ maxConcurrent: number;
61
+ maxQueueSize: number;
62
+ rejected: number;
63
+ totalExecuted: number;
64
+ }
65
+ ```
66
+
67
+ ### `pool.drainAll()`
68
+
69
+ Waits for all currently active and queued tasks to complete. Useful during graceful shutdown.
70
+
71
+ ```typescript
72
+ await pool.drainAll();
73
+ ```
74
+
75
+ ### Events
76
+
77
+ | Event | Trigger |
78
+ |-------|---------|
79
+ | `bulkhead:queued` | Task enters the overflow queue |
80
+ | `bulkhead:rejected` | Task rejected (pool + queue full) |
81
+ | `bulkhead:timeout` | Task exceeds its timeout while queued |
82
+
83
+ ### `BulkheadRejectedError`
84
+
85
+ Thrown when the pool is full. Extends `ImmortalError` with `kind: 'operational'`.
86
+
87
+ ```typescript
88
+ import { BulkheadRejectedError } from '@immortal/core';
89
+
90
+ try {
91
+ await pool.run(task);
92
+ } catch (err) {
93
+ if (err instanceof BulkheadRejectedError) {
94
+ return res.status(503).json({ error: 'Service overloaded, try again later' });
95
+ }
96
+ throw err;
97
+ }
98
+ ```
99
+
100
+ ---
101
+
102
+ ## BulkheadRegistry
103
+
104
+ Singleton registry for named bulkhead pools.
105
+
106
+ ```typescript
107
+ import { BulkheadRegistry } from '@immortal/core';
108
+
109
+ const registry = BulkheadRegistry.getInstance();
110
+
111
+ // Get or create a named pool
112
+ const pool = registry.get('database', { maxConcurrent: 5 });
113
+
114
+ // Iterate all pools
115
+ for (const [name, pool] of registry.entries()) {
116
+ console.log(name, pool.getStatus());
117
+ }
118
+ ```
119
+
120
+ ---
121
+
122
+ ## WorkerSandbox
123
+
124
+ Executes untrusted or CPU-intensive code in an isolated Node.js worker thread.
125
+
126
+ ### `new WorkerSandbox(config?)`
127
+
128
+ ```typescript
129
+ import { WorkerSandbox } from '@immortal/core';
130
+
131
+ const sandbox = new WorkerSandbox({
132
+ timeoutMs: 5_000,
133
+ memoryLimitMb: 128,
134
+ });
135
+ ```
136
+
137
+ **Config:**
138
+
139
+ ```typescript
140
+ interface WorkerSandboxConfig {
141
+ timeoutMs?: number; // default: 10_000
142
+ memoryLimitMb?: number; // default: 256
143
+ maxWorkers?: number; // default: os.cpus().length
144
+ }
145
+ ```
146
+
147
+ ### `sandbox.execute(code, args?)`
148
+
149
+ Execute a string of JavaScript code in an isolated worker thread. The code must `return` a value.
150
+
151
+ ```typescript
152
+ const result = await sandbox.execute(
153
+ `return input.a + input.b`,
154
+ { input: { a: 10, b: 32 } }
155
+ );
156
+ // → 42
157
+ ```
158
+
159
+ - `args` are serialized and passed to the worker via `structuredClone`
160
+ - The worker terminates after `timeoutMs` milliseconds
161
+ - Memory is garbage-collected after the task completes
162
+
163
+ **Throws:** `ImmortalError` with `code: 'WORKER_TIMEOUT'` or `code: 'WORKER_ERROR'`.
164
+
165
+ ### `sandbox.terminate()`
166
+
167
+ Forcefully terminates all worker threads in the pool.
168
+
169
+ ---
170
+
171
+ ## Design Notes
172
+
173
+ ### Bulkhead sizing
174
+
175
+ A common mistake is setting `maxConcurrent` too high. The optimal value depends on your I/O concurrency model:
176
+
177
+ - **Database connections:** Match your connection pool size (e.g. 10–20)
178
+ - **HTTP calls to external services:** Match the service's rate limit / connection limit
179
+ - **CPU work (Worker threads):** Match `os.cpus().length`
180
+
181
+ ### Queue sizing
182
+
183
+ `maxQueueSize` is a safety valve. A large queue adds latency — callers wait longer before failing. A size of 0 means "reject immediately when all slots are taken." Choose based on your SLA:
184
+
185
+ ```
186
+ queue_wait_time ≈ (maxQueueSize / maxConcurrent) × avg_task_duration
187
+ ```
188
+
189
+ ### Priority queuing
190
+
191
+ Priority queuing (`'high' | 'medium' | 'low'`) ensures critical paths are served first during overload. Use sparingly — abusing `'high'` starves lower priorities.
@@ -0,0 +1,187 @@
1
+ # API Reference — Lifecycle
2
+
3
+ Lifecycle management ensures your service starts cleanly, shuts down gracefully, and drains in-flight requests before exiting.
4
+
5
+ ---
6
+
7
+ ## GracefulShutdown
8
+
9
+ ### `new GracefulShutdown(config, bus)`
10
+
11
+ ```typescript
12
+ import { GracefulShutdown } from '@immortal/core';
13
+
14
+ const shutdown = new GracefulShutdown(
15
+ {
16
+ enabled: true,
17
+ timeoutMs: 30_000,
18
+ signals: ['SIGTERM', 'SIGINT'],
19
+ },
20
+ getEventBus()
21
+ );
22
+ ```
23
+
24
+ **Config:**
25
+
26
+ ```typescript
27
+ interface GracefulShutdownConfig {
28
+ enabled?: boolean;
29
+ timeoutMs?: number; // default: 30_000
30
+ signals?: NodeJS.Signals[]; // default: ['SIGTERM', 'SIGINT']
31
+ forceExitAfterMs?: number; // default: timeoutMs + 5_000
32
+ }
33
+ ```
34
+
35
+ ---
36
+
37
+ ### `shutdown.addHandler(name, fn, priority?)`
38
+
39
+ Register a shutdown hook. Handlers are called in **descending priority order** (highest first).
40
+
41
+ ```typescript
42
+ shutdown.addHandler('http-server', async () => {
43
+ await server.close();
44
+ }, 100);
45
+
46
+ shutdown.addHandler('database', async () => {
47
+ await db.end();
48
+ }, 50);
49
+
50
+ shutdown.addHandler('cache', async () => {
51
+ await redis.quit();
52
+ }, 10);
53
+ ```
54
+
55
+ **Parameters:**
56
+ - `name: string` — identifier for logging
57
+ - `fn: () => Promise<void>` — async cleanup function
58
+ - `priority?: number` — default `0`, higher runs first
59
+
60
+ ---
61
+
62
+ ### `shutdown.execute(signal?)`
63
+
64
+ Trigger the shutdown sequence programmatically.
65
+
66
+ ```typescript
67
+ await shutdown.execute('SIGTERM');
68
+ ```
69
+
70
+ 1. Emits `shutdown:initiated` on the event bus
71
+ 2. Calls all registered handlers in priority order
72
+ 3. Waits up to `timeoutMs` for each handler
73
+ 4. Emits `shutdown:complete` (or `shutdown:forced` on timeout)
74
+
75
+ ---
76
+
77
+ ### Automatic signal handling
78
+
79
+ When `enabled: true` and `signals` are configured, the shutdown sequence runs automatically on signal receipt:
80
+
81
+ ```typescript
82
+ // Registered automatically — no extra code needed
83
+ process.on('SIGTERM', () => shutdown.execute('SIGTERM'));
84
+ process.on('SIGINT', () => shutdown.execute('SIGINT'));
85
+ ```
86
+
87
+ ---
88
+
89
+ ## createImmortal
90
+
91
+ High-level factory that wires all five layers together.
92
+
93
+ ```typescript
94
+ import { createImmortal } from '@immortal/core';
95
+
96
+ const immortal = await createImmortal({
97
+ logger: { level: 'info', transport: 'json' },
98
+ healthMonitor: { enabled: true, checkIntervalMs: 30_000 },
99
+ memoryGuard: { enabled: true, warningThresholdMb: 400 },
100
+ gracefulShutdown: { enabled: true, timeoutMs: 30_000 },
101
+ plugins: [ConsoleLogPlugin, RequestTracingPlugin],
102
+ });
103
+ ```
104
+
105
+ Returns an `ImmortalRuntime` instance with all layers initialized.
106
+
107
+ ---
108
+
109
+ ## ImmortalRuntime
110
+
111
+ ### `runtime.config`
112
+
113
+ The fully-resolved (merged with defaults) configuration object.
114
+
115
+ ### `runtime.getMetrics()`
116
+
117
+ Returns the current `SystemMetrics` snapshot.
118
+
119
+ ### `runtime.isHealthy()`
120
+
121
+ Returns `true` if no critical health events have occurred.
122
+
123
+ ### `runtime.addPlugin(plugin)`
124
+
125
+ Attach a plugin after initialization. `onInit` is called immediately.
126
+
127
+ ```typescript
128
+ runtime.addPlugin(createSlackAlertPlugin({ webhookUrl: '...' }));
129
+ ```
130
+
131
+ ### `runtime.shutdown(signal?)`
132
+
133
+ Trigger graceful shutdown.
134
+
135
+ ```typescript
136
+ await runtime.shutdown('SIGTERM');
137
+ ```
138
+
139
+ ---
140
+
141
+ ## getImmortal
142
+
143
+ Returns the singleton `ImmortalRuntime`. Throws if `createImmortal()` has not been called.
144
+
145
+ ```typescript
146
+ import { getImmortal } from '@immortal/core';
147
+
148
+ const runtime = getImmortal();
149
+ const metrics = runtime.getMetrics();
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Typical startup pattern
155
+
156
+ ```typescript
157
+ import { createImmortal, ConsoleLogPlugin } from '@immortal/core';
158
+ import { createServer } from './server.js';
159
+
160
+ async function main() {
161
+ const immortal = await createImmortal({
162
+ logger: { level: process.env.LOG_LEVEL ?? 'info' },
163
+ gracefulShutdown: {
164
+ enabled: true,
165
+ timeoutMs: 30_000,
166
+ signals: ['SIGTERM', 'SIGINT'],
167
+ },
168
+ plugins: [ConsoleLogPlugin],
169
+ });
170
+
171
+ const server = await createServer();
172
+
173
+ // Register shutdown hooks
174
+ immortal.config.gracefulShutdown; // already wired via createImmortal
175
+ // Add app-specific hooks via the GracefulShutdown instance:
176
+ const { GracefulShutdown } = await import('@immortal/core');
177
+ // (or construct separately and pass via plugin)
178
+
179
+ await server.listen({ port: 3000 });
180
+ console.log('Server running on port 3000');
181
+ }
182
+
183
+ main().catch((err) => {
184
+ console.error('Fatal startup error:', err);
185
+ process.exit(1);
186
+ });
187
+ ```