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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Immortal.js Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
# Immortal.js
|
|
2
|
+
|
|
3
|
+
> **Production-grade resilience framework for Node.js** — five composable layers that make your services impossible to kill.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@immortal/core)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](#testing)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What is Immortal.js?
|
|
13
|
+
|
|
14
|
+
Immortal.js is a **monorepo** of battle-tested resilience primitives for Node.js microservices. It implements a strict five-layer architecture:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
┌──────────────────────────────────────────────────────┐
|
|
18
|
+
│ Layer 5 · Observability (Metrics, Health, Logs) │
|
|
19
|
+
├──────────────────────────────────────────────────────┤
|
|
20
|
+
│ Layer 4 · Supervision (Cluster, Process restart) │
|
|
21
|
+
├──────────────────────────────────────────────────────┤
|
|
22
|
+
│ Layer 3 · Isolation (Bulkhead, Worker threads) │
|
|
23
|
+
├──────────────────────────────────────────────────────┤
|
|
24
|
+
│ Layer 2 · Recovery (Retry, Circuit Breaker) │
|
|
25
|
+
├──────────────────────────────────────────────────────┤
|
|
26
|
+
│ Layer 1 · Containment (Safe Zone, Error Trap) │
|
|
27
|
+
└──────────────────────────────────────────────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Each layer is independently usable; together they provide defense-in-depth against every class of production failure.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Packages
|
|
35
|
+
|
|
36
|
+
| Package | Description | Version |
|
|
37
|
+
|---------|-------------|---------|
|
|
38
|
+
| [`@immortal/core`](./packages/core) | Core resilience primitives | `1.0.0` |
|
|
39
|
+
| [`@immortal/fastify`](./packages/adapter-fastify) | Fastify integration plugin | `1.0.0` |
|
|
40
|
+
| [`@immortal/nestjs`](./packages/adapter-nestjs) | NestJS module + decorators | `1.0.0` |
|
|
41
|
+
| [`@immortal/koa`](./packages/adapter-koa) | Koa middleware | `1.0.0` |
|
|
42
|
+
| [`@immortal/dashboard`](./packages/dashboard) | Real-time health dashboard | `1.0.0` |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install @immortal/core
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { createImmortal, withRetry, CircuitBreaker } from '@immortal/core';
|
|
54
|
+
|
|
55
|
+
// 1. Initialize the runtime
|
|
56
|
+
const immortal = await createImmortal({
|
|
57
|
+
logger: { level: 'info' },
|
|
58
|
+
healthMonitor: { enabled: true },
|
|
59
|
+
gracefulShutdown: { enabled: true, timeoutMs: 30_000 },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 2. Add a circuit breaker around any I/O call
|
|
63
|
+
const breaker = new CircuitBreaker('payment-service', {
|
|
64
|
+
threshold: 5,
|
|
65
|
+
timeout: 60_000,
|
|
66
|
+
halfOpenRequests: 2,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const result = await breaker.run(async () => {
|
|
70
|
+
return await paymentApi.charge(amount);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 3. Retry with exponential backoff
|
|
74
|
+
const data = await withRetry(
|
|
75
|
+
() => fetch('https://api.example.com/data'),
|
|
76
|
+
{ maxAttempts: 3, baseDelayMs: 200, backoffFactor: 2 }
|
|
77
|
+
);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
### npm workspaces (monorepo)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git clone https://github.com/Brah-Timo/immortal-js.git
|
|
88
|
+
cd immortal-js
|
|
89
|
+
npm install
|
|
90
|
+
npm test
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Single package
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm install @immortal/core
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### With framework adapter
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Fastify
|
|
103
|
+
npm install @immortal/core @immortal/fastify fastify
|
|
104
|
+
|
|
105
|
+
# NestJS
|
|
106
|
+
npm install @immortal/core @immortal/nestjs @nestjs/core @nestjs/common reflect-metadata
|
|
107
|
+
|
|
108
|
+
# Koa
|
|
109
|
+
npm install @immortal/core @immortal/koa koa
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Core API
|
|
115
|
+
|
|
116
|
+
### Layer 1 — Containment (Safe Zone)
|
|
117
|
+
|
|
118
|
+
Catches all unhandled errors at process and request boundaries.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { AsyncBoundary, asyncBoundary, ErrorTrap, classifyError } from '@immortal/core';
|
|
122
|
+
|
|
123
|
+
// Classify errors for routing
|
|
124
|
+
const kind = classifyError(error);
|
|
125
|
+
// → 'operational' | 'programming' | 'transient' | 'external' | 'unknown'
|
|
126
|
+
|
|
127
|
+
// Wrap a handler with automatic boundary context
|
|
128
|
+
const safeFn = asyncBoundary(myHandler, { fallback: () => defaultResponse });
|
|
129
|
+
|
|
130
|
+
// Static boundary methods
|
|
131
|
+
AsyncBoundary.run(async () => {
|
|
132
|
+
const ctx = AsyncBoundary.getContext(); // trace ID, request ID, etc.
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Global error trap (uncaught exceptions + unhandled rejections)
|
|
136
|
+
const trap = new ErrorTrap(bus);
|
|
137
|
+
trap.install();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Layer 2 — Recovery
|
|
141
|
+
|
|
142
|
+
**Retry Engine** with exponential backoff and jitter:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { withRetry, withTimeout, calculateBackoff } from '@immortal/core';
|
|
146
|
+
|
|
147
|
+
const result = await withRetry(operation, {
|
|
148
|
+
maxAttempts: 5,
|
|
149
|
+
baseDelayMs: 100,
|
|
150
|
+
backoffFactor: 2,
|
|
151
|
+
jitter: true,
|
|
152
|
+
retryOn: (err) => err.code === 'ECONNRESET',
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const bounded = await withTimeout(operation, 5_000, 'payment-charge');
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Circuit Breaker** with half-open probing:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { CircuitBreaker, CircuitBreakerRegistry } from '@immortal/core';
|
|
162
|
+
|
|
163
|
+
const cb = new CircuitBreaker('db', {
|
|
164
|
+
threshold: 5, // failures before opening
|
|
165
|
+
timeout: 30_000, // ms before trying half-open
|
|
166
|
+
halfOpenRequests: 3,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// States: 'closed' | 'open' | 'half-open'
|
|
170
|
+
const state = cb.getCurrentState();
|
|
171
|
+
|
|
172
|
+
// Force state (testing)
|
|
173
|
+
cb.forceState('open');
|
|
174
|
+
|
|
175
|
+
// Registry for multi-service management
|
|
176
|
+
const registry = CircuitBreakerRegistry.getInstance();
|
|
177
|
+
registry.get('db').getCurrentState();
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Fallback Cache** — serve stale on failure:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { RouteFallbackCache } from '@immortal/core';
|
|
184
|
+
|
|
185
|
+
const cache = new RouteFallbackCache({ maxEntries: 500, ttlMs: 60_000 });
|
|
186
|
+
cache.set('GET /products/42', productData, { ttlMs: 300_000 });
|
|
187
|
+
|
|
188
|
+
const stale = cache.get('GET /products/42');
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Layer 3 — Isolation
|
|
192
|
+
|
|
193
|
+
**Bulkhead Pool** — limit concurrency per service:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { BulkheadPool, BulkheadRegistry } from '@immortal/core';
|
|
197
|
+
|
|
198
|
+
const pool = new BulkheadPool('database', {
|
|
199
|
+
maxConcurrent: 10,
|
|
200
|
+
maxQueueSize: 50,
|
|
201
|
+
defaultTimeoutMs: 5_000,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const result = await pool.run(async () => db.query(sql), 'high', 3_000);
|
|
205
|
+
|
|
206
|
+
const status = pool.getStatus();
|
|
207
|
+
// → { name, active, queued, maxConcurrent, maxQueueSize, rejected, totalExecuted }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Worker Sandbox** — run untrusted code in a thread:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { WorkerSandbox } from '@immortal/core';
|
|
214
|
+
|
|
215
|
+
const sandbox = new WorkerSandbox({ timeoutMs: 5_000, memoryLimitMb: 64 });
|
|
216
|
+
const result = await sandbox.execute(`return input * 2`, { input: 21 });
|
|
217
|
+
// → 42
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Layer 4 — Supervision
|
|
221
|
+
|
|
222
|
+
**Supervisor** — manages named async processes with automatic restart:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { Supervisor } from '@immortal/core';
|
|
226
|
+
|
|
227
|
+
const supervisor = new Supervisor({
|
|
228
|
+
config: { maxRestartsInWindow: 5, windowMs: 60_000, baseRestartDelayMs: 1_000 },
|
|
229
|
+
onEscalation: (id) => alertTeam(`Worker ${id} is broken!`),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
supervisor.register('http-server', {
|
|
233
|
+
spawnFn: () => startHttpServer(),
|
|
234
|
+
terminate: (instance) => instance.close(),
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Get all worker states
|
|
238
|
+
const statuses = supervisor.getWorkerStatuses();
|
|
239
|
+
// → [{ id, state, restartCount, uptime, pid?, lastRestartTime? }]
|
|
240
|
+
|
|
241
|
+
await supervisor.stop('http-server');
|
|
242
|
+
await supervisor.stopAll();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Cluster Manager** — multi-core process management:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { ClusterManager } from '@immortal/core';
|
|
249
|
+
|
|
250
|
+
if (ClusterManager.isPrimary()) {
|
|
251
|
+
const mgr = new ClusterManager({
|
|
252
|
+
workerCount: 4,
|
|
253
|
+
onEscalation: (id) => console.error(`Worker ${id} escalated`),
|
|
254
|
+
});
|
|
255
|
+
await mgr.start();
|
|
256
|
+
// Zero-downtime rolling restart
|
|
257
|
+
await mgr.rollingRestart();
|
|
258
|
+
} else {
|
|
259
|
+
await startApp();
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Layer 5 — Observability
|
|
264
|
+
|
|
265
|
+
**Metrics Collector** — system metrics snapshot:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { MetricsCollector } from '@immortal/core';
|
|
269
|
+
|
|
270
|
+
const collector = new MetricsCollector();
|
|
271
|
+
const snapshot = collector.getSnapshot();
|
|
272
|
+
// → { timestamp, eventLoopLag, memory, cpu, handles, requests, circuits, bulkheads, workers }
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Health Monitor** — periodic health checks:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { HealthMonitor } from '@immortal/core';
|
|
279
|
+
|
|
280
|
+
const monitor = new HealthMonitor(
|
|
281
|
+
{ checkIntervalMs: 30_000, eventLoopLagCriticalMs: 100 },
|
|
282
|
+
bus,
|
|
283
|
+
metricsCollector
|
|
284
|
+
);
|
|
285
|
+
monitor.start();
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Memory Leak Guard** — detect and react to memory growth:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { MemoryLeakGuard } from '@immortal/core';
|
|
292
|
+
|
|
293
|
+
const guard = new MemoryLeakGuard({
|
|
294
|
+
checkIntervalMs: 30_000,
|
|
295
|
+
warningThresholdMb: 400,
|
|
296
|
+
restartThresholdMb: 600,
|
|
297
|
+
growthWindowCount: 5,
|
|
298
|
+
}, bus);
|
|
299
|
+
|
|
300
|
+
guard.start();
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Diagnostics Channel** — OpenTelemetry-compatible tracing:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { DiagnosticsChannel } from '@immortal/core';
|
|
307
|
+
|
|
308
|
+
const dc = new DiagnosticsChannel({ otlpEndpoint: 'http://otel-collector:4318' });
|
|
309
|
+
dc.recordRequest({ route: '/api/users', method: 'GET', statusCode: 200, durationMs: 45 });
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Plugins
|
|
315
|
+
|
|
316
|
+
Immortal.js has a first-class plugin system:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import {
|
|
320
|
+
createImmortal,
|
|
321
|
+
ConsoleLogPlugin,
|
|
322
|
+
RequestTracingPlugin,
|
|
323
|
+
createAnomalyDetectionPlugin,
|
|
324
|
+
createSlackAlertPlugin,
|
|
325
|
+
} from '@immortal/core';
|
|
326
|
+
|
|
327
|
+
const immortal = await createImmortal({
|
|
328
|
+
plugins: [
|
|
329
|
+
ConsoleLogPlugin,
|
|
330
|
+
RequestTracingPlugin,
|
|
331
|
+
createAnomalyDetectionPlugin({ threshold: 0.1 }),
|
|
332
|
+
createSlackAlertPlugin({ webhookUrl: process.env.SLACK_WEBHOOK! }),
|
|
333
|
+
],
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Built-in plugins:**
|
|
338
|
+
|
|
339
|
+
| Plugin | Description |
|
|
340
|
+
|--------|-------------|
|
|
341
|
+
| `ConsoleLogPlugin` | Structured JSON logging to stdout |
|
|
342
|
+
| `RequestTracingPlugin` | Request ID propagation via AsyncLocalStorage |
|
|
343
|
+
| `createAnomalyDetectionPlugin` | Statistical anomaly detection on metrics |
|
|
344
|
+
| `createSlackAlertPlugin` | Slack webhook notifications for critical events |
|
|
345
|
+
|
|
346
|
+
**Custom plugins:**
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import type { ImmortalPlugin } from '@immortal/core';
|
|
350
|
+
|
|
351
|
+
const myPlugin: ImmortalPlugin = {
|
|
352
|
+
name: 'my-plugin',
|
|
353
|
+
async onInit(ctx) {
|
|
354
|
+
ctx.logger.info('Plugin initialized', { config: ctx.config });
|
|
355
|
+
},
|
|
356
|
+
async onShutdown(signal) {
|
|
357
|
+
// cleanup
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Chaos Engineering
|
|
365
|
+
|
|
366
|
+
Test your resilience in staging:
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { ChaosEngine } from '@immortal/core';
|
|
370
|
+
|
|
371
|
+
const chaos = new ChaosEngine();
|
|
372
|
+
|
|
373
|
+
// Inject latency into specific routes
|
|
374
|
+
chaos.addFault({
|
|
375
|
+
type: 'latency',
|
|
376
|
+
probability: 0.1, // 10% of requests
|
|
377
|
+
minMs: 100,
|
|
378
|
+
maxMs: 2000,
|
|
379
|
+
targetRoutes: ['/api/orders'],
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Random errors
|
|
383
|
+
chaos.addFault({
|
|
384
|
+
type: 'error',
|
|
385
|
+
probability: 0.05, // 5% error rate
|
|
386
|
+
errorMessage: 'Chaos: simulated service failure',
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Wrap a handler
|
|
390
|
+
const chaosHandler = chaos.wrapHandler(originalHandler);
|
|
391
|
+
|
|
392
|
+
// Enable/disable at runtime
|
|
393
|
+
chaos.enable();
|
|
394
|
+
chaos.disable();
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Framework Integrations
|
|
400
|
+
|
|
401
|
+
### Fastify
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
import Fastify from 'fastify';
|
|
405
|
+
import immortalPlugin from '@immortal/fastify';
|
|
406
|
+
|
|
407
|
+
const app = Fastify();
|
|
408
|
+
await app.register(immortalPlugin, {
|
|
409
|
+
circuitBreakers: true,
|
|
410
|
+
bulkheads: true,
|
|
411
|
+
metrics: true,
|
|
412
|
+
gracefulShutdown: true,
|
|
413
|
+
});
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
See [examples/fastify-microservice](./examples/fastify-microservice/index.ts) for a full example.
|
|
417
|
+
|
|
418
|
+
### NestJS
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { Module } from '@nestjs/common';
|
|
422
|
+
import { ImmortalModule } from '@immortal/nestjs';
|
|
423
|
+
|
|
424
|
+
@Module({
|
|
425
|
+
imports: [
|
|
426
|
+
ImmortalModule.forRoot({
|
|
427
|
+
logger: { level: 'info' },
|
|
428
|
+
gracefulShutdown: { enabled: true },
|
|
429
|
+
}),
|
|
430
|
+
],
|
|
431
|
+
})
|
|
432
|
+
export class AppModule {}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Decorators:**
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
import { Bulkhead, Circuit, Retry } from '@immortal/nestjs';
|
|
439
|
+
|
|
440
|
+
@Injectable()
|
|
441
|
+
export class OrdersService {
|
|
442
|
+
@Bulkhead({ pool: 'orders', maxConcurrent: 5 })
|
|
443
|
+
@Circuit({ name: 'order-db', threshold: 3 })
|
|
444
|
+
@Retry({ maxAttempts: 2 })
|
|
445
|
+
async createOrder(dto: CreateOrderDto) {
|
|
446
|
+
return this.db.orders.create(dto);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
See [examples/nestjs-enterprise](./examples/nestjs-enterprise/app.module.ts) for a full example.
|
|
452
|
+
|
|
453
|
+
### Koa
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import Koa from 'koa';
|
|
457
|
+
import { immortalMiddleware } from '@immortal/koa';
|
|
458
|
+
|
|
459
|
+
const app = new Koa();
|
|
460
|
+
app.use(immortalMiddleware({ circuitBreakers: true, metrics: true }));
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Configuration Reference
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
interface ImmortalConfig {
|
|
469
|
+
logger?: {
|
|
470
|
+
level?: 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
471
|
+
transport?: 'console' | 'json' | 'pretty';
|
|
472
|
+
prefix?: string;
|
|
473
|
+
};
|
|
474
|
+
healthMonitor?: {
|
|
475
|
+
enabled?: boolean;
|
|
476
|
+
checkIntervalMs?: number;
|
|
477
|
+
eventLoopLagWarningMs?: number;
|
|
478
|
+
eventLoopLagCriticalMs?: number;
|
|
479
|
+
heapUsageWarningPercent?: number;
|
|
480
|
+
heapUsageCriticalPercent?: number;
|
|
481
|
+
};
|
|
482
|
+
memoryGuard?: {
|
|
483
|
+
enabled?: boolean;
|
|
484
|
+
checkIntervalMs?: number;
|
|
485
|
+
warningThresholdMb?: number;
|
|
486
|
+
restartThresholdMb?: number;
|
|
487
|
+
growthWindowCount?: number;
|
|
488
|
+
adaptive?: boolean;
|
|
489
|
+
};
|
|
490
|
+
gracefulShutdown?: {
|
|
491
|
+
enabled?: boolean;
|
|
492
|
+
timeoutMs?: number;
|
|
493
|
+
signals?: NodeJS.Signals[];
|
|
494
|
+
};
|
|
495
|
+
chaos?: {
|
|
496
|
+
enabled?: boolean;
|
|
497
|
+
};
|
|
498
|
+
plugins?: ImmortalPlugin[];
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## Testing
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# All tests
|
|
508
|
+
npm test --workspaces --if-present
|
|
509
|
+
|
|
510
|
+
# Core package only
|
|
511
|
+
cd packages/core && npm test
|
|
512
|
+
|
|
513
|
+
# Watch mode
|
|
514
|
+
cd packages/core && npm test -- --watch
|
|
515
|
+
|
|
516
|
+
# Coverage
|
|
517
|
+
cd packages/core && npm test -- --coverage
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**102 tests across 6 test suites** — all passing.
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Project Structure
|
|
525
|
+
|
|
526
|
+
```
|
|
527
|
+
immortal/
|
|
528
|
+
├── packages/
|
|
529
|
+
│ ├── core/ # Main resilience library
|
|
530
|
+
│ │ ├── src/
|
|
531
|
+
│ │ │ ├── safe-zone/ # Layer 1: Containment
|
|
532
|
+
│ │ │ ├── recovery/ # Layer 2: Retry, CircuitBreaker, FallbackCache
|
|
533
|
+
│ │ │ ├── isolation/ # Layer 3: BulkheadPool, WorkerSandbox
|
|
534
|
+
│ │ │ ├── supervision/ # Layer 4: Supervisor, ClusterManager
|
|
535
|
+
│ │ │ ├── monitoring/ # Layer 5: Metrics, Health, MemoryGuard
|
|
536
|
+
│ │ │ ├── lifecycle/ # GracefulShutdown
|
|
537
|
+
│ │ │ ├── chaos/ # ChaosEngine
|
|
538
|
+
│ │ │ ├── plugins/ # Built-in plugins
|
|
539
|
+
│ │ │ ├── config/ # Defaults & validation
|
|
540
|
+
│ │ │ ├── event-bus.ts # Typed internal event bus
|
|
541
|
+
│ │ │ ├── logger.ts # Structured logger
|
|
542
|
+
│ │ │ ├── runtime.ts # ImmortalRuntime orchestrator
|
|
543
|
+
│ │ │ ├── types.ts # All TypeScript interfaces
|
|
544
|
+
│ │ │ └── index.ts # Public API surface
|
|
545
|
+
│ │ └── test/ # Vitest test suites
|
|
546
|
+
│ ├── adapter-fastify/ # Fastify plugin
|
|
547
|
+
│ ├── adapter-nestjs/ # NestJS module
|
|
548
|
+
│ ├── adapter-koa/ # Koa middleware
|
|
549
|
+
│ └── dashboard/ # Real-time dashboard
|
|
550
|
+
├── examples/
|
|
551
|
+
│ ├── fastify-microservice/ # Complete Fastify example
|
|
552
|
+
│ └── nestjs-enterprise/ # Complete NestJS example
|
|
553
|
+
├── docs/
|
|
554
|
+
│ ├── api/ # Per-module API docs
|
|
555
|
+
│ └── guides/ # Integration guides
|
|
556
|
+
├── package.json # npm workspaces root
|
|
557
|
+
├── tsconfig.json # TypeScript 5.4+ strict config
|
|
558
|
+
└── LICENSE # MIT
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## Contributing
|
|
564
|
+
|
|
565
|
+
See [docs/CONTRIBUTING.md](./docs/CONTRIBUTING.md).
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## Changelog
|
|
570
|
+
|
|
571
|
+
See [docs/CHANGELOG.md](./docs/CHANGELOG.md).
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## License
|
|
576
|
+
|
|
577
|
+
MIT © Immortal.js Contributors — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Immortal.js will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.0.0] — 2026-07-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Layer 1 (Containment)**: `AsyncBoundary`, `asyncBoundary`, `ErrorTrap`, `classifyError`, `serializeError`, `ImmortalError`, `createSafeZone`
|
|
9
|
+
- **Layer 2 (Recovery)**: `withRetry`, `withTimeout`, `calculateBackoff`, `CircuitBreaker`, `CircuitBreakerRegistry`, `RouteFallbackCache`
|
|
10
|
+
- **Layer 3 (Isolation)**: `BulkheadPool`, `BulkheadRegistry`, `WorkerSandbox`, `BulkheadRejectedError`
|
|
11
|
+
- **Layer 4 (Supervision)**: `Supervisor`, `ClusterManager`, `computeRestartDecision`
|
|
12
|
+
- **Layer 5 (Observability)**: `MetricsCollector`, `HealthMonitor`, `MemoryLeakGuard`, `DiagnosticsChannel`
|
|
13
|
+
- **Lifecycle**: `GracefulShutdown`, `createImmortal`, `getImmortal`, `ImmortalRuntime`
|
|
14
|
+
- **Chaos**: `ChaosEngine` with latency, error, and worker-kill fault types
|
|
15
|
+
- **Plugins**: `ConsoleLogPlugin`, `RequestTracingPlugin`, `createAnomalyDetectionPlugin`, `createSlackAlertPlugin`
|
|
16
|
+
- **Event bus**: `ImmortalEventBus` with typed events, history, and wildcard subscriptions
|
|
17
|
+
- **Packages**: `@immortal/fastify`, `@immortal/nestjs`, `@immortal/koa`, `@immortal/dashboard`
|
|
18
|
+
- **Examples**: `fastify-microservice`, `nestjs-enterprise`
|
|
19
|
+
- **Docs**: Full API reference and integration guides
|
|
20
|
+
- **Tests**: 102 tests across 6 suites, all passing
|
|
21
|
+
- **TypeScript**: Strict mode with `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, `noImplicitOverride`
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Contributing to Immortal.js
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/your-org/immortal-js.git
|
|
7
|
+
cd immortal-js
|
|
8
|
+
npm install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Development
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# TypeScript check
|
|
15
|
+
./node_modules/.bin/tsc --noEmit --project packages/core/tsconfig.json
|
|
16
|
+
|
|
17
|
+
# Run tests
|
|
18
|
+
npm test --workspaces --if-present
|
|
19
|
+
|
|
20
|
+
# Watch mode
|
|
21
|
+
cd packages/core && npm test -- --watch
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Pull Request checklist
|
|
25
|
+
|
|
26
|
+
- [ ] `tsc --noEmit` passes with zero errors
|
|
27
|
+
- [ ] All existing tests pass
|
|
28
|
+
- [ ] New functionality has tests
|
|
29
|
+
- [ ] Public API changes are documented in `docs/`
|
|
30
|
+
- [ ] `docs/CHANGELOG.md` entry added
|
|
31
|
+
|
|
32
|
+
## Code style
|
|
33
|
+
|
|
34
|
+
- TypeScript strict mode — all five flags in `tsconfig.json` must be satisfied
|
|
35
|
+
- No `any` without a comment explaining why
|
|
36
|
+
- `exactOptionalPropertyTypes`: use `if (x !== undefined) obj.field = x` instead of direct assignment
|
|
37
|
+
- `delete obj.field` to clear optional fields instead of `= undefined`
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
By contributing, you agree your contributions are licensed under MIT.
|