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
JavaScript
"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