@backendkit-labs/bulkhead 0.1.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/README.md ADDED
@@ -0,0 +1,278 @@
1
+ # @backendkit-labs/bulkhead
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@backendkit-labs/bulkhead?style=flat-square&color=cb3837)](https://www.npmjs.com/package/@backendkit-labs/bulkhead)
4
+ [![CI](https://img.shields.io/github/actions/workflow/status/backendkit-dev/backendkit-monorepo/ci.yml?style=flat-square&label=CI)](https://github.com/backendkit-dev/backendkit-monorepo/actions/workflows/ci.yml)
5
+ [![License](https://img.shields.io/npm/l/@backendkit-labs/bulkhead?style=flat-square)](LICENSE)
6
+ [![Node](https://img.shields.io/node/v/@backendkit-labs/bulkhead?style=flat-square)](package.json)
7
+
8
+ > Bulkhead concurrency limiting for Node.js — inspired by Resilience4j. Framework-agnostic core with optional NestJS integration.
9
+
10
+ Prevents resource exhaustion and cascading failures by limiting how many operations run simultaneously on a given resource.
11
+
12
+ ---
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @backendkit-labs/bulkhead
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Quick Start — Framework-agnostic
23
+
24
+ ```typescript
25
+ import { Bulkhead } from '@backendkit-labs/bulkhead';
26
+
27
+ const bulkhead = new Bulkhead({
28
+ name: 'payments',
29
+ maxConcurrentCalls: 10,
30
+ maxQueueSize: 50,
31
+ queueTimeoutMs: 5000,
32
+ rejectWhenFull: true,
33
+ });
34
+
35
+ const result = await bulkhead.execute(() => callPaymentApi());
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Core API
41
+
42
+ ### `Bulkhead`
43
+
44
+ ```typescript
45
+ const bulkhead = new Bulkhead(config);
46
+
47
+ // Execute a task — waits in queue if at capacity
48
+ await bulkhead.execute(async () => { ... });
49
+
50
+ // Check if capacity is available before executing
51
+ if (bulkhead.canAccept()) { ... }
52
+
53
+ // Current metrics snapshot
54
+ const metrics = bulkhead.getMetrics();
55
+
56
+ // Reset all counters
57
+ bulkhead.resetMetrics();
58
+ ```
59
+
60
+ ### `BulkheadConfig`
61
+
62
+ | Property | Type | Description |
63
+ |----------|------|-------------|
64
+ | `name` | `string` | Identifier for metrics and error messages |
65
+ | `maxConcurrentCalls` | `number` | Max simultaneous executions |
66
+ | `maxQueueSize` | `number` | Max tasks waiting in queue |
67
+ | `queueTimeoutMs` | `number` | Max time a task can wait in queue (ms) |
68
+ | `rejectWhenFull` | `boolean` | Throw immediately when full; if `false`, retries with exponential backoff |
69
+
70
+ ### `BulkheadMetrics`
71
+
72
+ ```typescript
73
+ {
74
+ name: string;
75
+ activeCalls: number;
76
+ queuedCalls: number;
77
+ maxConcurrentCalls: number;
78
+ maxQueueSize: number;
79
+ totalCalls: number;
80
+ successfulCalls: number;
81
+ failedCalls: number;
82
+ rejectedCalls: number;
83
+ timedOutCalls: number;
84
+ averageDurationMs: number;
85
+ }
86
+ ```
87
+
88
+ ### Errors
89
+
90
+ ```typescript
91
+ import { BulkheadRejectedError, BulkheadTimeoutError } from '@backendkit-labs/bulkhead';
92
+
93
+ try {
94
+ await bulkhead.execute(task);
95
+ } catch (error) {
96
+ if (error instanceof BulkheadRejectedError) {
97
+ // Queue was full — task was not queued
98
+ }
99
+ if (error instanceof BulkheadTimeoutError) {
100
+ // Task waited too long in queue
101
+ }
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ## BulkheadRegistry
108
+
109
+ Manages named bulkhead instances with sensible defaults for common resource types:
110
+
111
+ ```typescript
112
+ import { BulkheadRegistry } from '@backendkit-labs/bulkhead';
113
+
114
+ const registry = new BulkheadRegistry();
115
+
116
+ // Custom
117
+ const bh = registry.getOrCreate({ name: 'my-service', maxConcurrentCalls: 15 });
118
+
119
+ // Pre-configured factory methods
120
+ const clientBh = registry.getForClient('client-123', '/api/orders'); // 5 concurrent, 20 queued
121
+ const serviceBh = registry.getForService('inventory-service'); // 20 concurrent, 200 queued
122
+ const dbBh = registry.getForDatabase('orders_schema'); // 15 concurrent, 150 queued
123
+ const externalBh = registry.getForHttpExternal('stripe-api'); // 8 concurrent, 50 queued, 10s timeout
124
+
125
+ // Observability
126
+ const all = registry.getAllMetrics();
127
+ const overloaded = registry.getOverloadedBulkheads(); // ≥80% active capacity
128
+ registry.resetAllMetrics();
129
+ ```
130
+
131
+ | Method | Concurrent | Queue | Timeout |
132
+ |--------|-----------|-------|---------|
133
+ | `getForClient(id, endpoint?)` | 5 | 20 | 30s |
134
+ | `getForService(name)` | 20 | 200 | 30s |
135
+ | `getForDatabase(schema)` | 15 | 150 | 30s |
136
+ | `getForHttpExternal(name)` | 8 | 50 | 10s |
137
+
138
+ ---
139
+
140
+ ## NestJS Integration
141
+
142
+ ```bash
143
+ npm install @backendkit-labs/bulkhead
144
+ ```
145
+
146
+ Import `BulkheadModule` into your NestJS application:
147
+
148
+ ```typescript
149
+ import { BulkheadModule } from '@backendkit-labs/bulkhead/nestjs';
150
+
151
+ @Module({
152
+ imports: [BulkheadModule],
153
+ })
154
+ export class AppModule {}
155
+ ```
156
+
157
+ ### Guard — declarative per-route protection
158
+
159
+ ```typescript
160
+ import { UseBulkhead, BulkheadGuard } from '@backendkit-labs/bulkhead/nestjs';
161
+
162
+ @Controller('orders')
163
+ export class OrdersController {
164
+ // Shared service-level limit
165
+ @UseBulkhead({ name: 'orders-service' })
166
+ @UseGuards(BulkheadGuard)
167
+ @Get()
168
+ findAll() { ... }
169
+
170
+ // Per-client isolation (reads x-client-id header)
171
+ @UseBulkhead({ name: 'orders-create', perClient: true })
172
+ @UseGuards(BulkheadGuard)
173
+ @Post()
174
+ create() { ... }
175
+ }
176
+ ```
177
+
178
+ Returns `503 Service Unavailable` when at capacity.
179
+
180
+ ### Interceptor — wraps handler execution inside the bulkhead
181
+
182
+ ```typescript
183
+ import { BulkheadInterceptor } from '@backendkit-labs/bulkhead/nestjs';
184
+
185
+ // Apply globally
186
+ app.useGlobalInterceptors(new BulkheadInterceptor(registry));
187
+
188
+ // Or per controller / route
189
+ @UseInterceptors(BulkheadInterceptor)
190
+ @Controller('reports')
191
+ export class ReportsController { ... }
192
+ ```
193
+
194
+ Returns `503` on rejection, `408` on timeout.
195
+
196
+ ### Middleware — global HTTP concurrency limit
197
+
198
+ Protects the entire service from being overwhelmed before requests even reach your handlers:
199
+
200
+ ```typescript
201
+ import { HttpBulkheadMiddleware } from '@backendkit-labs/bulkhead/nestjs';
202
+
203
+ @Module({ imports: [BulkheadModule] })
204
+ export class AppModule implements NestModule {
205
+ configure(consumer: MiddlewareConsumer) {
206
+ consumer.apply(HttpBulkheadMiddleware).forRoutes('*');
207
+ }
208
+ }
209
+ ```
210
+
211
+ Configure via environment variables:
212
+
213
+ | Variable | Default | Description |
214
+ |----------|---------|-------------|
215
+ | `HTTP_BULKHEAD_CONCURRENCY` | `50` | Max concurrent requests |
216
+ | `HTTP_BULKHEAD_MAX_QUEUE` | `100` | Max queued requests |
217
+
218
+ Returns `429 Too Many Requests` when the queue is full.
219
+
220
+ ### Method Decorator
221
+
222
+ ```typescript
223
+ import { WithBulkhead } from '@backendkit-labs/bulkhead/nestjs';
224
+
225
+ @Injectable()
226
+ export class ReportService {
227
+ // Must have bulkheadRegistry injected
228
+ constructor(public readonly bulkheadRegistry: BulkheadRegistry) {}
229
+
230
+ @WithBulkhead({ name: 'report-generation', maxConcurrent: 3 })
231
+ async generateReport(id: string) { ... }
232
+ }
233
+ ```
234
+
235
+ ### Monitoring — BulkheadService
236
+
237
+ ```typescript
238
+ import { BulkheadService } from '@backendkit-labs/bulkhead/nestjs';
239
+
240
+ @Controller('health')
241
+ export class HealthController {
242
+ constructor(private readonly bulkheads: BulkheadService) {}
243
+
244
+ @Get('bulkheads')
245
+ getMetrics() {
246
+ return {
247
+ all: this.bulkheads.getAllMetrics(),
248
+ critical: this.bulkheads.getCriticalBulkheads(), // ≥90% active
249
+ };
250
+ }
251
+ }
252
+ ```
253
+
254
+ `BulkheadService` also logs a warning every 60 seconds when any bulkhead reaches 90%+ utilization.
255
+
256
+ ---
257
+
258
+ ## Architecture
259
+
260
+ ```
261
+ @backendkit-labs/bulkhead (core — no framework deps)
262
+ Bulkhead queue-based concurrency limiter
263
+ BulkheadRegistry named instances + factory methods
264
+
265
+ @backendkit-labs/bulkhead/nestjs (optional NestJS layer)
266
+ BulkheadModule NestJS module
267
+ BulkheadGuard @UseBulkhead() per-route decorator
268
+ BulkheadInterceptor wraps handler in execute()
269
+ HttpBulkheadMiddleware global HTTP request limiter
270
+ WithBulkhead method-level decorator
271
+ BulkheadService metrics + auto-monitoring
272
+ ```
273
+
274
+ ---
275
+
276
+ ## License
277
+
278
+ MIT — [BackendKit Labs](https://github.com/backendkit-dev)
package/dist/index.cjs ADDED
@@ -0,0 +1,229 @@
1
+ 'use strict';
2
+
3
+ // src/bulkhead/bulkhead.ts
4
+ var BulkheadRejectedError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "BulkheadRejectedError";
8
+ }
9
+ };
10
+ var BulkheadTimeoutError = class extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "BulkheadTimeoutError";
14
+ }
15
+ };
16
+ var Bulkhead = class {
17
+ constructor(config) {
18
+ this.config = config;
19
+ }
20
+ config;
21
+ activeCalls = 0;
22
+ nextId = 0;
23
+ queue = [];
24
+ totalCalls = 0;
25
+ successfulCalls = 0;
26
+ failedCalls = 0;
27
+ rejectedCalls = 0;
28
+ timedOutCalls = 0;
29
+ totalDurationMs = 0;
30
+ async execute(task) {
31
+ const startTime = Date.now();
32
+ this.totalCalls++;
33
+ if (this.activeCalls < this.config.maxConcurrentCalls) {
34
+ return this.runTask(task, startTime);
35
+ }
36
+ if (this.queue.length >= this.config.maxQueueSize) {
37
+ if (this.config.rejectWhenFull) {
38
+ this.rejectedCalls++;
39
+ throw new BulkheadRejectedError(
40
+ `Bulkhead '${this.config.name}' is full. Active: ${this.activeCalls}, Queue: ${this.queue.length}, Max: ${this.config.maxConcurrentCalls}`
41
+ );
42
+ }
43
+ return this.waitAndRetry(task, startTime);
44
+ }
45
+ const entryId = this.nextId++;
46
+ return new Promise((resolve, reject) => {
47
+ const timeoutId = setTimeout(() => {
48
+ const index = this.queue.findIndex((item) => item.id === entryId);
49
+ if (index !== -1) this.queue.splice(index, 1);
50
+ this.timedOutCalls++;
51
+ reject(
52
+ new BulkheadTimeoutError(
53
+ `Bulkhead '${this.config.name}' timeout after ${this.config.queueTimeoutMs}ms`
54
+ )
55
+ );
56
+ }, this.config.queueTimeoutMs);
57
+ this.queue.push({
58
+ id: entryId,
59
+ task,
60
+ resolve: (value) => {
61
+ clearTimeout(timeoutId);
62
+ resolve(value);
63
+ },
64
+ reject: (reason) => {
65
+ clearTimeout(timeoutId);
66
+ reject(reason);
67
+ },
68
+ queuedAt: startTime
69
+ });
70
+ this.processQueue();
71
+ });
72
+ }
73
+ async runTask(task, startTime) {
74
+ this.activeCalls++;
75
+ try {
76
+ const result = await task();
77
+ this.successfulCalls++;
78
+ this.totalDurationMs += Date.now() - startTime;
79
+ return result;
80
+ } catch (error) {
81
+ this.failedCalls++;
82
+ throw error;
83
+ } finally {
84
+ this.activeCalls--;
85
+ this.processQueue();
86
+ }
87
+ }
88
+ async waitAndRetry(task, startTime) {
89
+ const maxRetries = 3;
90
+ const baseDelay = 100;
91
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
92
+ if (this.activeCalls < this.config.maxConcurrentCalls) {
93
+ return this.runTask(task, startTime);
94
+ }
95
+ await new Promise((resolve) => setTimeout(resolve, baseDelay * Math.pow(2, attempt - 1)));
96
+ }
97
+ this.rejectedCalls++;
98
+ throw new BulkheadRejectedError(
99
+ `Bulkhead '${this.config.name}' rejected after ${maxRetries} retries`
100
+ );
101
+ }
102
+ processQueue() {
103
+ while (this.activeCalls < this.config.maxConcurrentCalls && this.queue.length > 0) {
104
+ const next = this.queue.shift();
105
+ if (!next) break;
106
+ const waitTime = Date.now() - next.queuedAt;
107
+ if (waitTime > this.config.queueTimeoutMs) {
108
+ next.reject(new BulkheadTimeoutError(`Task timed out after ${waitTime}ms in queue`));
109
+ this.timedOutCalls++;
110
+ continue;
111
+ }
112
+ this.activeCalls++;
113
+ next.task().then((result) => {
114
+ this.successfulCalls++;
115
+ next.resolve(result);
116
+ }).catch((error) => {
117
+ this.failedCalls++;
118
+ next.reject(error);
119
+ }).finally(() => {
120
+ this.activeCalls--;
121
+ this.processQueue();
122
+ });
123
+ }
124
+ }
125
+ getMetrics() {
126
+ return {
127
+ name: this.config.name,
128
+ activeCalls: this.activeCalls,
129
+ queuedCalls: this.queue.length,
130
+ maxConcurrentCalls: this.config.maxConcurrentCalls,
131
+ maxQueueSize: this.config.maxQueueSize,
132
+ totalCalls: this.totalCalls,
133
+ successfulCalls: this.successfulCalls,
134
+ failedCalls: this.failedCalls,
135
+ rejectedCalls: this.rejectedCalls,
136
+ timedOutCalls: this.timedOutCalls,
137
+ averageDurationMs: this.successfulCalls > 0 ? Math.round(this.totalDurationMs / this.successfulCalls) : 0
138
+ };
139
+ }
140
+ canAccept() {
141
+ return this.activeCalls < this.config.maxConcurrentCalls || this.queue.length < this.config.maxQueueSize;
142
+ }
143
+ resetMetrics() {
144
+ this.totalCalls = 0;
145
+ this.successfulCalls = 0;
146
+ this.failedCalls = 0;
147
+ this.rejectedCalls = 0;
148
+ this.timedOutCalls = 0;
149
+ this.totalDurationMs = 0;
150
+ }
151
+ };
152
+
153
+ // src/bulkhead/bulkhead.registry.ts
154
+ var DEFAULT_CONFIG = {
155
+ maxConcurrentCalls: 10,
156
+ maxQueueSize: 100,
157
+ queueTimeoutMs: 3e4,
158
+ rejectWhenFull: true
159
+ };
160
+ var BulkheadRegistry = class {
161
+ bulkheads = /* @__PURE__ */ new Map();
162
+ getOrCreate(options) {
163
+ if (!this.bulkheads.has(options.name)) {
164
+ const config = { ...DEFAULT_CONFIG, ...options, name: options.name };
165
+ this.bulkheads.set(options.name, new Bulkhead(config));
166
+ }
167
+ return this.bulkheads.get(options.name);
168
+ }
169
+ /** Per-client isolation — 5 concurrent, 20 queued */
170
+ getForClient(clientId, endpoint) {
171
+ const name = endpoint ? `client:${clientId}:${endpoint}` : `client:${clientId}`;
172
+ return this.getOrCreate({ name, maxConcurrentCalls: 5, maxQueueSize: 20 });
173
+ }
174
+ /** Service-level limiting — 20 concurrent, 200 queued */
175
+ getForService(serviceName) {
176
+ return this.getOrCreate({
177
+ name: `service:${serviceName}`,
178
+ maxConcurrentCalls: 20,
179
+ maxQueueSize: 200
180
+ });
181
+ }
182
+ /** Database connection limiting — 15 concurrent, 150 queued */
183
+ getForDatabase(schema) {
184
+ return this.getOrCreate({
185
+ name: `database:${schema}`,
186
+ maxConcurrentCalls: 15,
187
+ maxQueueSize: 150
188
+ });
189
+ }
190
+ /** External HTTP calls — 8 concurrent, 50 queued, 10s timeout */
191
+ getForHttpExternal(serviceName) {
192
+ return this.getOrCreate({
193
+ name: `http:${serviceName}`,
194
+ maxConcurrentCalls: 8,
195
+ maxQueueSize: 50,
196
+ queueTimeoutMs: 1e4
197
+ });
198
+ }
199
+ getAllMetrics() {
200
+ const metrics = {};
201
+ for (const [name, bulkhead] of this.bulkheads) {
202
+ metrics[name] = bulkhead.getMetrics();
203
+ }
204
+ return metrics;
205
+ }
206
+ /** Returns bulkheads at or above 80% active capacity */
207
+ getOverloadedBulkheads() {
208
+ const overloaded = [];
209
+ for (const bulkhead of this.bulkheads.values()) {
210
+ const m = bulkhead.getMetrics();
211
+ if (m.activeCalls >= m.maxConcurrentCalls * 0.8) {
212
+ overloaded.push(m);
213
+ }
214
+ }
215
+ return overloaded;
216
+ }
217
+ resetAllMetrics() {
218
+ for (const bulkhead of this.bulkheads.values()) {
219
+ bulkhead.resetMetrics();
220
+ }
221
+ }
222
+ };
223
+
224
+ exports.Bulkhead = Bulkhead;
225
+ exports.BulkheadRegistry = BulkheadRegistry;
226
+ exports.BulkheadRejectedError = BulkheadRejectedError;
227
+ exports.BulkheadTimeoutError = BulkheadTimeoutError;
228
+ //# sourceMappingURL=index.cjs.map
229
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bulkhead/bulkhead.ts","../src/bulkhead/bulkhead.registry.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export interface BulkheadConfig {\n /** Max number of concurrent executions */\n maxConcurrentCalls: number;\n /** Max queue size for waiting tasks */\n maxQueueSize: number;\n /** Max time a task can wait in queue (ms) */\n queueTimeoutMs: number;\n /** Reject immediately when queue is full; if false, retries with backoff */\n rejectWhenFull: boolean;\n /** Identifier used in metrics and error messages */\n name: string;\n}\n\nexport interface BulkheadMetrics {\n name: string;\n activeCalls: number;\n queuedCalls: number;\n maxConcurrentCalls: number;\n maxQueueSize: number;\n totalCalls: number;\n successfulCalls: number;\n failedCalls: number;\n rejectedCalls: number;\n timedOutCalls: number;\n averageDurationMs: number;\n}\n\nexport class BulkheadRejectedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BulkheadRejectedError';\n }\n}\n\nexport class BulkheadTimeoutError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BulkheadTimeoutError';\n }\n}\n\nexport class Bulkhead {\n private activeCalls = 0;\n private nextId = 0;\n private queue: Array<{\n id: number;\n task: () => Promise<unknown>;\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n queuedAt: number;\n }> = [];\n\n private totalCalls = 0;\n private successfulCalls = 0;\n private failedCalls = 0;\n private rejectedCalls = 0;\n private timedOutCalls = 0;\n private totalDurationMs = 0;\n\n constructor(private readonly config: BulkheadConfig) {}\n\n async execute<T>(task: () => Promise<T>): Promise<T> {\n const startTime = Date.now();\n this.totalCalls++;\n\n if (this.activeCalls < this.config.maxConcurrentCalls) {\n return this.runTask(task, startTime);\n }\n\n if (this.queue.length >= this.config.maxQueueSize) {\n if (this.config.rejectWhenFull) {\n this.rejectedCalls++;\n throw new BulkheadRejectedError(\n `Bulkhead '${this.config.name}' is full. ` +\n `Active: ${this.activeCalls}, Queue: ${this.queue.length}, Max: ${this.config.maxConcurrentCalls}`,\n );\n }\n return this.waitAndRetry(task, startTime);\n }\n\n const entryId = this.nextId++;\n return new Promise<T>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n const index = this.queue.findIndex(item => item.id === entryId);\n if (index !== -1) this.queue.splice(index, 1);\n this.timedOutCalls++;\n reject(\n new BulkheadTimeoutError(\n `Bulkhead '${this.config.name}' timeout after ${this.config.queueTimeoutMs}ms`,\n ),\n );\n }, this.config.queueTimeoutMs);\n\n this.queue.push({\n id: entryId,\n task: task as () => Promise<unknown>,\n resolve: value => {\n clearTimeout(timeoutId);\n resolve(value as T);\n },\n reject: reason => {\n clearTimeout(timeoutId);\n reject(reason);\n },\n queuedAt: startTime,\n });\n\n this.processQueue();\n });\n }\n\n private async runTask<T>(task: () => Promise<T>, startTime: number): Promise<T> {\n this.activeCalls++;\n try {\n const result = await task();\n this.successfulCalls++;\n this.totalDurationMs += Date.now() - startTime;\n return result;\n } catch (error) {\n this.failedCalls++;\n throw error;\n } finally {\n this.activeCalls--;\n this.processQueue();\n }\n }\n\n private async waitAndRetry<T>(task: () => Promise<T>, startTime: number): Promise<T> {\n const maxRetries = 3;\n const baseDelay = 100;\n\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n if (this.activeCalls < this.config.maxConcurrentCalls) {\n return this.runTask(task, startTime);\n }\n await new Promise(resolve => setTimeout(resolve, baseDelay * Math.pow(2, attempt - 1)));\n }\n\n this.rejectedCalls++;\n throw new BulkheadRejectedError(\n `Bulkhead '${this.config.name}' rejected after ${maxRetries} retries`,\n );\n }\n\n private processQueue(): void {\n while (this.activeCalls < this.config.maxConcurrentCalls && this.queue.length > 0) {\n const next = this.queue.shift();\n if (!next) break;\n\n const waitTime = Date.now() - next.queuedAt;\n if (waitTime > this.config.queueTimeoutMs) {\n next.reject(new BulkheadTimeoutError(`Task timed out after ${waitTime}ms in queue`));\n this.timedOutCalls++;\n continue;\n }\n\n this.activeCalls++;\n next\n .task()\n .then(result => {\n this.successfulCalls++;\n next.resolve(result);\n })\n .catch(error => {\n this.failedCalls++;\n next.reject(error);\n })\n .finally(() => {\n this.activeCalls--;\n this.processQueue();\n });\n }\n }\n\n getMetrics(): BulkheadMetrics {\n return {\n name: this.config.name,\n activeCalls: this.activeCalls,\n queuedCalls: this.queue.length,\n maxConcurrentCalls: this.config.maxConcurrentCalls,\n maxQueueSize: this.config.maxQueueSize,\n totalCalls: this.totalCalls,\n successfulCalls: this.successfulCalls,\n failedCalls: this.failedCalls,\n rejectedCalls: this.rejectedCalls,\n timedOutCalls: this.timedOutCalls,\n averageDurationMs:\n this.successfulCalls > 0 ? Math.round(this.totalDurationMs / this.successfulCalls) : 0,\n };\n }\n\n canAccept(): boolean {\n return (\n this.activeCalls < this.config.maxConcurrentCalls ||\n this.queue.length < this.config.maxQueueSize\n );\n }\n\n resetMetrics(): void {\n this.totalCalls = 0;\n this.successfulCalls = 0;\n this.failedCalls = 0;\n this.rejectedCalls = 0;\n this.timedOutCalls = 0;\n this.totalDurationMs = 0;\n }\n}\n","import { Bulkhead, BulkheadConfig, BulkheadMetrics } from './bulkhead.js';\n\nexport interface BulkheadOptions extends Partial<BulkheadConfig> {\n name: string;\n}\n\nconst DEFAULT_CONFIG: Omit<BulkheadConfig, 'name'> = {\n maxConcurrentCalls: 10,\n maxQueueSize: 100,\n queueTimeoutMs: 30000,\n rejectWhenFull: true,\n};\n\nexport class BulkheadRegistry {\n private readonly bulkheads = new Map<string, Bulkhead>();\n\n getOrCreate(options: BulkheadOptions): Bulkhead {\n if (!this.bulkheads.has(options.name)) {\n const config: BulkheadConfig = { ...DEFAULT_CONFIG, ...options, name: options.name };\n this.bulkheads.set(options.name, new Bulkhead(config));\n }\n return this.bulkheads.get(options.name)!;\n }\n\n /** Per-client isolation — 5 concurrent, 20 queued */\n getForClient(clientId: string, endpoint?: string): Bulkhead {\n const name = endpoint ? `client:${clientId}:${endpoint}` : `client:${clientId}`;\n return this.getOrCreate({ name, maxConcurrentCalls: 5, maxQueueSize: 20 });\n }\n\n /** Service-level limiting — 20 concurrent, 200 queued */\n getForService(serviceName: string): Bulkhead {\n return this.getOrCreate({\n name: `service:${serviceName}`,\n maxConcurrentCalls: 20,\n maxQueueSize: 200,\n });\n }\n\n /** Database connection limiting — 15 concurrent, 150 queued */\n getForDatabase(schema: string): Bulkhead {\n return this.getOrCreate({\n name: `database:${schema}`,\n maxConcurrentCalls: 15,\n maxQueueSize: 150,\n });\n }\n\n /** External HTTP calls — 8 concurrent, 50 queued, 10s timeout */\n getForHttpExternal(serviceName: string): Bulkhead {\n return this.getOrCreate({\n name: `http:${serviceName}`,\n maxConcurrentCalls: 8,\n maxQueueSize: 50,\n queueTimeoutMs: 10000,\n });\n }\n\n getAllMetrics(): Record<string, BulkheadMetrics> {\n const metrics: Record<string, BulkheadMetrics> = {};\n for (const [name, bulkhead] of this.bulkheads) {\n metrics[name] = bulkhead.getMetrics();\n }\n return metrics;\n }\n\n /** Returns bulkheads at or above 80% active capacity */\n getOverloadedBulkheads(): BulkheadMetrics[] {\n const overloaded: BulkheadMetrics[] = [];\n for (const bulkhead of this.bulkheads.values()) {\n const m = bulkhead.getMetrics();\n if (m.activeCalls >= m.maxConcurrentCalls * 0.8) {\n overloaded.push(m);\n }\n }\n return overloaded;\n }\n\n resetAllMetrics(): void {\n for (const bulkhead of this.bulkheads.values()) {\n bulkhead.resetMetrics();\n }\n }\n}\n"]}
@@ -0,0 +1,73 @@
1
+ interface BulkheadConfig {
2
+ /** Max number of concurrent executions */
3
+ maxConcurrentCalls: number;
4
+ /** Max queue size for waiting tasks */
5
+ maxQueueSize: number;
6
+ /** Max time a task can wait in queue (ms) */
7
+ queueTimeoutMs: number;
8
+ /** Reject immediately when queue is full; if false, retries with backoff */
9
+ rejectWhenFull: boolean;
10
+ /** Identifier used in metrics and error messages */
11
+ name: string;
12
+ }
13
+ interface BulkheadMetrics {
14
+ name: string;
15
+ activeCalls: number;
16
+ queuedCalls: number;
17
+ maxConcurrentCalls: number;
18
+ maxQueueSize: number;
19
+ totalCalls: number;
20
+ successfulCalls: number;
21
+ failedCalls: number;
22
+ rejectedCalls: number;
23
+ timedOutCalls: number;
24
+ averageDurationMs: number;
25
+ }
26
+ declare class BulkheadRejectedError extends Error {
27
+ constructor(message: string);
28
+ }
29
+ declare class BulkheadTimeoutError extends Error {
30
+ constructor(message: string);
31
+ }
32
+ declare class Bulkhead {
33
+ private readonly config;
34
+ private activeCalls;
35
+ private nextId;
36
+ private queue;
37
+ private totalCalls;
38
+ private successfulCalls;
39
+ private failedCalls;
40
+ private rejectedCalls;
41
+ private timedOutCalls;
42
+ private totalDurationMs;
43
+ constructor(config: BulkheadConfig);
44
+ execute<T>(task: () => Promise<T>): Promise<T>;
45
+ private runTask;
46
+ private waitAndRetry;
47
+ private processQueue;
48
+ getMetrics(): BulkheadMetrics;
49
+ canAccept(): boolean;
50
+ resetMetrics(): void;
51
+ }
52
+
53
+ interface BulkheadOptions extends Partial<BulkheadConfig> {
54
+ name: string;
55
+ }
56
+ declare class BulkheadRegistry {
57
+ private readonly bulkheads;
58
+ getOrCreate(options: BulkheadOptions): Bulkhead;
59
+ /** Per-client isolation — 5 concurrent, 20 queued */
60
+ getForClient(clientId: string, endpoint?: string): Bulkhead;
61
+ /** Service-level limiting — 20 concurrent, 200 queued */
62
+ getForService(serviceName: string): Bulkhead;
63
+ /** Database connection limiting — 15 concurrent, 150 queued */
64
+ getForDatabase(schema: string): Bulkhead;
65
+ /** External HTTP calls — 8 concurrent, 50 queued, 10s timeout */
66
+ getForHttpExternal(serviceName: string): Bulkhead;
67
+ getAllMetrics(): Record<string, BulkheadMetrics>;
68
+ /** Returns bulkheads at or above 80% active capacity */
69
+ getOverloadedBulkheads(): BulkheadMetrics[];
70
+ resetAllMetrics(): void;
71
+ }
72
+
73
+ export { Bulkhead, type BulkheadConfig, type BulkheadMetrics, type BulkheadOptions, BulkheadRegistry, BulkheadRejectedError, BulkheadTimeoutError };
@@ -0,0 +1,73 @@
1
+ interface BulkheadConfig {
2
+ /** Max number of concurrent executions */
3
+ maxConcurrentCalls: number;
4
+ /** Max queue size for waiting tasks */
5
+ maxQueueSize: number;
6
+ /** Max time a task can wait in queue (ms) */
7
+ queueTimeoutMs: number;
8
+ /** Reject immediately when queue is full; if false, retries with backoff */
9
+ rejectWhenFull: boolean;
10
+ /** Identifier used in metrics and error messages */
11
+ name: string;
12
+ }
13
+ interface BulkheadMetrics {
14
+ name: string;
15
+ activeCalls: number;
16
+ queuedCalls: number;
17
+ maxConcurrentCalls: number;
18
+ maxQueueSize: number;
19
+ totalCalls: number;
20
+ successfulCalls: number;
21
+ failedCalls: number;
22
+ rejectedCalls: number;
23
+ timedOutCalls: number;
24
+ averageDurationMs: number;
25
+ }
26
+ declare class BulkheadRejectedError extends Error {
27
+ constructor(message: string);
28
+ }
29
+ declare class BulkheadTimeoutError extends Error {
30
+ constructor(message: string);
31
+ }
32
+ declare class Bulkhead {
33
+ private readonly config;
34
+ private activeCalls;
35
+ private nextId;
36
+ private queue;
37
+ private totalCalls;
38
+ private successfulCalls;
39
+ private failedCalls;
40
+ private rejectedCalls;
41
+ private timedOutCalls;
42
+ private totalDurationMs;
43
+ constructor(config: BulkheadConfig);
44
+ execute<T>(task: () => Promise<T>): Promise<T>;
45
+ private runTask;
46
+ private waitAndRetry;
47
+ private processQueue;
48
+ getMetrics(): BulkheadMetrics;
49
+ canAccept(): boolean;
50
+ resetMetrics(): void;
51
+ }
52
+
53
+ interface BulkheadOptions extends Partial<BulkheadConfig> {
54
+ name: string;
55
+ }
56
+ declare class BulkheadRegistry {
57
+ private readonly bulkheads;
58
+ getOrCreate(options: BulkheadOptions): Bulkhead;
59
+ /** Per-client isolation — 5 concurrent, 20 queued */
60
+ getForClient(clientId: string, endpoint?: string): Bulkhead;
61
+ /** Service-level limiting — 20 concurrent, 200 queued */
62
+ getForService(serviceName: string): Bulkhead;
63
+ /** Database connection limiting — 15 concurrent, 150 queued */
64
+ getForDatabase(schema: string): Bulkhead;
65
+ /** External HTTP calls — 8 concurrent, 50 queued, 10s timeout */
66
+ getForHttpExternal(serviceName: string): Bulkhead;
67
+ getAllMetrics(): Record<string, BulkheadMetrics>;
68
+ /** Returns bulkheads at or above 80% active capacity */
69
+ getOverloadedBulkheads(): BulkheadMetrics[];
70
+ resetAllMetrics(): void;
71
+ }
72
+
73
+ export { Bulkhead, type BulkheadConfig, type BulkheadMetrics, type BulkheadOptions, BulkheadRegistry, BulkheadRejectedError, BulkheadTimeoutError };