UNPKG

ai-patterns

Version:

Production-ready TypeScript patterns to build solid and robust AI applications. Retry logic, circuit breakers, rate limiting, human-in-the-loop escalation, prompt versioning, response validation, context window management, and more—all with complete type

119 lines 4.06 kB
"use strict"; /** * Bulkhead Pattern - Resource isolation with concurrency control */ Object.defineProperty(exports, "__esModule", { value: true }); exports.bulkhead = void 0; exports.defineBulkhead = defineBulkhead; const common_1 = require("../types/common"); const errors_1 = require("../types/errors"); /** * Bulkhead class for resource isolation */ class Bulkhead { constructor(fn, options) { this.fn = fn; this.options = options; this.concurrent = 0; this.queue = []; this.completed = 0; this.rejected = 0; const maxConcurrent = options.maxConcurrent ?? 10; if (maxConcurrent <= 0) { throw new errors_1.PatternError(`maxConcurrent must be > 0, received: ${maxConcurrent}`, errors_1.ErrorCode.INVALID_CONFIGURATION); } } async execute() { const { maxConcurrent = 10, maxQueue: maxQueueOpt, maxQueued, queueTimeout, logger = common_1.defaultLogger, onQueued, onQueueFull, } = this.options; const maxQueue = maxQueued ?? maxQueueOpt ?? 100; const queuedAt = Date.now(); // Check if we can execute immediately if (this.concurrent < maxConcurrent) { return await this.executeNow(queuedAt); } // Check if queue is full if (this.queue.length >= maxQueue) { this.rejected++; if (onQueueFull) { onQueueFull(); } throw new errors_1.PatternError(`Bulkhead queue full (${maxQueue})`, errors_1.ErrorCode.RATE_LIMIT_EXCEEDED); } // Add to queue return new Promise((resolve, reject) => { const request = { execute: () => this.fn(), resolve, reject, queuedAt, }; this.queue.push(request); if (onQueued) { onQueued(this.queue.length); } logger.info(`Request queued (${this.queue.length}/${maxQueue})`); // Queue timeout if (queueTimeout) { setTimeout(() => { const index = this.queue.indexOf(request); if (index !== -1) { this.queue.splice(index, 1); reject(new errors_1.PatternError(`Queue timeout after ${queueTimeout}ms`, errors_1.ErrorCode.TIMEOUT)); } }, queueTimeout); } }); } async executeNow(_queuedAt) { const { logger = common_1.defaultLogger } = this.options; this.concurrent++; if (this.options.onExecute) { this.options.onExecute(); } try { logger.info(`Executing (${this.concurrent} concurrent)`); const value = await this.fn(); this.completed++; return value; } finally { this.concurrent--; this.processQueue(); } } processQueue() { if (this.queue.length === 0) return; const { maxConcurrent = 10 } = this.options; if (this.concurrent < maxConcurrent) { const request = this.queue.shift(); this.executeNow(request.queuedAt) .then(request.resolve) .catch(request.reject); } } getStats() { return { concurrent: this.concurrent, queueSize: this.queue.length, activeCount: this.concurrent, queuedCount: this.queue.length, completed: this.completed, rejected: this.rejected, }; } } /** * Define a bulkhead with Vercel-style callable API */ function defineBulkhead(options) { const { execute: fn, ...rest } = options; const instance = new Bulkhead(fn, rest); const callable = async () => { return await instance.execute(); }; callable.getStats = () => instance.getStats(); return callable; } exports.bulkhead = defineBulkhead; //# sourceMappingURL=bulkhead.js.map