25 lines
1.3 KiB
JavaScript
25 lines
1.3 KiB
JavaScript
class RequestQueue {
|
|
constructor(getConfig) { this.getConfig=getConfig; this.active=0; this.pending=[]; this.rate=new Map(); }
|
|
get length(){ return this.pending.length; }
|
|
async run(userId, role, fn) {
|
|
const cfg=this.getConfig(); this.checkRate(userId,role,cfg);
|
|
if(this.pending.length >= cfg.max_queue_length) throw Object.assign(new Error("AI is busy right now. Try again in a moment."),{code:"QUEUE_FULL"});
|
|
const queuedAt=Date.now();
|
|
return new Promise((resolve,reject)=>{ this.pending.push({fn,resolve,reject,queuedAt}); this.drain(); });
|
|
}
|
|
checkRate(userId,role,cfg) {
|
|
if(role==="admin" && cfg.admin_bypass_rate_limit) return;
|
|
const now=Date.now(), key=`${role}:${userId}`, rows=(this.rate.get(key)||[]).filter(t=>now-t<60000);
|
|
if(rows.length >= cfg.per_user_requests_per_minute) throw Object.assign(new Error("AI rate limit reached. Try again shortly."),{code:"RATE_LIMIT"});
|
|
rows.push(now); this.rate.set(key,rows);
|
|
}
|
|
drain(){
|
|
const limit=Math.max(1,Number(this.getConfig().concurrency)||1);
|
|
while(this.active<limit && this.pending.length){
|
|
const job=this.pending.shift(); this.active++;
|
|
Promise.resolve().then(()=>job.fn(Date.now()-job.queuedAt)).then(job.resolve,job.reject).finally(()=>{this.active--;this.drain();});
|
|
}
|
|
}
|
|
}
|
|
module.exports = { RequestQueue };
|