@backendkit-labs/bulkhead 0.1.2 → 0.2.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.
@@ -39,6 +39,9 @@ var Bulkhead = class {
39
39
  rejectedCalls = 0;
40
40
  timedOutCalls = 0;
41
41
  totalDurationMs = 0;
42
+ updateConfig(partial) {
43
+ this.config = { ...this.config, ...partial };
44
+ }
42
45
  async execute(task) {
43
46
  const startTime = Date.now();
44
47
  this.totalCalls++;
@@ -58,16 +61,19 @@ var Bulkhead = class {
58
61
  return new Promise((resolve, reject) => {
59
62
  const timeoutId = setTimeout(() => {
60
63
  const index = this.queue.findIndex((item) => item.id === entryId);
61
- if (index !== -1) this.queue.splice(index, 1);
62
- this.timedOutCalls++;
63
- reject(
64
- new BulkheadTimeoutError(
65
- `Bulkhead '${this.config.name}' timeout after ${this.config.queueTimeoutMs}ms`
66
- )
67
- );
64
+ if (index !== -1) {
65
+ this.queue.splice(index, 1);
66
+ this.timedOutCalls++;
67
+ reject(
68
+ new BulkheadTimeoutError(
69
+ `Bulkhead '${this.config.name}' timeout after ${this.config.queueTimeoutMs}ms`
70
+ )
71
+ );
72
+ }
68
73
  }, this.config.queueTimeoutMs);
69
74
  this.queue.push({
70
75
  id: entryId,
76
+ timeoutId,
71
77
  task,
72
78
  resolve: (value) => {
73
79
  clearTimeout(timeoutId);
@@ -115,6 +121,7 @@ var Bulkhead = class {
115
121
  while (this.activeCalls < this.config.maxConcurrentCalls && this.queue.length > 0) {
116
122
  const next = this.queue.shift();
117
123
  if (!next) break;
124
+ clearTimeout(next.timeoutId);
118
125
  const waitTime = Date.now() - next.queuedAt;
119
126
  if (waitTime > this.config.queueTimeoutMs) {
120
127
  next.reject(new BulkheadTimeoutError(`Task timed out after ${waitTime}ms in queue`));
@@ -239,5 +246,5 @@ exports.BulkheadRejectedError = BulkheadRejectedError;
239
246
  exports.BulkheadTimeoutError = BulkheadTimeoutError;
240
247
  exports.__decorateClass = __decorateClass;
241
248
  exports.__decorateParam = __decorateParam;
242
- //# sourceMappingURL=chunk-LXXCDKHB.cjs.map
243
- //# sourceMappingURL=chunk-LXXCDKHB.cjs.map
249
+ //# sourceMappingURL=chunk-2HCEIIKT.cjs.map
250
+ //# sourceMappingURL=chunk-2HCEIIKT.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bulkhead/bulkhead.ts","../src/bulkhead/bulkhead.registry.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AA2BO,IAAM,qBAAA,GAAN,cAAoC,KAAA,CAAM;AAAA,EAC/C,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,uBAAA;AAAA,EACd;AACF;AAEO,IAAM,oBAAA,GAAN,cAAmC,KAAA,CAAM;AAAA,EAC9C,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,sBAAA;AAAA,EACd;AACF;AAEO,IAAM,WAAN,MAAe;AAAA,EAmBpB,YAAoB,MAAA,EAAwB;AAAxB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAyB;AAAA,EAAzB,MAAA;AAAA,EAlBZ,WAAA,GAAc,CAAA;AAAA,EACd,MAAA,GAAS,CAAA;AAAA,EACT,QAOH,EAAC;AAAA,EAEE,UAAA,GAAa,CAAA;AAAA,EACb,eAAA,GAAkB,CAAA;AAAA,EAClB,WAAA,GAAc,CAAA;AAAA,EACd,aAAA,GAAgB,CAAA;AAAA,EAChB,aAAA,GAAgB,CAAA;AAAA,EAChB,eAAA,GAAkB,CAAA;AAAA,EAI1B,aAAa,OAAA,EAAsD;AACjE,IAAA,IAAA,CAAK,SAAS,EAAE,GAAG,IAAA,CAAK,MAAA,EAAQ,GAAG,OAAA,EAAQ;AAAA,EAC7C;AAAA,EAEA,MAAM,QAAW,IAAA,EAAoC;AACnD,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,IAAA,CAAK,UAAA,EAAA;AAEL,IAAA,IAAI,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,MAAA,CAAO,kBAAA,EAAoB;AACrD,MAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,SAAS,CAAA;AAAA,IACrC;AAEA,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,MAAA,IAAU,IAAA,CAAK,OAAO,YAAA,EAAc;AACjD,MAAA,IAAI,IAAA,CAAK,OAAO,cAAA,EAAgB;AAC9B,QAAA,IAAA,CAAK,aAAA,EAAA;AACL,QAAA,MAAM,IAAI,qBAAA;AAAA,UACR,CAAA,UAAA,EAAa,IAAA,CAAK,MAAA,CAAO,IAAI,sBAChB,IAAA,CAAK,WAAW,CAAA,SAAA,EAAY,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,OAAA,EAAU,IAAA,CAAK,OAAO,kBAAkB,CAAA;AAAA,SACpG;AAAA,MACF;AACA,MAAA,OAAO,IAAA,CAAK,YAAA,CAAa,IAAA,EAAM,SAAS,CAAA;AAAA,IAC1C;AAEA,IAAA,MAAM,UAAU,IAAA,CAAK,MAAA,EAAA;AACrB,IAAA,OAAO,IAAI,OAAA,CAAW,CAAC,OAAA,EAAS,MAAA,KAAW;AACzC,MAAA,MAAM,SAAA,GAAY,WAAW,MAAM;AACjC,QAAA,MAAM,QAAQ,IAAA,CAAK,KAAA,CAAM,UAAU,CAAA,IAAA,KAAQ,IAAA,CAAK,OAAO,OAAO,CAAA;AAC9D,QAAA,IAAI,UAAU,EAAA,EAAI;AAChB,UAAA,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,KAAA,EAAO,CAAC,CAAA;AAC1B,UAAA,IAAA,CAAK,aAAA,EAAA;AACL,UAAA,MAAA;AAAA,YACE,IAAI,oBAAA;AAAA,cACF,aAAa,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,gBAAA,EAAmB,IAAA,CAAK,OAAO,cAAc,CAAA,EAAA;AAAA;AAC5E,WACF;AAAA,QACF;AAAA,MACF,CAAA,EAAG,IAAA,CAAK,MAAA,CAAO,cAAc,CAAA;AAE7B,MAAA,IAAA,CAAK,MAAM,IAAA,CAAK;AAAA,QACd,EAAA,EAAI,OAAA;AAAA,QACJ,SAAA;AAAA,QACA,IAAA;AAAA,QACA,SAAS,CAAA,KAAA,KAAS;AAChB,UAAA,YAAA,CAAa,SAAS,CAAA;AACtB,UAAA,OAAA,CAAQ,KAAU,CAAA;AAAA,QACpB,CAAA;AAAA,QACA,QAAQ,CAAA,MAAA,KAAU;AAChB,UAAA,YAAA,CAAa,SAAS,CAAA;AACtB,UAAA,MAAA,CAAO,MAAM,CAAA;AAAA,QACf,CAAA;AAAA,QACA,QAAA,EAAU;AAAA,OACX,CAAA;AAED,MAAA,IAAA,CAAK,YAAA,EAAa;AAAA,IACpB,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAc,OAAA,CAAW,IAAA,EAAwB,SAAA,EAA+B;AAC9E,IAAA,IAAA,CAAK,WAAA,EAAA;AACL,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,IAAA,EAAK;AAC1B,MAAA,IAAA,CAAK,eAAA,EAAA;AACL,MAAA,IAAA,CAAK,eAAA,IAAmB,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AACrC,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,WAAA,EAAA;AACL,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,WAAA,EAAA;AACL,MAAA,IAAA,CAAK,YAAA,EAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAc,YAAA,CAAgB,IAAA,EAAwB,SAAA,EAA+B;AACnF,IAAA,MAAM,UAAA,GAAa,CAAA;AACnB,IAAA,MAAM,SAAA,GAAY,GAAA;AAElB,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,UAAA,EAAY,OAAA,EAAA,EAAW;AACtD,MAAA,IAAI,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,MAAA,CAAO,kBAAA,EAAoB;AACrD,QAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,SAAS,CAAA;AAAA,MACrC;AACA,MAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAA,GAAU,CAAC,CAAC,CAAC,CAAA;AAAA,IACxF;AAEA,IAAA,IAAA,CAAK,aAAA,EAAA;AACL,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,CAAA,UAAA,EAAa,IAAA,CAAK,MAAA,CAAO,IAAI,oBAAoB,UAAU,CAAA,QAAA;AAAA,KAC7D;AAAA,EACF;AAAA,EAEQ,YAAA,GAAqB;AAC3B,IAAA,OAAO,IAAA,CAAK,cAAc,IAAA,CAAK,MAAA,CAAO,sBAAsB,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA,EAAG;AACjF,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,KAAA,EAAM;AAC9B,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,YAAA,CAAa,KAAK,SAAS,CAAA;AAE3B,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK,QAAA;AACnC,MAAA,IAAI,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,cAAA,EAAgB;AACzC,QAAA,IAAA,CAAK,OAAO,IAAI,oBAAA,CAAqB,CAAA,qBAAA,EAAwB,QAAQ,aAAa,CAAC,CAAA;AACnF,QAAA,IAAA,CAAK,aAAA,EAAA;AACL,QAAA;AAAA,MACF;AAEA,MAAA,IAAA,CAAK,WAAA,EAAA;AACL,MAAA,IAAA,CACG,IAAA,EAAK,CACL,IAAA,CAAK,CAAA,MAAA,KAAU;AACd,QAAA,IAAA,CAAK,eAAA,EAAA;AACL,QAAA,IAAA,CAAK,QAAQ,MAAM,CAAA;AAAA,MACrB,CAAC,CAAA,CACA,KAAA,CAAM,CAAA,KAAA,KAAS;AACd,QAAA,IAAA,CAAK,WAAA,EAAA;AACL,QAAA,IAAA,CAAK,OAAO,KAAK,CAAA;AAAA,MACnB,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,QAAA,IAAA,CAAK,WAAA,EAAA;AACL,QAAA,IAAA,CAAK,YAAA,EAAa;AAAA,MACpB,CAAC,CAAA;AAAA,IACL;AAAA,EACF;AAAA,EAEA,UAAA,GAA8B;AAC5B,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,KAAK,MAAA,CAAO,IAAA;AAAA,MAClB,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,WAAA,EAAa,KAAK,KAAA,CAAM,MAAA;AAAA,MACxB,kBAAA,EAAoB,KAAK,MAAA,CAAO,kBAAA;AAAA,MAChC,YAAA,EAAc,KAAK,MAAA,CAAO,YAAA;AAAA,MAC1B,YAAY,IAAA,CAAK,UAAA;AAAA,MACjB,iBAAiB,IAAA,CAAK,eAAA;AAAA,MACtB,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,eAAe,IAAA,CAAK,aAAA;AAAA,MACpB,eAAe,IAAA,CAAK,aAAA;AAAA,MACpB,iBAAA,EACE,IAAA,CAAK,eAAA,GAAkB,CAAA,GAAI,IAAA,CAAK,MAAM,IAAA,CAAK,eAAA,GAAkB,IAAA,CAAK,eAAe,CAAA,GAAI;AAAA,KACzF;AAAA,EACF;AAAA,EAEA,SAAA,GAAqB;AACnB,IAAA,OACE,IAAA,CAAK,cAAc,IAAA,CAAK,MAAA,CAAO,sBAC/B,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,YAAA;AAAA,EAEpC;AAAA,EAEA,YAAA,GAAqB;AACnB,IAAA,IAAA,CAAK,UAAA,GAAa,CAAA;AAClB,IAAA,IAAA,CAAK,eAAA,GAAkB,CAAA;AACvB,IAAA,IAAA,CAAK,WAAA,GAAc,CAAA;AACnB,IAAA,IAAA,CAAK,aAAA,GAAgB,CAAA;AACrB,IAAA,IAAA,CAAK,aAAA,GAAgB,CAAA;AACrB,IAAA,IAAA,CAAK,eAAA,GAAkB,CAAA;AAAA,EACzB;AACF;;;AClNA,IAAM,cAAA,GAA+C;AAAA,EACnD,kBAAA,EAAoB,EAAA;AAAA,EACpB,YAAA,EAAc,GAAA;AAAA,EACd,cAAA,EAAgB,GAAA;AAAA,EAChB,cAAA,EAAgB;AAClB,CAAA;AAEO,IAAM,mBAAN,MAAuB;AAAA,EACX,SAAA,uBAAgB,GAAA,EAAsB;AAAA,EAEvD,YAAY,OAAA,EAAoC;AAC9C,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA,EAAG;AACrC,MAAA,MAAM,MAAA,GAAyB,EAAE,GAAG,cAAA,EAAgB,GAAG,OAAA,EAAS,IAAA,EAAM,QAAQ,IAAA,EAAK;AACnF,MAAA,IAAA,CAAK,UAAU,GAAA,CAAI,OAAA,CAAQ,MAAM,IAAI,QAAA,CAAS,MAAM,CAAC,CAAA;AAAA,IACvD;AACA,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AAAA,EACxC;AAAA;AAAA,EAGA,YAAA,CAAa,UAAkB,QAAA,EAA6B;AAC1D,IAAA,MAAM,IAAA,GAAO,WAAW,CAAA,OAAA,EAAU,QAAQ,IAAI,QAAQ,CAAA,CAAA,GAAK,UAAU,QAAQ,CAAA,CAAA;AAC7E,IAAA,OAAO,IAAA,CAAK,YAAY,EAAE,IAAA,EAAM,oBAAoB,CAAA,EAAG,YAAA,EAAc,IAAI,CAAA;AAAA,EAC3E;AAAA;AAAA,EAGA,cAAc,WAAA,EAA+B;AAC3C,IAAA,OAAO,KAAK,WAAA,CAAY;AAAA,MACtB,IAAA,EAAM,WAAW,WAAW,CAAA,CAAA;AAAA,MAC5B,kBAAA,EAAoB,EAAA;AAAA,MACpB,YAAA,EAAc;AAAA,KACf,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,eAAe,MAAA,EAA0B;AACvC,IAAA,OAAO,KAAK,WAAA,CAAY;AAAA,MACtB,IAAA,EAAM,YAAY,MAAM,CAAA,CAAA;AAAA,MACxB,kBAAA,EAAoB,EAAA;AAAA,MACpB,YAAA,EAAc;AAAA,KACf,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,mBAAmB,WAAA,EAA+B;AAChD,IAAA,OAAO,KAAK,WAAA,CAAY;AAAA,MACtB,IAAA,EAAM,QAAQ,WAAW,CAAA,CAAA;AAAA,MACzB,kBAAA,EAAoB,CAAA;AAAA,MACpB,YAAA,EAAc,EAAA;AAAA,MACd,cAAA,EAAgB;AAAA,KACjB,CAAA;AAAA,EACH;AAAA,EAEA,aAAA,GAAiD;AAC/C,IAAA,MAAM,UAA2C,EAAC;AAClD,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,QAAQ,CAAA,IAAK,KAAK,SAAA,EAAW;AAC7C,MAAA,OAAA,CAAQ,IAAI,CAAA,GAAI,QAAA,CAAS,UAAA,EAAW;AAAA,IACtC;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA,EAGA,sBAAA,GAA4C;AAC1C,IAAA,MAAM,aAAgC,EAAC;AACvC,IAAA,KAAA,MAAW,QAAA,IAAY,IAAA,CAAK,SAAA,CAAU,MAAA,EAAO,EAAG;AAC9C,MAAA,MAAM,CAAA,GAAI,SAAS,UAAA,EAAW;AAC9B,MAAA,IAAI,CAAA,CAAE,WAAA,IAAe,CAAA,CAAE,kBAAA,GAAqB,GAAA,EAAK;AAC/C,QAAA,UAAA,CAAW,KAAK,CAAC,CAAA;AAAA,MACnB;AAAA,IACF;AACA,IAAA,OAAO,UAAA;AAAA,EACT;AAAA,EAEA,eAAA,GAAwB;AACtB,IAAA,KAAA,MAAW,QAAA,IAAY,IAAA,CAAK,SAAA,CAAU,MAAA,EAAO,EAAG;AAC9C,MAAA,QAAA,CAAS,YAAA,EAAa;AAAA,IACxB;AAAA,EACF;AACF","file":"chunk-2HCEIIKT.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 timeoutId: ReturnType<typeof setTimeout>;\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 config: BulkheadConfig) {}\n\n updateConfig(partial: Partial<Omit<BulkheadConfig, 'name'>>): void {\n this.config = { ...this.config, ...partial };\n }\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) {\n 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 }\n }, this.config.queueTimeoutMs);\n\n this.queue.push({\n id: entryId,\n timeoutId,\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 clearTimeout(next.timeoutId);\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"]}
@@ -37,6 +37,9 @@ var Bulkhead = class {
37
37
  rejectedCalls = 0;
38
38
  timedOutCalls = 0;
39
39
  totalDurationMs = 0;
40
+ updateConfig(partial) {
41
+ this.config = { ...this.config, ...partial };
42
+ }
40
43
  async execute(task) {
41
44
  const startTime = Date.now();
42
45
  this.totalCalls++;
@@ -56,16 +59,19 @@ var Bulkhead = class {
56
59
  return new Promise((resolve, reject) => {
57
60
  const timeoutId = setTimeout(() => {
58
61
  const index = this.queue.findIndex((item) => item.id === entryId);
59
- if (index !== -1) this.queue.splice(index, 1);
60
- this.timedOutCalls++;
61
- reject(
62
- new BulkheadTimeoutError(
63
- `Bulkhead '${this.config.name}' timeout after ${this.config.queueTimeoutMs}ms`
64
- )
65
- );
62
+ if (index !== -1) {
63
+ this.queue.splice(index, 1);
64
+ this.timedOutCalls++;
65
+ reject(
66
+ new BulkheadTimeoutError(
67
+ `Bulkhead '${this.config.name}' timeout after ${this.config.queueTimeoutMs}ms`
68
+ )
69
+ );
70
+ }
66
71
  }, this.config.queueTimeoutMs);
67
72
  this.queue.push({
68
73
  id: entryId,
74
+ timeoutId,
69
75
  task,
70
76
  resolve: (value) => {
71
77
  clearTimeout(timeoutId);
@@ -113,6 +119,7 @@ var Bulkhead = class {
113
119
  while (this.activeCalls < this.config.maxConcurrentCalls && this.queue.length > 0) {
114
120
  const next = this.queue.shift();
115
121
  if (!next) break;
122
+ clearTimeout(next.timeoutId);
116
123
  const waitTime = Date.now() - next.queuedAt;
117
124
  if (waitTime > this.config.queueTimeoutMs) {
118
125
  next.reject(new BulkheadTimeoutError(`Task timed out after ${waitTime}ms in queue`));
@@ -232,5 +239,5 @@ var BulkheadRegistry = class {
232
239
  };
233
240
 
234
241
  export { Bulkhead, BulkheadRegistry, BulkheadRejectedError, BulkheadTimeoutError, __decorateClass, __decorateParam };
235
- //# sourceMappingURL=chunk-SRWDZPTJ.js.map
236
- //# sourceMappingURL=chunk-SRWDZPTJ.js.map
242
+ //# sourceMappingURL=chunk-OROUPJGG.js.map
243
+ //# sourceMappingURL=chunk-OROUPJGG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bulkhead/bulkhead.ts","../src/bulkhead/bulkhead.registry.ts"],"names":[],"mappings":";;;;;;;;;;;;;AA2BO,IAAM,qBAAA,GAAN,cAAoC,KAAA,CAAM;AAAA,EAC/C,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,uBAAA;AAAA,EACd;AACF;AAEO,IAAM,oBAAA,GAAN,cAAmC,KAAA,CAAM;AAAA,EAC9C,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,sBAAA;AAAA,EACd;AACF;AAEO,IAAM,WAAN,MAAe;AAAA,EAmBpB,YAAoB,MAAA,EAAwB;AAAxB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAyB;AAAA,EAAzB,MAAA;AAAA,EAlBZ,WAAA,GAAc,CAAA;AAAA,EACd,MAAA,GAAS,CAAA;AAAA,EACT,QAOH,EAAC;AAAA,EAEE,UAAA,GAAa,CAAA;AAAA,EACb,eAAA,GAAkB,CAAA;AAAA,EAClB,WAAA,GAAc,CAAA;AAAA,EACd,aAAA,GAAgB,CAAA;AAAA,EAChB,aAAA,GAAgB,CAAA;AAAA,EAChB,eAAA,GAAkB,CAAA;AAAA,EAI1B,aAAa,OAAA,EAAsD;AACjE,IAAA,IAAA,CAAK,SAAS,EAAE,GAAG,IAAA,CAAK,MAAA,EAAQ,GAAG,OAAA,EAAQ;AAAA,EAC7C;AAAA,EAEA,MAAM,QAAW,IAAA,EAAoC;AACnD,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,IAAA,CAAK,UAAA,EAAA;AAEL,IAAA,IAAI,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,MAAA,CAAO,kBAAA,EAAoB;AACrD,MAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,SAAS,CAAA;AAAA,IACrC;AAEA,IAAA,IAAI,IAAA,CAAK,KAAA,CAAM,MAAA,IAAU,IAAA,CAAK,OAAO,YAAA,EAAc;AACjD,MAAA,IAAI,IAAA,CAAK,OAAO,cAAA,EAAgB;AAC9B,QAAA,IAAA,CAAK,aAAA,EAAA;AACL,QAAA,MAAM,IAAI,qBAAA;AAAA,UACR,CAAA,UAAA,EAAa,IAAA,CAAK,MAAA,CAAO,IAAI,sBAChB,IAAA,CAAK,WAAW,CAAA,SAAA,EAAY,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,OAAA,EAAU,IAAA,CAAK,OAAO,kBAAkB,CAAA;AAAA,SACpG;AAAA,MACF;AACA,MAAA,OAAO,IAAA,CAAK,YAAA,CAAa,IAAA,EAAM,SAAS,CAAA;AAAA,IAC1C;AAEA,IAAA,MAAM,UAAU,IAAA,CAAK,MAAA,EAAA;AACrB,IAAA,OAAO,IAAI,OAAA,CAAW,CAAC,OAAA,EAAS,MAAA,KAAW;AACzC,MAAA,MAAM,SAAA,GAAY,WAAW,MAAM;AACjC,QAAA,MAAM,QAAQ,IAAA,CAAK,KAAA,CAAM,UAAU,CAAA,IAAA,KAAQ,IAAA,CAAK,OAAO,OAAO,CAAA;AAC9D,QAAA,IAAI,UAAU,EAAA,EAAI;AAChB,UAAA,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,KAAA,EAAO,CAAC,CAAA;AAC1B,UAAA,IAAA,CAAK,aAAA,EAAA;AACL,UAAA,MAAA;AAAA,YACE,IAAI,oBAAA;AAAA,cACF,aAAa,IAAA,CAAK,MAAA,CAAO,IAAI,CAAA,gBAAA,EAAmB,IAAA,CAAK,OAAO,cAAc,CAAA,EAAA;AAAA;AAC5E,WACF;AAAA,QACF;AAAA,MACF,CAAA,EAAG,IAAA,CAAK,MAAA,CAAO,cAAc,CAAA;AAE7B,MAAA,IAAA,CAAK,MAAM,IAAA,CAAK;AAAA,QACd,EAAA,EAAI,OAAA;AAAA,QACJ,SAAA;AAAA,QACA,IAAA;AAAA,QACA,SAAS,CAAA,KAAA,KAAS;AAChB,UAAA,YAAA,CAAa,SAAS,CAAA;AACtB,UAAA,OAAA,CAAQ,KAAU,CAAA;AAAA,QACpB,CAAA;AAAA,QACA,QAAQ,CAAA,MAAA,KAAU;AAChB,UAAA,YAAA,CAAa,SAAS,CAAA;AACtB,UAAA,MAAA,CAAO,MAAM,CAAA;AAAA,QACf,CAAA;AAAA,QACA,QAAA,EAAU;AAAA,OACX,CAAA;AAED,MAAA,IAAA,CAAK,YAAA,EAAa;AAAA,IACpB,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAc,OAAA,CAAW,IAAA,EAAwB,SAAA,EAA+B;AAC9E,IAAA,IAAA,CAAK,WAAA,EAAA;AACL,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,IAAA,EAAK;AAC1B,MAAA,IAAA,CAAK,eAAA,EAAA;AACL,MAAA,IAAA,CAAK,eAAA,IAAmB,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AACrC,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,WAAA,EAAA;AACL,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,WAAA,EAAA;AACL,MAAA,IAAA,CAAK,YAAA,EAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAc,YAAA,CAAgB,IAAA,EAAwB,SAAA,EAA+B;AACnF,IAAA,MAAM,UAAA,GAAa,CAAA;AACnB,IAAA,MAAM,SAAA,GAAY,GAAA;AAElB,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,UAAA,EAAY,OAAA,EAAA,EAAW;AACtD,MAAA,IAAI,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,MAAA,CAAO,kBAAA,EAAoB;AACrD,QAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,SAAS,CAAA;AAAA,MACrC;AACA,MAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAA,GAAU,CAAC,CAAC,CAAC,CAAA;AAAA,IACxF;AAEA,IAAA,IAAA,CAAK,aAAA,EAAA;AACL,IAAA,MAAM,IAAI,qBAAA;AAAA,MACR,CAAA,UAAA,EAAa,IAAA,CAAK,MAAA,CAAO,IAAI,oBAAoB,UAAU,CAAA,QAAA;AAAA,KAC7D;AAAA,EACF;AAAA,EAEQ,YAAA,GAAqB;AAC3B,IAAA,OAAO,IAAA,CAAK,cAAc,IAAA,CAAK,MAAA,CAAO,sBAAsB,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA,EAAG;AACjF,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,KAAA,EAAM;AAC9B,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,YAAA,CAAa,KAAK,SAAS,CAAA;AAE3B,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK,QAAA;AACnC,MAAA,IAAI,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,cAAA,EAAgB;AACzC,QAAA,IAAA,CAAK,OAAO,IAAI,oBAAA,CAAqB,CAAA,qBAAA,EAAwB,QAAQ,aAAa,CAAC,CAAA;AACnF,QAAA,IAAA,CAAK,aAAA,EAAA;AACL,QAAA;AAAA,MACF;AAEA,MAAA,IAAA,CAAK,WAAA,EAAA;AACL,MAAA,IAAA,CACG,IAAA,EAAK,CACL,IAAA,CAAK,CAAA,MAAA,KAAU;AACd,QAAA,IAAA,CAAK,eAAA,EAAA;AACL,QAAA,IAAA,CAAK,QAAQ,MAAM,CAAA;AAAA,MACrB,CAAC,CAAA,CACA,KAAA,CAAM,CAAA,KAAA,KAAS;AACd,QAAA,IAAA,CAAK,WAAA,EAAA;AACL,QAAA,IAAA,CAAK,OAAO,KAAK,CAAA;AAAA,MACnB,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,QAAA,IAAA,CAAK,WAAA,EAAA;AACL,QAAA,IAAA,CAAK,YAAA,EAAa;AAAA,MACpB,CAAC,CAAA;AAAA,IACL;AAAA,EACF;AAAA,EAEA,UAAA,GAA8B;AAC5B,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,KAAK,MAAA,CAAO,IAAA;AAAA,MAClB,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,WAAA,EAAa,KAAK,KAAA,CAAM,MAAA;AAAA,MACxB,kBAAA,EAAoB,KAAK,MAAA,CAAO,kBAAA;AAAA,MAChC,YAAA,EAAc,KAAK,MAAA,CAAO,YAAA;AAAA,MAC1B,YAAY,IAAA,CAAK,UAAA;AAAA,MACjB,iBAAiB,IAAA,CAAK,eAAA;AAAA,MACtB,aAAa,IAAA,CAAK,WAAA;AAAA,MAClB,eAAe,IAAA,CAAK,aAAA;AAAA,MACpB,eAAe,IAAA,CAAK,aAAA;AAAA,MACpB,iBAAA,EACE,IAAA,CAAK,eAAA,GAAkB,CAAA,GAAI,IAAA,CAAK,MAAM,IAAA,CAAK,eAAA,GAAkB,IAAA,CAAK,eAAe,CAAA,GAAI;AAAA,KACzF;AAAA,EACF;AAAA,EAEA,SAAA,GAAqB;AACnB,IAAA,OACE,IAAA,CAAK,cAAc,IAAA,CAAK,MAAA,CAAO,sBAC/B,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,YAAA;AAAA,EAEpC;AAAA,EAEA,YAAA,GAAqB;AACnB,IAAA,IAAA,CAAK,UAAA,GAAa,CAAA;AAClB,IAAA,IAAA,CAAK,eAAA,GAAkB,CAAA;AACvB,IAAA,IAAA,CAAK,WAAA,GAAc,CAAA;AACnB,IAAA,IAAA,CAAK,aAAA,GAAgB,CAAA;AACrB,IAAA,IAAA,CAAK,aAAA,GAAgB,CAAA;AACrB,IAAA,IAAA,CAAK,eAAA,GAAkB,CAAA;AAAA,EACzB;AACF;;;AClNA,IAAM,cAAA,GAA+C;AAAA,EACnD,kBAAA,EAAoB,EAAA;AAAA,EACpB,YAAA,EAAc,GAAA;AAAA,EACd,cAAA,EAAgB,GAAA;AAAA,EAChB,cAAA,EAAgB;AAClB,CAAA;AAEO,IAAM,mBAAN,MAAuB;AAAA,EACX,SAAA,uBAAgB,GAAA,EAAsB;AAAA,EAEvD,YAAY,OAAA,EAAoC;AAC9C,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA,EAAG;AACrC,MAAA,MAAM,MAAA,GAAyB,EAAE,GAAG,cAAA,EAAgB,GAAG,OAAA,EAAS,IAAA,EAAM,QAAQ,IAAA,EAAK;AACnF,MAAA,IAAA,CAAK,UAAU,GAAA,CAAI,OAAA,CAAQ,MAAM,IAAI,QAAA,CAAS,MAAM,CAAC,CAAA;AAAA,IACvD;AACA,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AAAA,EACxC;AAAA;AAAA,EAGA,YAAA,CAAa,UAAkB,QAAA,EAA6B;AAC1D,IAAA,MAAM,IAAA,GAAO,WAAW,CAAA,OAAA,EAAU,QAAQ,IAAI,QAAQ,CAAA,CAAA,GAAK,UAAU,QAAQ,CAAA,CAAA;AAC7E,IAAA,OAAO,IAAA,CAAK,YAAY,EAAE,IAAA,EAAM,oBAAoB,CAAA,EAAG,YAAA,EAAc,IAAI,CAAA;AAAA,EAC3E;AAAA;AAAA,EAGA,cAAc,WAAA,EAA+B;AAC3C,IAAA,OAAO,KAAK,WAAA,CAAY;AAAA,MACtB,IAAA,EAAM,WAAW,WAAW,CAAA,CAAA;AAAA,MAC5B,kBAAA,EAAoB,EAAA;AAAA,MACpB,YAAA,EAAc;AAAA,KACf,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,eAAe,MAAA,EAA0B;AACvC,IAAA,OAAO,KAAK,WAAA,CAAY;AAAA,MACtB,IAAA,EAAM,YAAY,MAAM,CAAA,CAAA;AAAA,MACxB,kBAAA,EAAoB,EAAA;AAAA,MACpB,YAAA,EAAc;AAAA,KACf,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,mBAAmB,WAAA,EAA+B;AAChD,IAAA,OAAO,KAAK,WAAA,CAAY;AAAA,MACtB,IAAA,EAAM,QAAQ,WAAW,CAAA,CAAA;AAAA,MACzB,kBAAA,EAAoB,CAAA;AAAA,MACpB,YAAA,EAAc,EAAA;AAAA,MACd,cAAA,EAAgB;AAAA,KACjB,CAAA;AAAA,EACH;AAAA,EAEA,aAAA,GAAiD;AAC/C,IAAA,MAAM,UAA2C,EAAC;AAClD,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,QAAQ,CAAA,IAAK,KAAK,SAAA,EAAW;AAC7C,MAAA,OAAA,CAAQ,IAAI,CAAA,GAAI,QAAA,CAAS,UAAA,EAAW;AAAA,IACtC;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA,EAGA,sBAAA,GAA4C;AAC1C,IAAA,MAAM,aAAgC,EAAC;AACvC,IAAA,KAAA,MAAW,QAAA,IAAY,IAAA,CAAK,SAAA,CAAU,MAAA,EAAO,EAAG;AAC9C,MAAA,MAAM,CAAA,GAAI,SAAS,UAAA,EAAW;AAC9B,MAAA,IAAI,CAAA,CAAE,WAAA,IAAe,CAAA,CAAE,kBAAA,GAAqB,GAAA,EAAK;AAC/C,QAAA,UAAA,CAAW,KAAK,CAAC,CAAA;AAAA,MACnB;AAAA,IACF;AACA,IAAA,OAAO,UAAA;AAAA,EACT;AAAA,EAEA,eAAA,GAAwB;AACtB,IAAA,KAAA,MAAW,QAAA,IAAY,IAAA,CAAK,SAAA,CAAU,MAAA,EAAO,EAAG;AAC9C,MAAA,QAAA,CAAS,YAAA,EAAa;AAAA,IACxB;AAAA,EACF;AACF","file":"chunk-OROUPJGG.js","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 timeoutId: ReturnType<typeof setTimeout>;\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 config: BulkheadConfig) {}\n\n updateConfig(partial: Partial<Omit<BulkheadConfig, 'name'>>): void {\n this.config = { ...this.config, ...partial };\n }\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) {\n 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 }\n }, this.config.queueTimeoutMs);\n\n this.queue.push({\n id: entryId,\n timeoutId,\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 clearTimeout(next.timeoutId);\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"]}
package/dist/index.cjs CHANGED
@@ -1,24 +1,24 @@
1
1
  'use strict';
2
2
 
3
- var chunkLXXCDKHB_cjs = require('./chunk-LXXCDKHB.cjs');
3
+ var chunk2HCEIIKT_cjs = require('./chunk-2HCEIIKT.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "Bulkhead", {
8
8
  enumerable: true,
9
- get: function () { return chunkLXXCDKHB_cjs.Bulkhead; }
9
+ get: function () { return chunk2HCEIIKT_cjs.Bulkhead; }
10
10
  });
11
11
  Object.defineProperty(exports, "BulkheadRegistry", {
12
12
  enumerable: true,
13
- get: function () { return chunkLXXCDKHB_cjs.BulkheadRegistry; }
13
+ get: function () { return chunk2HCEIIKT_cjs.BulkheadRegistry; }
14
14
  });
15
15
  Object.defineProperty(exports, "BulkheadRejectedError", {
16
16
  enumerable: true,
17
- get: function () { return chunkLXXCDKHB_cjs.BulkheadRejectedError; }
17
+ get: function () { return chunk2HCEIIKT_cjs.BulkheadRejectedError; }
18
18
  });
19
19
  Object.defineProperty(exports, "BulkheadTimeoutError", {
20
20
  enumerable: true,
21
- get: function () { return chunkLXXCDKHB_cjs.BulkheadTimeoutError; }
21
+ get: function () { return chunk2HCEIIKT_cjs.BulkheadTimeoutError; }
22
22
  });
23
23
  //# sourceMappingURL=index.cjs.map
24
24
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.cts CHANGED
@@ -30,7 +30,7 @@ declare class BulkheadTimeoutError extends Error {
30
30
  constructor(message: string);
31
31
  }
32
32
  declare class Bulkhead {
33
- private readonly config;
33
+ private config;
34
34
  private activeCalls;
35
35
  private nextId;
36
36
  private queue;
@@ -41,6 +41,7 @@ declare class Bulkhead {
41
41
  private timedOutCalls;
42
42
  private totalDurationMs;
43
43
  constructor(config: BulkheadConfig);
44
+ updateConfig(partial: Partial<Omit<BulkheadConfig, 'name'>>): void;
44
45
  execute<T>(task: () => Promise<T>): Promise<T>;
45
46
  private runTask;
46
47
  private waitAndRetry;
package/dist/index.d.ts CHANGED
@@ -30,7 +30,7 @@ declare class BulkheadTimeoutError extends Error {
30
30
  constructor(message: string);
31
31
  }
32
32
  declare class Bulkhead {
33
- private readonly config;
33
+ private config;
34
34
  private activeCalls;
35
35
  private nextId;
36
36
  private queue;
@@ -41,6 +41,7 @@ declare class Bulkhead {
41
41
  private timedOutCalls;
42
42
  private totalDurationMs;
43
43
  constructor(config: BulkheadConfig);
44
+ updateConfig(partial: Partial<Omit<BulkheadConfig, 'name'>>): void;
44
45
  execute<T>(task: () => Promise<T>): Promise<T>;
45
46
  private runTask;
46
47
  private waitAndRetry;
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Bulkhead, BulkheadRegistry, BulkheadRejectedError, BulkheadTimeoutError } from './chunk-SRWDZPTJ.js';
1
+ export { Bulkhead, BulkheadRegistry, BulkheadRejectedError, BulkheadTimeoutError } from './chunk-OROUPJGG.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkLXXCDKHB_cjs = require('../chunk-LXXCDKHB.cjs');
3
+ var chunk2HCEIIKT_cjs = require('../chunk-2HCEIIKT.cjs');
4
4
  var common = require('@nestjs/common');
5
5
  var core = require('@nestjs/core');
6
6
  var rxjs = require('rxjs');
@@ -50,9 +50,9 @@ exports.BulkheadService = class BulkheadService {
50
50
  }, 6e4);
51
51
  }
52
52
  };
53
- exports.BulkheadService = chunkLXXCDKHB_cjs.__decorateClass([
53
+ exports.BulkheadService = chunk2HCEIIKT_cjs.__decorateClass([
54
54
  common.Injectable(),
55
- chunkLXXCDKHB_cjs.__decorateParam(0, common.Inject(chunkLXXCDKHB_cjs.BulkheadRegistry))
55
+ chunk2HCEIIKT_cjs.__decorateParam(0, common.Inject(chunk2HCEIIKT_cjs.BulkheadRegistry))
56
56
  ], exports.BulkheadService);
57
57
  var UseBulkhead = (options = {}) => core.Reflector.createDecorator({ key: "bulkhead", transform: () => options });
58
58
  exports.BulkheadGuard = class BulkheadGuard {
@@ -75,10 +75,10 @@ exports.BulkheadGuard = class BulkheadGuard {
75
75
  return true;
76
76
  }
77
77
  };
78
- exports.BulkheadGuard = chunkLXXCDKHB_cjs.__decorateClass([
78
+ exports.BulkheadGuard = chunk2HCEIIKT_cjs.__decorateClass([
79
79
  common.Injectable(),
80
- chunkLXXCDKHB_cjs.__decorateParam(0, common.Inject(core.Reflector)),
81
- chunkLXXCDKHB_cjs.__decorateParam(1, common.Inject(chunkLXXCDKHB_cjs.BulkheadRegistry))
80
+ chunk2HCEIIKT_cjs.__decorateParam(0, common.Inject(core.Reflector)),
81
+ chunk2HCEIIKT_cjs.__decorateParam(1, common.Inject(chunk2HCEIIKT_cjs.BulkheadRegistry))
82
82
  ], exports.BulkheadGuard);
83
83
  exports.BulkheadInterceptor = class BulkheadInterceptor {
84
84
  constructor(registry) {
@@ -97,12 +97,12 @@ exports.BulkheadInterceptor = class BulkheadInterceptor {
97
97
  subscriber.complete();
98
98
  });
99
99
  } catch (error) {
100
- if (error instanceof chunkLXXCDKHB_cjs.BulkheadRejectedError) {
100
+ if (error instanceof chunk2HCEIIKT_cjs.BulkheadRejectedError) {
101
101
  throw new common.ServiceUnavailableException(
102
102
  "Service at capacity \u2014 please retry in a moment"
103
103
  );
104
104
  }
105
- if (error instanceof chunkLXXCDKHB_cjs.BulkheadTimeoutError) {
105
+ if (error instanceof chunk2HCEIIKT_cjs.BulkheadTimeoutError) {
106
106
  throw new common.RequestTimeoutException(
107
107
  "Request timed out waiting for processing \u2014 please retry"
108
108
  );
@@ -111,9 +111,9 @@ exports.BulkheadInterceptor = class BulkheadInterceptor {
111
111
  }
112
112
  }
113
113
  };
114
- exports.BulkheadInterceptor = chunkLXXCDKHB_cjs.__decorateClass([
114
+ exports.BulkheadInterceptor = chunk2HCEIIKT_cjs.__decorateClass([
115
115
  common.Injectable(),
116
- chunkLXXCDKHB_cjs.__decorateParam(0, common.Inject(chunkLXXCDKHB_cjs.BulkheadRegistry))
116
+ chunk2HCEIIKT_cjs.__decorateParam(0, common.Inject(chunk2HCEIIKT_cjs.BulkheadRegistry))
117
117
  ], exports.BulkheadInterceptor);
118
118
  var DEFAULT_CONCURRENCY = 50;
119
119
  var DEFAULT_MAX_QUEUE = 100;
@@ -121,15 +121,11 @@ exports.HttpBulkheadMiddleware = class HttpBulkheadMiddleware {
121
121
  logger = new common.Logger(exports.HttpBulkheadMiddleware.name);
122
122
  bulkhead;
123
123
  constructor() {
124
- const concurrency = parseInt(
125
- process.env["HTTP_BULKHEAD_CONCURRENCY"] ?? String(DEFAULT_CONCURRENCY),
126
- 10
127
- );
128
- const maxQueueSize = parseInt(
129
- process.env["HTTP_BULKHEAD_MAX_QUEUE"] ?? String(DEFAULT_MAX_QUEUE),
130
- 10
131
- );
132
- this.bulkhead = new chunkLXXCDKHB_cjs.Bulkhead({
124
+ const rawConcurrency = parseInt(process.env["HTTP_BULKHEAD_CONCURRENCY"] ?? "", 10);
125
+ const concurrency = Number.isNaN(rawConcurrency) || rawConcurrency < 1 ? DEFAULT_CONCURRENCY : rawConcurrency;
126
+ const rawMaxQueue = parseInt(process.env["HTTP_BULKHEAD_MAX_QUEUE"] ?? "", 10);
127
+ const maxQueueSize = Number.isNaN(rawMaxQueue) || rawMaxQueue < 0 ? DEFAULT_MAX_QUEUE : rawMaxQueue;
128
+ this.bulkhead = new chunk2HCEIIKT_cjs.Bulkhead({
133
129
  name: "http:global",
134
130
  maxConcurrentCalls: concurrency,
135
131
  maxQueueSize,
@@ -149,7 +145,7 @@ exports.HttpBulkheadMiddleware = class HttpBulkheadMiddleware {
149
145
  })
150
146
  ).catch((error) => {
151
147
  if (res.headersSent) return;
152
- if (error instanceof chunkLXXCDKHB_cjs.BulkheadRejectedError) {
148
+ if (error instanceof chunk2HCEIIKT_cjs.BulkheadRejectedError) {
153
149
  const m = this.bulkhead.getMetrics();
154
150
  this.logger.warn(
155
151
  `HTTP Bulkhead saturated [active=${m.activeCalls} queued=${m.queuedCalls}] \u2014 rejecting ${req.method} ${req.path}`
@@ -159,7 +155,7 @@ exports.HttpBulkheadMiddleware = class HttpBulkheadMiddleware {
159
155
  error: "Too Many Requests",
160
156
  message: "Service temporarily overloaded. Please retry in a few seconds."
161
157
  });
162
- } else if (error instanceof chunkLXXCDKHB_cjs.BulkheadTimeoutError) {
158
+ } else if (error instanceof chunk2HCEIIKT_cjs.BulkheadTimeoutError) {
163
159
  res.status(503).json({
164
160
  statusCode: 503,
165
161
  error: "Service Unavailable",
@@ -172,24 +168,24 @@ exports.HttpBulkheadMiddleware = class HttpBulkheadMiddleware {
172
168
  return this.bulkhead.getMetrics();
173
169
  }
174
170
  };
175
- exports.HttpBulkheadMiddleware = chunkLXXCDKHB_cjs.__decorateClass([
171
+ exports.HttpBulkheadMiddleware = chunk2HCEIIKT_cjs.__decorateClass([
176
172
  common.Injectable()
177
173
  ], exports.HttpBulkheadMiddleware);
178
174
 
179
175
  // src/nestjs/bulkhead.module.ts
180
176
  exports.BulkheadModule = class BulkheadModule {
181
177
  };
182
- exports.BulkheadModule = chunkLXXCDKHB_cjs.__decorateClass([
178
+ exports.BulkheadModule = chunk2HCEIIKT_cjs.__decorateClass([
183
179
  common.Module({
184
180
  providers: [
185
- chunkLXXCDKHB_cjs.BulkheadRegistry,
181
+ chunk2HCEIIKT_cjs.BulkheadRegistry,
186
182
  exports.BulkheadService,
187
183
  exports.BulkheadGuard,
188
184
  exports.BulkheadInterceptor,
189
185
  exports.HttpBulkheadMiddleware
190
186
  ],
191
187
  exports: [
192
- chunkLXXCDKHB_cjs.BulkheadRegistry,
188
+ chunk2HCEIIKT_cjs.BulkheadRegistry,
193
189
  exports.BulkheadService,
194
190
  exports.BulkheadGuard,
195
191
  exports.BulkheadInterceptor,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/nestjs/bulkhead.service.ts","../../src/nestjs/bulkhead.guard.ts","../../src/nestjs/bulkhead.interceptor.ts","../../src/nestjs/http-bulkhead.middleware.ts","../../src/nestjs/bulkhead.module.ts","../../src/nestjs/bulkhead.decorator.ts"],"names":["BulkheadService","Logger","__decorateClass","Injectable","__decorateParam","BulkheadRegistry","Reflector","BulkheadGuard","ServiceUnavailableException","Inject","BulkheadInterceptor","firstValueFrom","Observable","BulkheadRejectedError","BulkheadTimeoutError","RequestTimeoutException","HttpBulkheadMiddleware","Bulkhead","BulkheadModule","Module"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Injectable, Inject, Logger, OnModuleDestroy } from '@nestjs/common';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport type { BulkheadMetrics } from '../bulkhead/bulkhead.js';\n\n@Injectable()\nexport class BulkheadService implements OnModuleDestroy {\n private readonly logger = new Logger(BulkheadService.name);\n private monitorInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(@Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry) {\n this.startMonitoring();\n }\n\n onModuleDestroy(): void {\n if (this.monitorInterval) {\n clearInterval(this.monitorInterval);\n this.monitorInterval = null;\n }\n }\n\n getAllMetrics(): Record<string, BulkheadMetrics> {\n return this.registry.getAllMetrics();\n }\n\n getCriticalBulkheads(): BulkheadMetrics[] {\n return Object.values(this.registry.getAllMetrics()).filter(\n m => m.activeCalls / m.maxConcurrentCalls > 0.9,\n );\n }\n\n resetMetrics(name?: string): void {\n if (name) {\n this.registry.getOrCreate({ name }).resetMetrics();\n } else {\n this.registry.resetAllMetrics();\n }\n }\n\n private startMonitoring(): void {\n this.monitorInterval = setInterval(() => {\n const critical = this.getCriticalBulkheads();\n if (critical.length > 0) {\n this.logger.warn(\n `Critical bulkheads detected: ${critical.length}`,\n critical.map(c => ({\n name: c.name,\n utilization: `${Math.round((c.activeCalls / c.maxConcurrentCalls) * 100)}%`,\n queued: c.queuedCalls,\n })),\n );\n }\n }, 60_000);\n }\n}\n","import {\n Injectable,\n Inject,\n CanActivate,\n ExecutionContext,\n ServiceUnavailableException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\n\nexport interface BulkheadGuardOptions {\n /** Named bulkhead to use; defaults to the route path */\n name?: string;\n /** Create a separate bulkhead per x-client-id header value */\n perClient?: boolean;\n maxConcurrent?: number;\n timeoutMs?: number;\n}\n\nexport const UseBulkhead = (options: BulkheadGuardOptions = {}) =>\n Reflector.createDecorator<BulkheadGuardOptions>({ key: 'bulkhead', transform: () => options });\n\n@Injectable()\nexport class BulkheadGuard implements CanActivate {\n constructor(\n @Inject(Reflector) private readonly reflector: Reflector,\n @Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const options = this.reflector.get<BulkheadGuardOptions>('bulkhead', context.getHandler());\n if (!options) return true;\n\n const request = context.switchToHttp().getRequest();\n const clientId: string = request.headers['x-client-id'] ?? 'anonymous';\n\n const bulkhead = options.perClient\n ? this.registry.getForClient(clientId, options.name ?? (request.route?.path as string))\n : this.registry.getForService(options.name ?? 'default');\n\n if (!bulkhead.canAccept()) {\n throw new ServiceUnavailableException('Service temporarily unavailable due to high load');\n }\n\n (request as Record<string, unknown>)['bulkhead'] = bulkhead;\n return true;\n }\n}\n","import {\n Injectable,\n Inject,\n NestInterceptor,\n ExecutionContext,\n CallHandler,\n ServiceUnavailableException,\n RequestTimeoutException,\n} from '@nestjs/common';\nimport { firstValueFrom, Observable } from 'rxjs';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport { BulkheadRejectedError, BulkheadTimeoutError } from '../bulkhead/bulkhead.js';\n\n@Injectable()\nexport class BulkheadInterceptor implements NestInterceptor {\n constructor(@Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry) {}\n\n async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<unknown>> {\n const request = context.switchToHttp().getRequest();\n const clientId: string = request.headers['x-client-id'] ?? 'anonymous';\n const handlerName: string = context.getHandler().name;\n\n const bulkhead = this.registry.getForClient(clientId, handlerName);\n\n try {\n const result = await bulkhead.execute(() => firstValueFrom(next.handle()));\n return new Observable(subscriber => {\n subscriber.next(result);\n subscriber.complete();\n });\n } catch (error) {\n if (error instanceof BulkheadRejectedError) {\n throw new ServiceUnavailableException(\n 'Service at capacity — please retry in a moment',\n );\n }\n if (error instanceof BulkheadTimeoutError) {\n throw new RequestTimeoutException(\n 'Request timed out waiting for processing — please retry',\n );\n }\n throw error;\n }\n }\n}\n","import { Injectable, NestMiddleware, Logger } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { Bulkhead, BulkheadRejectedError, BulkheadTimeoutError } from '../bulkhead/bulkhead.js';\nimport type { BulkheadMetrics } from '../bulkhead/bulkhead.js';\n\nconst DEFAULT_CONCURRENCY = 50;\nconst DEFAULT_MAX_QUEUE = 100;\n\n/**\n * Global HTTP middleware that limits simultaneous in-flight requests.\n *\n * Configure via environment variables:\n * HTTP_BULKHEAD_CONCURRENCY — max concurrent requests (default: 50)\n * HTTP_BULKHEAD_MAX_QUEUE — max queued requests (default: 100)\n */\n@Injectable()\nexport class HttpBulkheadMiddleware implements NestMiddleware {\n private readonly logger = new Logger(HttpBulkheadMiddleware.name);\n private readonly bulkhead: Bulkhead;\n\n constructor() {\n const concurrency = parseInt(\n process.env['HTTP_BULKHEAD_CONCURRENCY'] ?? String(DEFAULT_CONCURRENCY),\n 10,\n );\n const maxQueueSize = parseInt(\n process.env['HTTP_BULKHEAD_MAX_QUEUE'] ?? String(DEFAULT_MAX_QUEUE),\n 10,\n );\n\n this.bulkhead = new Bulkhead({\n name: 'http:global',\n maxConcurrentCalls: concurrency,\n maxQueueSize,\n queueTimeoutMs: 30_000,\n rejectWhenFull: true,\n });\n\n this.logger.log(\n `HTTP Bulkhead ready — concurrency: ${concurrency}, maxQueue: ${maxQueueSize}`,\n );\n }\n\n use(req: Request, res: Response, next: NextFunction): void {\n this.bulkhead\n .execute(\n () =>\n new Promise<void>(resolve => {\n res.once('finish', resolve);\n res.once('close', resolve);\n next();\n }),\n )\n .catch((error: unknown) => {\n if (res.headersSent) return;\n if (error instanceof BulkheadRejectedError) {\n const m = this.bulkhead.getMetrics();\n this.logger.warn(\n `HTTP Bulkhead saturated [active=${m.activeCalls} queued=${m.queuedCalls}]` +\n ` — rejecting ${req.method} ${req.path}`,\n );\n res.status(429).json({\n statusCode: 429,\n error: 'Too Many Requests',\n message: 'Service temporarily overloaded. Please retry in a few seconds.',\n });\n } else if (error instanceof BulkheadTimeoutError) {\n res.status(503).json({\n statusCode: 503,\n error: 'Service Unavailable',\n message: 'Request timed out waiting for processing.',\n });\n }\n });\n }\n\n getStats(): BulkheadMetrics {\n return this.bulkhead.getMetrics();\n }\n}\n","import { Module } from '@nestjs/common';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport { BulkheadService } from './bulkhead.service.js';\nimport { BulkheadGuard } from './bulkhead.guard.js';\nimport { BulkheadInterceptor } from './bulkhead.interceptor.js';\nimport { HttpBulkheadMiddleware } from './http-bulkhead.middleware.js';\n\n@Module({\n providers: [\n BulkheadRegistry,\n BulkheadService,\n BulkheadGuard,\n BulkheadInterceptor,\n HttpBulkheadMiddleware,\n ],\n exports: [\n BulkheadRegistry,\n BulkheadService,\n BulkheadGuard,\n BulkheadInterceptor,\n HttpBulkheadMiddleware,\n ],\n})\nexport class BulkheadModule {}\n","import { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\n\n/**\n * Method decorator that wraps execution inside a named bulkhead.\n * The class must have `bulkheadRegistry: BulkheadRegistry` injected.\n */\nexport function WithBulkhead(options: {\n name: string;\n maxConcurrent?: number;\n timeoutMs?: number;\n}) {\n return function (\n _target: unknown,\n _propertyKey: string,\n descriptor: PropertyDescriptor,\n ): PropertyDescriptor {\n const originalMethod = descriptor.value as (...args: unknown[]) => Promise<unknown>;\n\n descriptor.value = async function (...args: unknown[]) {\n const registry = (this as { bulkheadRegistry?: BulkheadRegistry }).bulkheadRegistry;\n if (!registry) throw new Error('BulkheadRegistry not injected in class.');\n\n const bulkhead = registry.getOrCreate({\n name: options.name,\n maxConcurrentCalls: options.maxConcurrent ?? 5,\n queueTimeoutMs: options.timeoutMs ?? 30_000,\n maxQueueSize: 50,\n rejectWhenFull: true,\n });\n\n return bulkhead.execute(() => originalMethod.apply(this, args));\n };\n\n return descriptor;\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/nestjs/bulkhead.service.ts","../../src/nestjs/bulkhead.guard.ts","../../src/nestjs/bulkhead.interceptor.ts","../../src/nestjs/http-bulkhead.middleware.ts","../../src/nestjs/bulkhead.module.ts","../../src/nestjs/bulkhead.decorator.ts"],"names":["BulkheadService","Logger","__decorateClass","Injectable","__decorateParam","BulkheadRegistry","Reflector","BulkheadGuard","ServiceUnavailableException","Inject","BulkheadInterceptor","firstValueFrom","Observable","BulkheadRejectedError","BulkheadTimeoutError","RequestTimeoutException","HttpBulkheadMiddleware","Bulkhead","BulkheadModule","Module"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Injectable, Inject, Logger, OnModuleDestroy } from '@nestjs/common';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport type { BulkheadMetrics } from '../bulkhead/bulkhead.js';\n\n@Injectable()\nexport class BulkheadService implements OnModuleDestroy {\n private readonly logger = new Logger(BulkheadService.name);\n private monitorInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(@Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry) {\n this.startMonitoring();\n }\n\n onModuleDestroy(): void {\n if (this.monitorInterval) {\n clearInterval(this.monitorInterval);\n this.monitorInterval = null;\n }\n }\n\n getAllMetrics(): Record<string, BulkheadMetrics> {\n return this.registry.getAllMetrics();\n }\n\n getCriticalBulkheads(): BulkheadMetrics[] {\n return Object.values(this.registry.getAllMetrics()).filter(\n m => m.activeCalls / m.maxConcurrentCalls > 0.9,\n );\n }\n\n resetMetrics(name?: string): void {\n if (name) {\n this.registry.getOrCreate({ name }).resetMetrics();\n } else {\n this.registry.resetAllMetrics();\n }\n }\n\n private startMonitoring(): void {\n this.monitorInterval = setInterval(() => {\n const critical = this.getCriticalBulkheads();\n if (critical.length > 0) {\n this.logger.warn(\n `Critical bulkheads detected: ${critical.length}`,\n critical.map(c => ({\n name: c.name,\n utilization: `${Math.round((c.activeCalls / c.maxConcurrentCalls) * 100)}%`,\n queued: c.queuedCalls,\n })),\n );\n }\n }, 60_000);\n }\n}\n","import {\n Injectable,\n Inject,\n CanActivate,\n ExecutionContext,\n ServiceUnavailableException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\n\nexport interface BulkheadGuardOptions {\n /** Named bulkhead to use; defaults to the route path */\n name?: string;\n /** Create a separate bulkhead per x-client-id header value */\n perClient?: boolean;\n maxConcurrent?: number;\n timeoutMs?: number;\n}\n\nexport const UseBulkhead = (options: BulkheadGuardOptions = {}) =>\n Reflector.createDecorator<BulkheadGuardOptions>({ key: 'bulkhead', transform: () => options });\n\n@Injectable()\nexport class BulkheadGuard implements CanActivate {\n constructor(\n @Inject(Reflector) private readonly reflector: Reflector,\n @Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const options = this.reflector.get<BulkheadGuardOptions>('bulkhead', context.getHandler());\n if (!options) return true;\n\n const request = context.switchToHttp().getRequest();\n const clientId: string = request.headers['x-client-id'] ?? 'anonymous';\n\n const bulkhead = options.perClient\n ? this.registry.getForClient(clientId, options.name ?? (request.route?.path as string))\n : this.registry.getForService(options.name ?? 'default');\n\n if (!bulkhead.canAccept()) {\n throw new ServiceUnavailableException('Service temporarily unavailable due to high load');\n }\n\n (request as Record<string, unknown>)['bulkhead'] = bulkhead;\n return true;\n }\n}\n","import {\n Injectable,\n Inject,\n NestInterceptor,\n ExecutionContext,\n CallHandler,\n ServiceUnavailableException,\n RequestTimeoutException,\n} from '@nestjs/common';\nimport { firstValueFrom, Observable } from 'rxjs';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport { BulkheadRejectedError, BulkheadTimeoutError } from '../bulkhead/bulkhead.js';\n\n@Injectable()\nexport class BulkheadInterceptor implements NestInterceptor {\n constructor(@Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry) {}\n\n async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<unknown>> {\n const request = context.switchToHttp().getRequest();\n const clientId: string = request.headers['x-client-id'] ?? 'anonymous';\n const handlerName: string = context.getHandler().name;\n\n const bulkhead = this.registry.getForClient(clientId, handlerName);\n\n try {\n const result = await bulkhead.execute(() => firstValueFrom(next.handle()));\n return new Observable(subscriber => {\n subscriber.next(result);\n subscriber.complete();\n });\n } catch (error) {\n if (error instanceof BulkheadRejectedError) {\n throw new ServiceUnavailableException(\n 'Service at capacity — please retry in a moment',\n );\n }\n if (error instanceof BulkheadTimeoutError) {\n throw new RequestTimeoutException(\n 'Request timed out waiting for processing — please retry',\n );\n }\n throw error;\n }\n }\n}\n","import { Injectable, NestMiddleware, Logger } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { Bulkhead, BulkheadRejectedError, BulkheadTimeoutError } from '../bulkhead/bulkhead.js';\nimport type { BulkheadMetrics } from '../bulkhead/bulkhead.js';\n\nconst DEFAULT_CONCURRENCY = 50;\nconst DEFAULT_MAX_QUEUE = 100;\n\n/**\n * Global HTTP middleware that limits simultaneous in-flight requests.\n *\n * Configure via environment variables:\n * HTTP_BULKHEAD_CONCURRENCY — max concurrent requests (default: 50)\n * HTTP_BULKHEAD_MAX_QUEUE — max queued requests (default: 100)\n */\n@Injectable()\nexport class HttpBulkheadMiddleware implements NestMiddleware {\n private readonly logger = new Logger(HttpBulkheadMiddleware.name);\n private readonly bulkhead: Bulkhead;\n\n constructor() {\n const rawConcurrency = parseInt(process.env['HTTP_BULKHEAD_CONCURRENCY'] ?? '', 10);\n const concurrency =\n Number.isNaN(rawConcurrency) || rawConcurrency < 1 ? DEFAULT_CONCURRENCY : rawConcurrency;\n\n const rawMaxQueue = parseInt(process.env['HTTP_BULKHEAD_MAX_QUEUE'] ?? '', 10);\n const maxQueueSize =\n Number.isNaN(rawMaxQueue) || rawMaxQueue < 0 ? DEFAULT_MAX_QUEUE : rawMaxQueue;\n\n this.bulkhead = new Bulkhead({\n name: 'http:global',\n maxConcurrentCalls: concurrency,\n maxQueueSize,\n queueTimeoutMs: 30_000,\n rejectWhenFull: true,\n });\n\n this.logger.log(\n `HTTP Bulkhead ready — concurrency: ${concurrency}, maxQueue: ${maxQueueSize}`,\n );\n }\n\n use(req: Request, res: Response, next: NextFunction): void {\n this.bulkhead\n .execute(\n () =>\n new Promise<void>(resolve => {\n res.once('finish', resolve);\n res.once('close', resolve);\n next();\n }),\n )\n .catch((error: unknown) => {\n if (res.headersSent) return;\n if (error instanceof BulkheadRejectedError) {\n const m = this.bulkhead.getMetrics();\n this.logger.warn(\n `HTTP Bulkhead saturated [active=${m.activeCalls} queued=${m.queuedCalls}]` +\n ` — rejecting ${req.method} ${req.path}`,\n );\n res.status(429).json({\n statusCode: 429,\n error: 'Too Many Requests',\n message: 'Service temporarily overloaded. Please retry in a few seconds.',\n });\n } else if (error instanceof BulkheadTimeoutError) {\n res.status(503).json({\n statusCode: 503,\n error: 'Service Unavailable',\n message: 'Request timed out waiting for processing.',\n });\n }\n });\n }\n\n getStats(): BulkheadMetrics {\n return this.bulkhead.getMetrics();\n }\n}\n","import { Module } from '@nestjs/common';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport { BulkheadService } from './bulkhead.service.js';\nimport { BulkheadGuard } from './bulkhead.guard.js';\nimport { BulkheadInterceptor } from './bulkhead.interceptor.js';\nimport { HttpBulkheadMiddleware } from './http-bulkhead.middleware.js';\n\n@Module({\n providers: [\n BulkheadRegistry,\n BulkheadService,\n BulkheadGuard,\n BulkheadInterceptor,\n HttpBulkheadMiddleware,\n ],\n exports: [\n BulkheadRegistry,\n BulkheadService,\n BulkheadGuard,\n BulkheadInterceptor,\n HttpBulkheadMiddleware,\n ],\n})\nexport class BulkheadModule {}\n","import { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\n\n/**\n * Method decorator that wraps execution inside a named bulkhead.\n * The class must have `bulkheadRegistry: BulkheadRegistry` injected.\n */\nexport function WithBulkhead(options: {\n name: string;\n maxConcurrent?: number;\n timeoutMs?: number;\n}) {\n return function (\n _target: unknown,\n _propertyKey: string,\n descriptor: PropertyDescriptor,\n ): PropertyDescriptor {\n const originalMethod = descriptor.value as (...args: unknown[]) => Promise<unknown>;\n\n descriptor.value = async function (...args: unknown[]) {\n const registry = (this as { bulkheadRegistry?: BulkheadRegistry }).bulkheadRegistry;\n if (!registry) throw new Error('BulkheadRegistry not injected in class.');\n\n const bulkhead = registry.getOrCreate({\n name: options.name,\n maxConcurrentCalls: options.maxConcurrent ?? 5,\n queueTimeoutMs: options.timeoutMs ?? 30_000,\n maxQueueSize: 50,\n rejectWhenFull: true,\n });\n\n return bulkhead.execute(() => originalMethod.apply(this, args));\n };\n\n return descriptor;\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { __decorateClass, __decorateParam, BulkheadRegistry, BulkheadRejectedError, BulkheadTimeoutError, Bulkhead } from '../chunk-SRWDZPTJ.js';
1
+ import { __decorateClass, __decorateParam, BulkheadRegistry, BulkheadRejectedError, BulkheadTimeoutError, Bulkhead } from '../chunk-OROUPJGG.js';
2
2
  import { Injectable, Inject, Module, Logger, ServiceUnavailableException, RequestTimeoutException } from '@nestjs/common';
3
3
  import { Reflector } from '@nestjs/core';
4
4
  import { firstValueFrom, Observable } from 'rxjs';
@@ -119,14 +119,10 @@ var HttpBulkheadMiddleware = class {
119
119
  logger = new Logger(HttpBulkheadMiddleware.name);
120
120
  bulkhead;
121
121
  constructor() {
122
- const concurrency = parseInt(
123
- process.env["HTTP_BULKHEAD_CONCURRENCY"] ?? String(DEFAULT_CONCURRENCY),
124
- 10
125
- );
126
- const maxQueueSize = parseInt(
127
- process.env["HTTP_BULKHEAD_MAX_QUEUE"] ?? String(DEFAULT_MAX_QUEUE),
128
- 10
129
- );
122
+ const rawConcurrency = parseInt(process.env["HTTP_BULKHEAD_CONCURRENCY"] ?? "", 10);
123
+ const concurrency = Number.isNaN(rawConcurrency) || rawConcurrency < 1 ? DEFAULT_CONCURRENCY : rawConcurrency;
124
+ const rawMaxQueue = parseInt(process.env["HTTP_BULKHEAD_MAX_QUEUE"] ?? "", 10);
125
+ const maxQueueSize = Number.isNaN(rawMaxQueue) || rawMaxQueue < 0 ? DEFAULT_MAX_QUEUE : rawMaxQueue;
130
126
  this.bulkhead = new Bulkhead({
131
127
  name: "http:global",
132
128
  maxConcurrentCalls: concurrency,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/nestjs/bulkhead.service.ts","../../src/nestjs/bulkhead.guard.ts","../../src/nestjs/bulkhead.interceptor.ts","../../src/nestjs/http-bulkhead.middleware.ts","../../src/nestjs/bulkhead.module.ts","../../src/nestjs/bulkhead.decorator.ts"],"names":["Injectable","Inject","ServiceUnavailableException","Logger"],"mappings":"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","file":"index.js","sourcesContent":["import { Injectable, Inject, Logger, OnModuleDestroy } from '@nestjs/common';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport type { BulkheadMetrics } from '../bulkhead/bulkhead.js';\n\n@Injectable()\nexport class BulkheadService implements OnModuleDestroy {\n private readonly logger = new Logger(BulkheadService.name);\n private monitorInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(@Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry) {\n this.startMonitoring();\n }\n\n onModuleDestroy(): void {\n if (this.monitorInterval) {\n clearInterval(this.monitorInterval);\n this.monitorInterval = null;\n }\n }\n\n getAllMetrics(): Record<string, BulkheadMetrics> {\n return this.registry.getAllMetrics();\n }\n\n getCriticalBulkheads(): BulkheadMetrics[] {\n return Object.values(this.registry.getAllMetrics()).filter(\n m => m.activeCalls / m.maxConcurrentCalls > 0.9,\n );\n }\n\n resetMetrics(name?: string): void {\n if (name) {\n this.registry.getOrCreate({ name }).resetMetrics();\n } else {\n this.registry.resetAllMetrics();\n }\n }\n\n private startMonitoring(): void {\n this.monitorInterval = setInterval(() => {\n const critical = this.getCriticalBulkheads();\n if (critical.length > 0) {\n this.logger.warn(\n `Critical bulkheads detected: ${critical.length}`,\n critical.map(c => ({\n name: c.name,\n utilization: `${Math.round((c.activeCalls / c.maxConcurrentCalls) * 100)}%`,\n queued: c.queuedCalls,\n })),\n );\n }\n }, 60_000);\n }\n}\n","import {\n Injectable,\n Inject,\n CanActivate,\n ExecutionContext,\n ServiceUnavailableException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\n\nexport interface BulkheadGuardOptions {\n /** Named bulkhead to use; defaults to the route path */\n name?: string;\n /** Create a separate bulkhead per x-client-id header value */\n perClient?: boolean;\n maxConcurrent?: number;\n timeoutMs?: number;\n}\n\nexport const UseBulkhead = (options: BulkheadGuardOptions = {}) =>\n Reflector.createDecorator<BulkheadGuardOptions>({ key: 'bulkhead', transform: () => options });\n\n@Injectable()\nexport class BulkheadGuard implements CanActivate {\n constructor(\n @Inject(Reflector) private readonly reflector: Reflector,\n @Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const options = this.reflector.get<BulkheadGuardOptions>('bulkhead', context.getHandler());\n if (!options) return true;\n\n const request = context.switchToHttp().getRequest();\n const clientId: string = request.headers['x-client-id'] ?? 'anonymous';\n\n const bulkhead = options.perClient\n ? this.registry.getForClient(clientId, options.name ?? (request.route?.path as string))\n : this.registry.getForService(options.name ?? 'default');\n\n if (!bulkhead.canAccept()) {\n throw new ServiceUnavailableException('Service temporarily unavailable due to high load');\n }\n\n (request as Record<string, unknown>)['bulkhead'] = bulkhead;\n return true;\n }\n}\n","import {\n Injectable,\n Inject,\n NestInterceptor,\n ExecutionContext,\n CallHandler,\n ServiceUnavailableException,\n RequestTimeoutException,\n} from '@nestjs/common';\nimport { firstValueFrom, Observable } from 'rxjs';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport { BulkheadRejectedError, BulkheadTimeoutError } from '../bulkhead/bulkhead.js';\n\n@Injectable()\nexport class BulkheadInterceptor implements NestInterceptor {\n constructor(@Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry) {}\n\n async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<unknown>> {\n const request = context.switchToHttp().getRequest();\n const clientId: string = request.headers['x-client-id'] ?? 'anonymous';\n const handlerName: string = context.getHandler().name;\n\n const bulkhead = this.registry.getForClient(clientId, handlerName);\n\n try {\n const result = await bulkhead.execute(() => firstValueFrom(next.handle()));\n return new Observable(subscriber => {\n subscriber.next(result);\n subscriber.complete();\n });\n } catch (error) {\n if (error instanceof BulkheadRejectedError) {\n throw new ServiceUnavailableException(\n 'Service at capacity — please retry in a moment',\n );\n }\n if (error instanceof BulkheadTimeoutError) {\n throw new RequestTimeoutException(\n 'Request timed out waiting for processing — please retry',\n );\n }\n throw error;\n }\n }\n}\n","import { Injectable, NestMiddleware, Logger } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { Bulkhead, BulkheadRejectedError, BulkheadTimeoutError } from '../bulkhead/bulkhead.js';\nimport type { BulkheadMetrics } from '../bulkhead/bulkhead.js';\n\nconst DEFAULT_CONCURRENCY = 50;\nconst DEFAULT_MAX_QUEUE = 100;\n\n/**\n * Global HTTP middleware that limits simultaneous in-flight requests.\n *\n * Configure via environment variables:\n * HTTP_BULKHEAD_CONCURRENCY — max concurrent requests (default: 50)\n * HTTP_BULKHEAD_MAX_QUEUE — max queued requests (default: 100)\n */\n@Injectable()\nexport class HttpBulkheadMiddleware implements NestMiddleware {\n private readonly logger = new Logger(HttpBulkheadMiddleware.name);\n private readonly bulkhead: Bulkhead;\n\n constructor() {\n const concurrency = parseInt(\n process.env['HTTP_BULKHEAD_CONCURRENCY'] ?? String(DEFAULT_CONCURRENCY),\n 10,\n );\n const maxQueueSize = parseInt(\n process.env['HTTP_BULKHEAD_MAX_QUEUE'] ?? String(DEFAULT_MAX_QUEUE),\n 10,\n );\n\n this.bulkhead = new Bulkhead({\n name: 'http:global',\n maxConcurrentCalls: concurrency,\n maxQueueSize,\n queueTimeoutMs: 30_000,\n rejectWhenFull: true,\n });\n\n this.logger.log(\n `HTTP Bulkhead ready — concurrency: ${concurrency}, maxQueue: ${maxQueueSize}`,\n );\n }\n\n use(req: Request, res: Response, next: NextFunction): void {\n this.bulkhead\n .execute(\n () =>\n new Promise<void>(resolve => {\n res.once('finish', resolve);\n res.once('close', resolve);\n next();\n }),\n )\n .catch((error: unknown) => {\n if (res.headersSent) return;\n if (error instanceof BulkheadRejectedError) {\n const m = this.bulkhead.getMetrics();\n this.logger.warn(\n `HTTP Bulkhead saturated [active=${m.activeCalls} queued=${m.queuedCalls}]` +\n ` — rejecting ${req.method} ${req.path}`,\n );\n res.status(429).json({\n statusCode: 429,\n error: 'Too Many Requests',\n message: 'Service temporarily overloaded. Please retry in a few seconds.',\n });\n } else if (error instanceof BulkheadTimeoutError) {\n res.status(503).json({\n statusCode: 503,\n error: 'Service Unavailable',\n message: 'Request timed out waiting for processing.',\n });\n }\n });\n }\n\n getStats(): BulkheadMetrics {\n return this.bulkhead.getMetrics();\n }\n}\n","import { Module } from '@nestjs/common';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport { BulkheadService } from './bulkhead.service.js';\nimport { BulkheadGuard } from './bulkhead.guard.js';\nimport { BulkheadInterceptor } from './bulkhead.interceptor.js';\nimport { HttpBulkheadMiddleware } from './http-bulkhead.middleware.js';\n\n@Module({\n providers: [\n BulkheadRegistry,\n BulkheadService,\n BulkheadGuard,\n BulkheadInterceptor,\n HttpBulkheadMiddleware,\n ],\n exports: [\n BulkheadRegistry,\n BulkheadService,\n BulkheadGuard,\n BulkheadInterceptor,\n HttpBulkheadMiddleware,\n ],\n})\nexport class BulkheadModule {}\n","import { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\n\n/**\n * Method decorator that wraps execution inside a named bulkhead.\n * The class must have `bulkheadRegistry: BulkheadRegistry` injected.\n */\nexport function WithBulkhead(options: {\n name: string;\n maxConcurrent?: number;\n timeoutMs?: number;\n}) {\n return function (\n _target: unknown,\n _propertyKey: string,\n descriptor: PropertyDescriptor,\n ): PropertyDescriptor {\n const originalMethod = descriptor.value as (...args: unknown[]) => Promise<unknown>;\n\n descriptor.value = async function (...args: unknown[]) {\n const registry = (this as { bulkheadRegistry?: BulkheadRegistry }).bulkheadRegistry;\n if (!registry) throw new Error('BulkheadRegistry not injected in class.');\n\n const bulkhead = registry.getOrCreate({\n name: options.name,\n maxConcurrentCalls: options.maxConcurrent ?? 5,\n queueTimeoutMs: options.timeoutMs ?? 30_000,\n maxQueueSize: 50,\n rejectWhenFull: true,\n });\n\n return bulkhead.execute(() => originalMethod.apply(this, args));\n };\n\n return descriptor;\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/nestjs/bulkhead.service.ts","../../src/nestjs/bulkhead.guard.ts","../../src/nestjs/bulkhead.interceptor.ts","../../src/nestjs/http-bulkhead.middleware.ts","../../src/nestjs/bulkhead.module.ts","../../src/nestjs/bulkhead.decorator.ts"],"names":["Injectable","Inject","ServiceUnavailableException","Logger"],"mappings":"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","file":"index.js","sourcesContent":["import { Injectable, Inject, Logger, OnModuleDestroy } from '@nestjs/common';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport type { BulkheadMetrics } from '../bulkhead/bulkhead.js';\n\n@Injectable()\nexport class BulkheadService implements OnModuleDestroy {\n private readonly logger = new Logger(BulkheadService.name);\n private monitorInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(@Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry) {\n this.startMonitoring();\n }\n\n onModuleDestroy(): void {\n if (this.monitorInterval) {\n clearInterval(this.monitorInterval);\n this.monitorInterval = null;\n }\n }\n\n getAllMetrics(): Record<string, BulkheadMetrics> {\n return this.registry.getAllMetrics();\n }\n\n getCriticalBulkheads(): BulkheadMetrics[] {\n return Object.values(this.registry.getAllMetrics()).filter(\n m => m.activeCalls / m.maxConcurrentCalls > 0.9,\n );\n }\n\n resetMetrics(name?: string): void {\n if (name) {\n this.registry.getOrCreate({ name }).resetMetrics();\n } else {\n this.registry.resetAllMetrics();\n }\n }\n\n private startMonitoring(): void {\n this.monitorInterval = setInterval(() => {\n const critical = this.getCriticalBulkheads();\n if (critical.length > 0) {\n this.logger.warn(\n `Critical bulkheads detected: ${critical.length}`,\n critical.map(c => ({\n name: c.name,\n utilization: `${Math.round((c.activeCalls / c.maxConcurrentCalls) * 100)}%`,\n queued: c.queuedCalls,\n })),\n );\n }\n }, 60_000);\n }\n}\n","import {\n Injectable,\n Inject,\n CanActivate,\n ExecutionContext,\n ServiceUnavailableException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\n\nexport interface BulkheadGuardOptions {\n /** Named bulkhead to use; defaults to the route path */\n name?: string;\n /** Create a separate bulkhead per x-client-id header value */\n perClient?: boolean;\n maxConcurrent?: number;\n timeoutMs?: number;\n}\n\nexport const UseBulkhead = (options: BulkheadGuardOptions = {}) =>\n Reflector.createDecorator<BulkheadGuardOptions>({ key: 'bulkhead', transform: () => options });\n\n@Injectable()\nexport class BulkheadGuard implements CanActivate {\n constructor(\n @Inject(Reflector) private readonly reflector: Reflector,\n @Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const options = this.reflector.get<BulkheadGuardOptions>('bulkhead', context.getHandler());\n if (!options) return true;\n\n const request = context.switchToHttp().getRequest();\n const clientId: string = request.headers['x-client-id'] ?? 'anonymous';\n\n const bulkhead = options.perClient\n ? this.registry.getForClient(clientId, options.name ?? (request.route?.path as string))\n : this.registry.getForService(options.name ?? 'default');\n\n if (!bulkhead.canAccept()) {\n throw new ServiceUnavailableException('Service temporarily unavailable due to high load');\n }\n\n (request as Record<string, unknown>)['bulkhead'] = bulkhead;\n return true;\n }\n}\n","import {\n Injectable,\n Inject,\n NestInterceptor,\n ExecutionContext,\n CallHandler,\n ServiceUnavailableException,\n RequestTimeoutException,\n} from '@nestjs/common';\nimport { firstValueFrom, Observable } from 'rxjs';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport { BulkheadRejectedError, BulkheadTimeoutError } from '../bulkhead/bulkhead.js';\n\n@Injectable()\nexport class BulkheadInterceptor implements NestInterceptor {\n constructor(@Inject(BulkheadRegistry) private readonly registry: BulkheadRegistry) {}\n\n async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<unknown>> {\n const request = context.switchToHttp().getRequest();\n const clientId: string = request.headers['x-client-id'] ?? 'anonymous';\n const handlerName: string = context.getHandler().name;\n\n const bulkhead = this.registry.getForClient(clientId, handlerName);\n\n try {\n const result = await bulkhead.execute(() => firstValueFrom(next.handle()));\n return new Observable(subscriber => {\n subscriber.next(result);\n subscriber.complete();\n });\n } catch (error) {\n if (error instanceof BulkheadRejectedError) {\n throw new ServiceUnavailableException(\n 'Service at capacity — please retry in a moment',\n );\n }\n if (error instanceof BulkheadTimeoutError) {\n throw new RequestTimeoutException(\n 'Request timed out waiting for processing — please retry',\n );\n }\n throw error;\n }\n }\n}\n","import { Injectable, NestMiddleware, Logger } from '@nestjs/common';\nimport type { Request, Response, NextFunction } from 'express';\nimport { Bulkhead, BulkheadRejectedError, BulkheadTimeoutError } from '../bulkhead/bulkhead.js';\nimport type { BulkheadMetrics } from '../bulkhead/bulkhead.js';\n\nconst DEFAULT_CONCURRENCY = 50;\nconst DEFAULT_MAX_QUEUE = 100;\n\n/**\n * Global HTTP middleware that limits simultaneous in-flight requests.\n *\n * Configure via environment variables:\n * HTTP_BULKHEAD_CONCURRENCY — max concurrent requests (default: 50)\n * HTTP_BULKHEAD_MAX_QUEUE — max queued requests (default: 100)\n */\n@Injectable()\nexport class HttpBulkheadMiddleware implements NestMiddleware {\n private readonly logger = new Logger(HttpBulkheadMiddleware.name);\n private readonly bulkhead: Bulkhead;\n\n constructor() {\n const rawConcurrency = parseInt(process.env['HTTP_BULKHEAD_CONCURRENCY'] ?? '', 10);\n const concurrency =\n Number.isNaN(rawConcurrency) || rawConcurrency < 1 ? DEFAULT_CONCURRENCY : rawConcurrency;\n\n const rawMaxQueue = parseInt(process.env['HTTP_BULKHEAD_MAX_QUEUE'] ?? '', 10);\n const maxQueueSize =\n Number.isNaN(rawMaxQueue) || rawMaxQueue < 0 ? DEFAULT_MAX_QUEUE : rawMaxQueue;\n\n this.bulkhead = new Bulkhead({\n name: 'http:global',\n maxConcurrentCalls: concurrency,\n maxQueueSize,\n queueTimeoutMs: 30_000,\n rejectWhenFull: true,\n });\n\n this.logger.log(\n `HTTP Bulkhead ready — concurrency: ${concurrency}, maxQueue: ${maxQueueSize}`,\n );\n }\n\n use(req: Request, res: Response, next: NextFunction): void {\n this.bulkhead\n .execute(\n () =>\n new Promise<void>(resolve => {\n res.once('finish', resolve);\n res.once('close', resolve);\n next();\n }),\n )\n .catch((error: unknown) => {\n if (res.headersSent) return;\n if (error instanceof BulkheadRejectedError) {\n const m = this.bulkhead.getMetrics();\n this.logger.warn(\n `HTTP Bulkhead saturated [active=${m.activeCalls} queued=${m.queuedCalls}]` +\n ` — rejecting ${req.method} ${req.path}`,\n );\n res.status(429).json({\n statusCode: 429,\n error: 'Too Many Requests',\n message: 'Service temporarily overloaded. Please retry in a few seconds.',\n });\n } else if (error instanceof BulkheadTimeoutError) {\n res.status(503).json({\n statusCode: 503,\n error: 'Service Unavailable',\n message: 'Request timed out waiting for processing.',\n });\n }\n });\n }\n\n getStats(): BulkheadMetrics {\n return this.bulkhead.getMetrics();\n }\n}\n","import { Module } from '@nestjs/common';\nimport { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\nimport { BulkheadService } from './bulkhead.service.js';\nimport { BulkheadGuard } from './bulkhead.guard.js';\nimport { BulkheadInterceptor } from './bulkhead.interceptor.js';\nimport { HttpBulkheadMiddleware } from './http-bulkhead.middleware.js';\n\n@Module({\n providers: [\n BulkheadRegistry,\n BulkheadService,\n BulkheadGuard,\n BulkheadInterceptor,\n HttpBulkheadMiddleware,\n ],\n exports: [\n BulkheadRegistry,\n BulkheadService,\n BulkheadGuard,\n BulkheadInterceptor,\n HttpBulkheadMiddleware,\n ],\n})\nexport class BulkheadModule {}\n","import { BulkheadRegistry } from '../bulkhead/bulkhead.registry.js';\n\n/**\n * Method decorator that wraps execution inside a named bulkhead.\n * The class must have `bulkheadRegistry: BulkheadRegistry` injected.\n */\nexport function WithBulkhead(options: {\n name: string;\n maxConcurrent?: number;\n timeoutMs?: number;\n}) {\n return function (\n _target: unknown,\n _propertyKey: string,\n descriptor: PropertyDescriptor,\n ): PropertyDescriptor {\n const originalMethod = descriptor.value as (...args: unknown[]) => Promise<unknown>;\n\n descriptor.value = async function (...args: unknown[]) {\n const registry = (this as { bulkheadRegistry?: BulkheadRegistry }).bulkheadRegistry;\n if (!registry) throw new Error('BulkheadRegistry not injected in class.');\n\n const bulkhead = registry.getOrCreate({\n name: options.name,\n maxConcurrentCalls: options.maxConcurrent ?? 5,\n queueTimeoutMs: options.timeoutMs ?? 30_000,\n maxQueueSize: 50,\n rejectWhenFull: true,\n });\n\n return bulkhead.execute(() => originalMethod.apply(this, args));\n };\n\n return descriptor;\n };\n}\n"]}