@backendkit-labs/bulkhead 0.1.0 → 0.1.2

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 CHANGED
@@ -1,278 +1,337 @@
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)
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
+ ## TypeScript Configuration
23
+
24
+ ### Subpath exports (`/nestjs`)
25
+
26
+ This package uses the `exports` field in `package.json` to expose the `/nestjs` subpath. TypeScript's ability to resolve it depends on the `moduleResolution` setting in your `tsconfig.json`.
27
+
28
+ **Modern resolution (recommended) — no extra config needed:**
29
+
30
+ ```json
31
+ {
32
+ "compilerOptions": {
33
+ "moduleResolution": "bundler"
34
+ }
35
+ }
36
+ ```
37
+
38
+ `"bundler"`, `"node16"`, and `"nodenext"` all understand the `exports` field natively. This is the recommended setting for any project using a bundler or NestJS on TypeScript ≥ 5.
39
+
40
+ **Legacy resolution (`"node"`) — add a `paths` alias:**
41
+
42
+ NestJS projects generated before ~2024 default to `"moduleResolution": "node"`, which ignores the `exports` field. Add an explicit alias so TypeScript can find the types:
43
+
44
+ ```json
45
+ {
46
+ "compilerOptions": {
47
+ "moduleResolution": "node",
48
+ "paths": {
49
+ "@backendkit-labs/bulkhead/nestjs": [
50
+ "./node_modules/@backendkit-labs/bulkhead/dist/nestjs/index"
51
+ ]
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ > **Why?** The `"node"` resolver was designed before subpath exports existed and only reads `main`/`types` at the package root — it ignores the `exports` map entirely. The `paths` alias manually points TypeScript to the correct `.d.ts` file.
58
+
59
+ ### NestJS decorator support
60
+
61
+ ```json
62
+ {
63
+ "compilerOptions": {
64
+ "experimentalDecorators": true,
65
+ "emitDecoratorMetadata": true
66
+ }
67
+ }
68
+ ```
69
+
70
+ And import `reflect-metadata` once at application startup:
71
+
72
+ ```typescript
73
+ // main.ts
74
+ import 'reflect-metadata';
75
+ ```
76
+
77
+ > NestJS CLI scaffolds these automatically. You only need to verify them if setting up a project manually.
78
+
79
+ ---
80
+
81
+ ## Quick Start — Framework-agnostic
82
+
83
+ ```typescript
84
+ import { Bulkhead } from '@backendkit-labs/bulkhead';
85
+
86
+ const bulkhead = new Bulkhead({
87
+ name: 'payments',
88
+ maxConcurrentCalls: 10,
89
+ maxQueueSize: 50,
90
+ queueTimeoutMs: 5000,
91
+ rejectWhenFull: true,
92
+ });
93
+
94
+ const result = await bulkhead.execute(() => callPaymentApi());
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Core API
100
+
101
+ ### `Bulkhead`
102
+
103
+ ```typescript
104
+ const bulkhead = new Bulkhead(config);
105
+
106
+ // Execute a task — waits in queue if at capacity
107
+ await bulkhead.execute(async () => { ... });
108
+
109
+ // Check if capacity is available before executing
110
+ if (bulkhead.canAccept()) { ... }
111
+
112
+ // Current metrics snapshot
113
+ const metrics = bulkhead.getMetrics();
114
+
115
+ // Reset all counters
116
+ bulkhead.resetMetrics();
117
+ ```
118
+
119
+ ### `BulkheadConfig`
120
+
121
+ | Property | Type | Description |
122
+ |----------|------|-------------|
123
+ | `name` | `string` | Identifier for metrics and error messages |
124
+ | `maxConcurrentCalls` | `number` | Max simultaneous executions |
125
+ | `maxQueueSize` | `number` | Max tasks waiting in queue |
126
+ | `queueTimeoutMs` | `number` | Max time a task can wait in queue (ms) |
127
+ | `rejectWhenFull` | `boolean` | Throw immediately when full; if `false`, retries with exponential backoff |
128
+
129
+ ### `BulkheadMetrics`
130
+
131
+ ```typescript
132
+ {
133
+ name: string;
134
+ activeCalls: number;
135
+ queuedCalls: number;
136
+ maxConcurrentCalls: number;
137
+ maxQueueSize: number;
138
+ totalCalls: number;
139
+ successfulCalls: number;
140
+ failedCalls: number;
141
+ rejectedCalls: number;
142
+ timedOutCalls: number;
143
+ averageDurationMs: number;
144
+ }
145
+ ```
146
+
147
+ ### Errors
148
+
149
+ ```typescript
150
+ import { BulkheadRejectedError, BulkheadTimeoutError } from '@backendkit-labs/bulkhead';
151
+
152
+ try {
153
+ await bulkhead.execute(task);
154
+ } catch (error) {
155
+ if (error instanceof BulkheadRejectedError) {
156
+ // Queue was full — task was not queued
157
+ }
158
+ if (error instanceof BulkheadTimeoutError) {
159
+ // Task waited too long in queue
160
+ }
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## BulkheadRegistry
167
+
168
+ Manages named bulkhead instances with sensible defaults for common resource types:
169
+
170
+ ```typescript
171
+ import { BulkheadRegistry } from '@backendkit-labs/bulkhead';
172
+
173
+ const registry = new BulkheadRegistry();
174
+
175
+ // Custom
176
+ const bh = registry.getOrCreate({ name: 'my-service', maxConcurrentCalls: 15 });
177
+
178
+ // Pre-configured factory methods
179
+ const clientBh = registry.getForClient('client-123', '/api/orders'); // 5 concurrent, 20 queued
180
+ const serviceBh = registry.getForService('inventory-service'); // 20 concurrent, 200 queued
181
+ const dbBh = registry.getForDatabase('orders_schema'); // 15 concurrent, 150 queued
182
+ const externalBh = registry.getForHttpExternal('stripe-api'); // 8 concurrent, 50 queued, 10s timeout
183
+
184
+ // Observability
185
+ const all = registry.getAllMetrics();
186
+ const overloaded = registry.getOverloadedBulkheads(); // ≥80% active capacity
187
+ registry.resetAllMetrics();
188
+ ```
189
+
190
+ | Method | Concurrent | Queue | Timeout |
191
+ |--------|-----------|-------|---------|
192
+ | `getForClient(id, endpoint?)` | 5 | 20 | 30s |
193
+ | `getForService(name)` | 20 | 200 | 30s |
194
+ | `getForDatabase(schema)` | 15 | 150 | 30s |
195
+ | `getForHttpExternal(name)` | 8 | 50 | 10s |
196
+
197
+ ---
198
+
199
+ ## NestJS Integration
200
+
201
+ ```bash
202
+ npm install @backendkit-labs/bulkhead
203
+ ```
204
+
205
+ Import `BulkheadModule` into your NestJS application:
206
+
207
+ ```typescript
208
+ import { BulkheadModule } from '@backendkit-labs/bulkhead/nestjs';
209
+
210
+ @Module({
211
+ imports: [BulkheadModule],
212
+ })
213
+ export class AppModule {}
214
+ ```
215
+
216
+ ### Guard declarative per-route protection
217
+
218
+ ```typescript
219
+ import { UseBulkhead, BulkheadGuard } from '@backendkit-labs/bulkhead/nestjs';
220
+
221
+ @Controller('orders')
222
+ export class OrdersController {
223
+ // Shared service-level limit
224
+ @UseBulkhead({ name: 'orders-service' })
225
+ @UseGuards(BulkheadGuard)
226
+ @Get()
227
+ findAll() { ... }
228
+
229
+ // Per-client isolation (reads x-client-id header)
230
+ @UseBulkhead({ name: 'orders-create', perClient: true })
231
+ @UseGuards(BulkheadGuard)
232
+ @Post()
233
+ create() { ... }
234
+ }
235
+ ```
236
+
237
+ Returns `503 Service Unavailable` when at capacity.
238
+
239
+ ### Interceptor — wraps handler execution inside the bulkhead
240
+
241
+ ```typescript
242
+ import { BulkheadInterceptor } from '@backendkit-labs/bulkhead/nestjs';
243
+
244
+ // Apply globally
245
+ app.useGlobalInterceptors(new BulkheadInterceptor(registry));
246
+
247
+ // Or per controller / route
248
+ @UseInterceptors(BulkheadInterceptor)
249
+ @Controller('reports')
250
+ export class ReportsController { ... }
251
+ ```
252
+
253
+ Returns `503` on rejection, `408` on timeout.
254
+
255
+ ### Middleware — global HTTP concurrency limit
256
+
257
+ Protects the entire service from being overwhelmed before requests even reach your handlers:
258
+
259
+ ```typescript
260
+ import { HttpBulkheadMiddleware } from '@backendkit-labs/bulkhead/nestjs';
261
+
262
+ @Module({ imports: [BulkheadModule] })
263
+ export class AppModule implements NestModule {
264
+ configure(consumer: MiddlewareConsumer) {
265
+ consumer.apply(HttpBulkheadMiddleware).forRoutes('*');
266
+ }
267
+ }
268
+ ```
269
+
270
+ Configure via environment variables:
271
+
272
+ | Variable | Default | Description |
273
+ |----------|---------|-------------|
274
+ | `HTTP_BULKHEAD_CONCURRENCY` | `50` | Max concurrent requests |
275
+ | `HTTP_BULKHEAD_MAX_QUEUE` | `100` | Max queued requests |
276
+
277
+ Returns `429 Too Many Requests` when the queue is full.
278
+
279
+ ### Method Decorator
280
+
281
+ ```typescript
282
+ import { WithBulkhead } from '@backendkit-labs/bulkhead/nestjs';
283
+
284
+ @Injectable()
285
+ export class ReportService {
286
+ // Must have bulkheadRegistry injected
287
+ constructor(public readonly bulkheadRegistry: BulkheadRegistry) {}
288
+
289
+ @WithBulkhead({ name: 'report-generation', maxConcurrent: 3 })
290
+ async generateReport(id: string) { ... }
291
+ }
292
+ ```
293
+
294
+ ### Monitoring — BulkheadService
295
+
296
+ ```typescript
297
+ import { BulkheadService } from '@backendkit-labs/bulkhead/nestjs';
298
+
299
+ @Controller('health')
300
+ export class HealthController {
301
+ constructor(private readonly bulkheads: BulkheadService) {}
302
+
303
+ @Get('bulkheads')
304
+ getMetrics() {
305
+ return {
306
+ all: this.bulkheads.getAllMetrics(),
307
+ critical: this.bulkheads.getCriticalBulkheads(), // ≥90% active
308
+ };
309
+ }
310
+ }
311
+ ```
312
+
313
+ `BulkheadService` also logs a warning every 60 seconds when any bulkhead reaches 90%+ utilization.
314
+
315
+ ---
316
+
317
+ ## Architecture
318
+
319
+ ```
320
+ @backendkit-labs/bulkhead (core — no framework deps)
321
+ Bulkhead queue-based concurrency limiter
322
+ BulkheadRegistry named instances + factory methods
323
+
324
+ @backendkit-labs/bulkhead/nestjs (optional NestJS layer)
325
+ BulkheadModule NestJS module
326
+ BulkheadGuard @UseBulkhead() per-route decorator
327
+ BulkheadInterceptor wraps handler in execute()
328
+ HttpBulkheadMiddleware global HTTP request limiter
329
+ WithBulkhead method-level decorator
330
+ BulkheadService metrics + auto-monitoring
331
+ ```
332
+
333
+ ---
334
+
335
+ ## License
336
+
337
+ Apache-2.0 — [BackendKit Labs](https://github.com/backendkit-dev)