@stacksjs/queue 0.70.206 → 0.70.208

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.
Files changed (2) hide show
  1. package/dist/index.js +2 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var{defineProperty:m1,getOwnPropertyNames:IL,getOwnPropertyDescriptor:yL}=Object,wL=Object.prototype.hasOwnProperty;function fL(L){return this[L]}var mL=(L)=>{var $=(H4??=new WeakMap).get(L),V;if($)return $;if($=m1({},"__esModule",{value:!0}),L&&typeof L==="object"||typeof L==="function"){for(var Y of IL(L))if(!wL.call($,Y))m1($,Y,{get:fL.bind(L,Y),enumerable:!(V=yL(L,Y))||V.enumerable})}return H4.set(L,$),$},H4;var jL=(L)=>L;function hL(L,$){this[L]=jL.bind(null,$)}var f=(L,$)=>{for(var V in $)m1(L,V,{get:$[V],enumerable:!0,configurable:!0,set:hL.bind($,V)})};var m=(L,$)=>()=>(L&&($=L(L=0)),$);var F=import.meta.require;function j(L,$,V){return{jobName:L,payload:$,options:V,envelopeVersion:1,dispatchedAt:new Date().toISOString()}}function Q4(L,$){if(j1.has(L))return;j1.add(L),console.warn($)}function gL(){j1.clear()}function B1(L){let $;if(typeof L==="string")try{$=JSON.parse(L)}catch{return{ok:!1,reason:"malformed",detail:"not valid JSON"}}else if(L&&typeof L==="object")$=L;else return{ok:!1,reason:"malformed",detail:`expected string or object, got ${typeof L}`};if($.envelopeVersion===1){if(typeof $.jobName!=="string")return{ok:!1,reason:"missing-job-name"};return{ok:!0,envelope:{jobName:$.jobName,payload:$.payload,options:$.options??void 0,envelopeVersion:1,dispatchedAt:typeof $.dispatchedAt==="string"?$.dispatchedAt:new Date(0).toISOString()},source:"v1"}}if(typeof $.envelopeVersion==="number"&&$.envelopeVersion>1)return{ok:!1,reason:"unknown-version",detail:`envelopeVersion=${$.envelopeVersion}, this worker speaks v1`};if(typeof $.jobName==="string")return Q4("v0-implicit","[queue/envelope] Processing pre-#1884 job envelope without envelopeVersion. In-flight jobs from before the upgrade will continue to work; new dispatches use the v1 shape automatically."),{ok:!0,envelope:{jobName:$.jobName,payload:$.payload,options:$.options??void 0,envelopeVersion:1,dispatchedAt:typeof $.dispatchedAt==="string"?$.dispatchedAt:new Date(0).toISOString()},source:"v0-implicit"};if(typeof $.job==="string")return Q4("laravel-legacy","[queue/envelope] Processing Laravel-legacy job envelope (`{ job, data }` shape). "+"Will continue to process but the queue table contains migration-era rows \u2014 "+"consider flushing once they drain."),{ok:!0,envelope:{jobName:$.job.replace(/^App\\+Jobs\\+/,"").replace(/^.*\\/,""),payload:$.data,options:void 0,envelopeVersion:1,dispatchedAt:new Date(0).toISOString()},source:"laravel-legacy"};return{ok:!1,reason:"missing-job-name"}}var pL=1,j1;var X1=m(()=>{j1=new Set});var g1={};f(g1,{runJob:()=>B4,restore:()=>p1,isFaked:()=>P4,getFakeQueue:()=>D4,fake:()=>h1,expectJobToFail:()=>E4,createQueueTester:()=>M4,QueueTester:()=>E1});class A4{dispatchedJobs=[];pushedJobs=[];processedJobs=[];failedJobs=[];dispatch(L,$,V={}){this.dispatchedJobs.push({name:L,data:$,options:V,dispatchedAt:new Date,queue:V.queue||"default"})}push(L,$,V={}){this.pushedJobs.push({name:L,data:$,options:V,dispatchedAt:new Date,queue:V.queue||"default"})}dispatched(L){if(L)return this.dispatchedJobs.filter(($)=>$.name===L);return[...this.dispatchedJobs]}pushed(L){if(L)return this.pushedJobs.filter(($)=>$.name===L);return[...this.pushedJobs]}assertDispatched(L,$){let V=this.dispatched(L);if(V.length===0)throw Error(`Expected job "${L}" to be dispatched, but it was not.`);if($){if(V.filter($).length===0)throw Error(`Expected job "${L}" to be dispatched matching the callback, but no matching jobs were found.`)}}assertNotDispatched(L){let $=this.dispatched(L);if($.length>0)throw Error(`Expected job "${L}" to not be dispatched, but it was dispatched ${$.length} time(s).`)}assertDispatchedTimes(L,$){let V=this.dispatched(L);if(V.length!==$)throw Error(`Expected job "${L}" to be dispatched ${$} time(s), but it was dispatched ${V.length} time(s).`)}assertNothingDispatched(){if(this.dispatchedJobs.length>0){let L=[...new Set(this.dispatchedJobs.map(($)=>$.name))].join(", ");throw Error(`Expected no jobs to be dispatched, but found: ${L}`)}}assertPushed(L,$){let V=this.pushed(L);if(V.length===0)throw Error(`Expected job "${L}" to be pushed, but it was not.`);if($){if(V.filter($).length===0)throw Error(`Expected job "${L}" to be pushed matching the callback, but no matching jobs were found.`)}}assertPushedWithDelay(L,$){if(this.pushed(L).filter((G)=>G.options.delay===$).length===0)throw Error(`Expected job "${L}" to be pushed with delay ${$}ms, but no matching jobs were found.`)}assertPushedOn(L,$){if(this.pushed($).filter((Y)=>Y.queue===L).length===0)throw Error(`Expected job "${$}" to be pushed on queue "${L}", but it was not.`)}async processJob(L,$){let V=this.dispatchedJobs.find((Y)=>Y.name===L);if(!V)throw Error(`No dispatched job found with name "${L}"`);try{await $(V.data),this.processedJobs.push(V)}catch(Y){throw this.failedJobs.push({job:V,error:Y}),Y}}processed(L){if(L)return this.processedJobs.filter(($)=>$.name===L);return[...this.processedJobs]}failed(L){if(L)return this.failedJobs.filter(($)=>$.job.name===L);return[...this.failedJobs]}reset(){this.dispatchedJobs=[],this.pushedJobs=[],this.processedJobs=[],this.failedJobs=[]}}function h1(){return K1=new A4,K1}function D4(){return K1}function P4(){return K1!==null}function p1(){K1=null}class E1{queue;constructor(){this.queue=h1()}dispatch(L,$,V={}){return this.queue.dispatch(L,$,V),this}push(L,$,V={}){return this.queue.push(L,$,V),this}assertDispatched(L,$){return this.queue.assertDispatched(L,$),this}assertNotDispatched(L){return this.queue.assertNotDispatched(L),this}assertDispatchedTimes(L,$){return this.queue.assertDispatchedTimes(L,$),this}assertNothingDispatched(){return this.queue.assertNothingDispatched(),this}dispatched(L){return this.queue.dispatched(L)}reset(){return this.queue.reset(),this}cleanup(){p1()}}function M4(){return new E1}async function B4(L,$){return await L.handle($)}async function E4(L,$,V){try{throw await L.handle($),Error("Expected job to fail, but it succeeded")}catch(Y){if(Y.message==="Expected job to fail, but it succeeded")throw Y;if(V){let G=Y.message;if(typeof V==="string"){if(!G.includes(V))throw Error(`Expected error to contain "${V}", got "${G}"`)}else if(!V.test(G))throw Error(`Expected error to match ${V}, got "${G}"`)}return Y}}var K1=null;var z1={};f(z1,{setQueueManager:()=>tL,getQueueManager:()=>nL,dispatchSync:()=>dL,dispatchAfter:()=>iL,dispatch:()=>cL,createRedisDispatcher:()=>rL,chain:()=>lL,batch:()=>bL,StacksQueueManager:()=>T4,RedisQueue:()=>a,RedisJob:()=>N4,QueueManager:()=>sL});import{Queue as uL,batch as bL,chain as lL,dispatch as cL,dispatchAfter as iL,dispatchSync as dL,getQueueManager as nL,QueueManager as sL,setQueueManager as tL}from"@stacksjs/bun-queue";import{log as u1}from"@stacksjs/logging";class a{queue;config;isProcessing=!1;constructor(L,$){this.config=$,this.queue=new uL(L,{driver:"redis",prefix:$.prefix,redis:$.redis?{url:$.redis.url||this.buildRedisUrl($.redis)}:void 0,defaultJobOptions:$.defaultJobOptions?{delay:$.defaultJobOptions.delay,attempts:$.defaultJobOptions.attempts,backoff:$.defaultJobOptions.backoff,removeOnComplete:$.defaultJobOptions.removeOnComplete,removeOnFail:$.defaultJobOptions.removeOnFail,priority:$.defaultJobOptions.priority,lifo:$.defaultJobOptions.lifo,timeout:$.defaultJobOptions.timeout,jobId:$.defaultJobOptions.jobId,dependsOn:$.defaultJobOptions.dependsOn,keepJobs:$.defaultJobOptions.keepJobs,deadLetter:$.defaultJobOptions.deadLetter}:void 0,limiter:$.limiter,metrics:$.metrics,stalledJobCheckInterval:$.stalledJobCheckInterval,maxStalledJobRetries:$.maxStalledJobRetries,distributedLock:$.distributedLock,defaultDeadLetterOptions:$.defaultDeadLetterOptions,horizontalScaling:$.horizontalScaling,logLevel:$.logLevel}),u1.debug(`Redis queue "${L}" initialized`)}buildRedisUrl(L){let $=L.host||"localhost",V=L.port||6379,Y=L.password?`:${encodeURIComponent(L.password)}@`:"",G=Number.isFinite(L.db)&&L.db>=0?L.db:0;return`redis://${Y}${$}:${V}/${G}`}async add(L,$){let V={delay:$?.delay?$.delay*1000:void 0,attempts:$?.maxTries,priority:$?.priority,timeout:$?.timeout?$.timeout*1000:void 0,backoff:Array.isArray($?.backoff)?$.backoff.map((Y)=>(Number(Y)||1)*1000):$?.backoff};return this.queue.add(L,V)}process(L,$){if(this.isProcessing){u1.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process(L,$),u1.info(`Started processing queue with concurrency ${L}`)}async getJob(L){return this.queue.getJob(L)}async getJobs(L){return this.queue.getJobs(L)}async getJobCounts(){return this.queue.getJobCounts()}async removeJob(L){return this.queue.removeJob(L)}async pause(){return this.queue.pause()}async resume(){return this.queue.resume()}async empty(){return this.queue.empty()}async close(){return this.isProcessing=!1,this.queue.close()}async getMetrics(){return this.queue.getMetrics()}async ping(){return this.queue.ping()}async scheduleCron(L){return this.queue.scheduleCron({cronExpression:L.cron,data:L.data,timezone:L.tz,jobId:L.name})}async unscheduleCron(L){return this.queue.unscheduleCron(L)}async getDeadLetterJobs(){return this.queue.getDeadLetterJobs()}async republishDeadLetterJob(L){return this.queue.republishDeadLetterJob(L)}async clearDeadLetterQueue(){return this.queue.clearDeadLetterQueue()}async bulkRemove(L){return this.queue.bulkRemove(L)}async getClusterInfo(){return this.queue.getClusterInfo()}isLeader(){return this.queue.isLeader()}getQueue(){return this.queue}on(L,$){this.queue.events.on(L,$)}}class T4{config;queues=new Map;defaultConnection="default";constructor(L){this.config=L;for(let[$,V]of Object.entries(L))if(V.driver==="redis")this.queues.set($,new a($,V))}queue(L){let $=L||this.defaultConnection,V=this.queues.get($);if(!V){let Y=this.config[$];if(!Y)throw Error(`Queue "${$}" not configured`);V=new a($,Y),this.queues.set($,V)}return V}setDefaultConnection(L){this.defaultConnection=L}async closeAll(){let L=Array.from(this.queues.values()).map(($)=>$.close());await Promise.all(L),this.queues.clear()}}function rL(L,$){let V=new a(L,$);return async(Y,G)=>{return V.add(Y,G)}}class N4{data;options={};queue;constructor(L,$,V){this.data=V;this.queue=new a(L,$)}async dispatch(){await this.queue.add(this.data,this.options)}async dispatchNow(){await this.queue.add(this.data,{...this.options,immediate:!0})}delay(L){return this.options.delay=L,this}afterResponse(){return this.options.afterResponse=!0,this}chain(L){return this.options.chainedJobs=L,this}onQueue(L){return this.options.queue=L,this}priority(L){return this.options.priority=L,this}tries(L){return this.options.maxTries=L,this}timeout(L){return this.options.timeout=L,this}backoff(L){return this.options.backoff=L,this}}var _1=()=>{};function B(L){let $=L,V=$?.errno;if(typeof V==="string"&&V.toUpperCase()==="42P01")return!0;if(typeof V==="number"&&V===1146)return!0;if((typeof $?.code==="string"?$.code.toUpperCase():"")==="42P01")return!0;let G=$?.message??"";return G.includes("no such table")||G.includes("doesn't exist")||G.includes("does not exist")}var k4={};f(k4,{retryDeadLetterJob:()=>q4,purgeDeadLetterJobs:()=>v4,moveToDeadLetter:()=>N1,listDeadLetterJobs:()=>C4});import{db as o}from"@stacksjs/database";function T1(){if(R4)return;R4=!0,console.warn("[queue/dlq] dead_letter_jobs table missing \u2014 DLQ disabled. "+"Run migrations to enable poison-message isolation.")}async function N1(L,$,V=1){let Y=new Date().toISOString().slice(0,19).replace("T"," ");try{return await o.insertInto("dead_letter_jobs").values({uuid:L.uuid??crypto.randomUUID(),connection:L.connection??"database",queue:L.queue??"default",payload:L.payload??"{}",exception:L.exception??"unknown",reason:$,total_failures:V,first_failed_at:L.failed_at??Y,last_failed_at:Y,dead_lettered_at:Y}).execute(),!0}catch(G){if(B(G))return T1(),!1;throw G}}async function C4(L={}){try{let $=o.selectFrom("dead_letter_jobs").selectAll();if(L.queue)$=$.where("queue","=",L.queue);if(L.reason)$=$.where("reason","=",L.reason);if(L.sinceCutoffMs){let Y=new Date(L.sinceCutoffMs).toISOString().slice(0,19).replace("T"," ");$=$.where("dead_lettered_at",">=",Y)}if(L.limit&&L.limit>0)$=$.limit(L.limit);return await $.execute()??[]}catch($){if(B($))return T1(),[];throw $}}async function q4(L){try{let $=await o.selectFrom("dead_letter_jobs").where("id","=",L).selectAll().executeTakeFirst();if(!$)return!1;let V=Math.floor(Date.now()/1000);return await o.insertInto("jobs").values({queue:$.queue,payload:$.payload,attempts:0,reserved_at:null,available_at:V,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),await o.deleteFrom("dead_letter_jobs").where("id","=",L).execute(),!0}catch($){if(B($))return T1(),!1;throw $}}async function v4(L=30){try{let $=new Date(Date.now()-L*24*60*60*1000).toISOString().slice(0,19).replace("T"," "),V=await o.deleteFrom("dead_letter_jobs").where("dead_lettered_at","<",$).execute();return Number(V?.numDeletedRows??V?.[0]?.numDeletedRows??V?.affectedRows??0)}catch($){if(B($))return T1(),0;throw $}}var R4=!1;var S1=()=>{};function b1(){if(I4)return;I4=!0,console.warn("[queue/idempotency] job_idempotency table missing \u2014 idempotency keys are accepted but NOT enforced. "+"Run migrations to enable dedup.")}async function oL(L){try{let{db:$}=await import("@stacksjs/database"),V=await $.selectFrom("job_idempotency").where("idempotency_key","=",L).select(["idempotency_key"]).executeTakeFirst();return Boolean(V)}catch($){if(isMissingTableError($))return b1(),!1;throw $}}async function eL(L,$,V){try{let{db:Y}=await import("@stacksjs/database");await Y.insertInto("job_idempotency").values({idempotency_key:L,job_name:$,queue:V??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}catch(Y){if(isMissingTableError(Y)){b1();return}let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return;throw Y}}async function l1(L,$,V){try{let{db:Y}=await import("@stacksjs/database");return await Y.insertInto("job_idempotency").values({idempotency_key:L,job_name:$,queue:V??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),"claimed"}catch(Y){if(isMissingTableError(Y))return b1(),"unenforced";let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return"duplicate";throw Y}}async function c1(L){try{let{db:$}=await import("@stacksjs/database");await $.deleteFrom("job_idempotency").where("idempotency_key","=",L).execute()}catch{}}var I4=!1;var h4={};f(h4,{unquarantineJob:()=>m4,recordFailureForPoison:()=>w4,quarantineJob:()=>f4,listQuarantined:()=>j4,isQuarantined:()=>J1,hashPayload:()=>Z1});import{createHash as L2}from"crypto";import{db as R}from"@stacksjs/database";function W1(){if(y4)return;y4=!0,console.warn("[queue/poison] job_quarantine table missing \u2014 poison detection disabled. "+"Run migrations to enable.")}function Z1(L){let $;try{$=typeof L==="string"?L:JSON.stringify(L??null)}catch{$=String(L)}return L2("sha256").update($).digest("hex").slice(0,32)}async function w4(L,$,V={}){let Y=V.maxFailures??5,G=V.windowMinutes??60,U=Z1($),X=new Date,_=X.toISOString().slice(0,19).replace("T"," ");try{let K=await R.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","=",U).selectAll().executeTakeFirst();if(!K)return await R.insertInto("job_quarantine").values({job_name:L,payload_hash:U,failure_count:1,window_start:_,quarantined_at:null}).execute(),!1;if(K.quarantined_at)return!0;let z=Date.parse(K.window_start.replace(" ","T")+"Z"),O=X.getTime()-z,Q=G*60*1000;if(Number.isFinite(z)&&O>Q)return await R.updateTable("job_quarantine").set({failure_count:1,window_start:_}).where("id","=",K.id).execute(),!1;let P=K.failure_count+1;if(P>=Y)return await R.updateTable("job_quarantine").set({failure_count:P,quarantined_at:_}).where("id","=",K.id).execute(),!0;return await R.updateTable("job_quarantine").set({failure_count:P}).where("id","=",K.id).execute(),!1}catch(K){if(B(K))return W1(),!1;throw K}}async function J1(L,$){let V=Z1($);try{let Y=await R.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","in",[V,"*"]).where("quarantined_at","is not",null).select(["id"]).executeTakeFirst();return Boolean(Y)}catch(Y){if(B(Y))return W1(),!1;throw Y}}async function f4(L,$){let V=$===void 0?"*":Z1($),Y=new Date().toISOString().slice(0,19).replace("T"," ");try{let G=await R.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","=",V).select(["id"]).executeTakeFirst();if(G)await R.updateTable("job_quarantine").set({quarantined_at:Y}).where("id","=",G.id).execute();else await R.insertInto("job_quarantine").values({job_name:L,payload_hash:V,failure_count:0,window_start:Y,quarantined_at:Y}).execute()}catch(G){if(B(G)){W1();return}throw G}}async function m4(L){try{await R.deleteFrom("job_quarantine").where("job_name","=",L).execute()}catch($){if(B($)){W1();return}throw $}}async function j4(){try{return await R.selectFrom("job_quarantine").where("quarantined_at","is not",null).selectAll().execute()??[]}catch(L){if(B(L))return W1(),[];throw L}}var y4=!1;var R1=()=>{};function e(L){let $=L?.numUpdatedRows;if($===null||$===void 0)return 0;if(typeof $==="object")return Number($.changes??0);return Number($)}async function i1(L,$){let V=JSON.stringify({jobName:L,payload:$.payload||{},options:{queue:$.queue,tries:$.maxTries,timeout:$.timeout,backoff:$.backoff}}),{db:Y}=await import("@stacksjs/database");await Y.insertInto("jobs").values({queue:$.queue||"default",payload:V,attempts:0,available_at:$2($.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}function $2(L){let $=Date.now();return Math.floor($/1000+L)}var h={};f(h,{withEvents:()=>g4,onQueueEvent:()=>p4,getWorkerTracker:()=>O1,getQueueEvents:()=>L1,getGlobalMetrics:()=>v1,emitQueueEvent:()=>c,QueueMetrics:()=>q1,QueueEvents:()=>C1,OnQueueEvent:()=>u4});import{log as y}from"@stacksjs/logging";class C1{handlers=new Map;wildcardHandlers=new Set;on(L,$){if(!this.handlers.has(L))this.handlers.set(L,new Set);return this.handlers.get(L).add($),()=>{this.handlers.get(L)?.delete($)}}onAny(L){return this.wildcardHandlers.add(L),()=>{this.wildcardHandlers.delete(L)}}once(L,$){let V=async(Y)=>{this.handlers.get(L)?.delete(V),await $(Y)};return this.on(L,V)}async emit(L,$){let V={...$,timestamp:Date.now()};this.logEvent(L,V);let Y=this.handlers.get(L);if(Y)for(let G of Y)try{await G(V)}catch(U){y.error(`Error in queue event handler for ${L}:`,U)}for(let G of this.wildcardHandlers)try{await G(L,V)}catch(U){y.error("Error in wildcard queue event handler:",U)}}logEvent(L,$){let V=$.jobId?`[${$.jobId}]`:"",Y=$.queueName?`on ${$.queueName}`:"";switch(L){case"job:added":y.debug(`Job added ${V} ${Y}`);break;case"job:processing":y.debug(`Job processing ${V} ${Y}`);break;case"job:completed":y.info(`Job completed ${V} ${Y} in ${$.duration}ms`);break;case"job:failed":y.error(`Job failed ${V} ${Y}:`,$.error);break;case"job:retrying":y.warn(`Job retrying ${V} ${Y} (attempt ${$.attemptsMade})`);break;case"job:stalled":y.warn(`Job stalled ${V} ${Y}`);break;case"queue:error":y.error(`Queue error ${Y}:`,$.error);break}}off(L){this.handlers.delete(L)}removeAllListeners(){this.handlers.clear(),this.wildcardHandlers.clear()}}function L1(){if(!d1)d1=new C1;return d1}function p4(L,$){let V=L1();if(L==="*")return V.onAny($);return V.on(L,$)}function c(L,$){return L1().emit(L,$)}function g4(L,$){return async(...V)=>{let Y=V[0]?.id||"unknown",G=Date.now();await c("job:processing",{jobId:Y,queueName:L,data:V[0]?.data});try{let U=await $(...V);return await c("job:completed",{jobId:Y,queueName:L,result:U,duration:Date.now()-G}),U}catch(U){throw await c("job:failed",{jobId:Y,queueName:L,error:U,duration:Date.now()-G}),U}}}function u4(L){return function($,V,Y){let G=Y.value;return L1().on(L,G),Y}}class q1{jobCounts={added:0,completed:0,failed:0,processing:0};completions=[];errors=[];unsubscribe=[];constructor(){this.setupListeners()}setupListeners(){let L=L1();this.unsubscribe.push(L.on("job:added",()=>{this.jobCounts.added++}),L.on("job:processing",()=>{this.jobCounts.processing++}),L.on("job:completed",($)=>{this.jobCounts.completed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1);let V=$.duration||0;if(this.completions.push({timestamp:Date.now(),duration:V}),this.completions.length>1000)this.completions.shift()}),L.on("job:failed",($)=>{if(this.jobCounts.failed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1),$.error){if(this.errors.push({error:$.error,timestamp:Date.now()}),this.errors.length>100)this.errors.shift()}}))}getThroughputPerMinute(){let L=Date.now()-60000;return this.completions.filter((V)=>V.timestamp>=L).length}getAverageProcessingTime(){let L=Date.now()-60000,$=this.completions.filter((V)=>V.timestamp>=L);if($.length===0)return 0;return $.reduce((V,Y)=>V+Y.duration,0)/$.length}getMetrics(){return{counts:{...this.jobCounts},averageDuration:this.getAverageProcessingTime(),recentErrors:[...this.errors],throughputPerMinute:this.getThroughputPerMinute()}}reset(){this.jobCounts={added:0,completed:0,failed:0,processing:0},this.completions=[],this.errors=[]}stop(){this.unsubscribe.forEach((L)=>L()),this.unsubscribe=[]}}function v1(){if(!n1)n1=new q1;return n1}class b4{workers=new Map;register(L,$){this.workers.set(L,{id:L,status:"idle",queue:$,processedCount:0,failedCount:0,lastActivityAt:new Date().toISOString(),startedAt:new Date().toISOString()})}markActive(L){let $=this.workers.get(L);if($)$.status="active",$.lastActivityAt=new Date().toISOString()}markIdle(L){let $=this.workers.get(L);if($)$.status="idle",$.lastActivityAt=new Date().toISOString()}recordCompletion(L){let $=this.workers.get(L);if($)$.processedCount++,$.lastActivityAt=new Date().toISOString()}recordFailure(L){let $=this.workers.get(L);if($)$.failedCount++,$.lastActivityAt=new Date().toISOString()}unregister(L){let $=this.workers.get(L);if($)$.status="stopped"}getAll(){return Array.from(this.workers.values())}clear(){this.workers.clear()}}function O1(){return V2}var d1=null,n1=null,V2;var v=m(()=>{V2=new b4});var u={};f(u,{recordBatchJobFailure:()=>e1,recordBatchJobCompletion:()=>o1,isBatchCancelled:()=>e4,getBatchCallbacks:()=>F1,PendingBatch:()=>I1,DispatchedBatch:()=>p,Batch:()=>s1});var{RedisClient:Y2}=globalThis.Bun;import{log as E}from"@stacksjs/logging";import{env as G2}from"@stacksjs/env";function g(){return G2.QUEUE_DRIVER||"sync"}class I1{jobs;options={thenCallbacks:[],catchCallbacks:[],finallyCallbacks:[],progressCallbacks:[]};constructor(L){this.jobs=L.map(($)=>("job"in $)?$:{job:$})}name(L){return this.options.name=L,this}onQueue(L){return this.options.queue=L,this}allowFailures(){return this.options.allowFailures=!0,this}then(L){return this.options.thenCallbacks.push(L),this}catch(L){return this.options.catchCallbacks.push(L),this}finally(L){return this.options.finallyCallbacks.push(L),this}progress(L){return this.options.progressCallbacks.push(L),this}thenHandler(L){return this.options.thenHandler=L,this}catchHandler(L){return this.options.catchHandler=L,this}finallyHandler(L){return this.options.finallyHandler=L,this}async dispatch(){let L=crypto.randomUUID(),$=this.jobs.length;if($===0)throw Error("Cannot dispatch an empty batch");let V=g();await X2({id:L,name:this.options.name||"",total_jobs:$,pending_jobs:$,failed_jobs:0,failed_job_ids:"[]",options:JSON.stringify({queue:this.options.queue,allowFailures:this.options.allowFailures||!1}),cancelled_at:null,created_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:null,then_handler:this.options.thenHandler?JSON.stringify(this.options.thenHandler):null,catch_handler:this.options.catchHandler?JSON.stringify(this.options.catchHandler):null,finally_handler:this.options.finallyHandler?JSON.stringify(this.options.finallyHandler):null}),U2(L,this.options);try{let{emitQueueEvent:Y}=await Promise.resolve().then(() => (v(),h));await Y("batch:added",{jobId:L,data:{name:this.options.name,totalJobs:$}})}catch{}for(let Y=0;Y<this.jobs.length;Y++){let G=this.jobs[Y];if(!G)continue;let{job:U,payload:X}=G,_={...X,_batchId:L,_batchIndex:Y};if(this.options.queue&&!U.queue)U.queue=this.options.queue;if(V==="sync")try{await U.dispatchNow(_),await o1(L)}catch(K){await e1(L,`${L}:${Y}`,K)}else await U.dispatch(_)}return E.info(`[Batch] Dispatched batch "${this.options.name||L}" with ${$} jobs`),new p(L)}getJobs(){return[...this.jobs]}getOptions(){return this.options}}class p{id;constructor(L){this.id=L}async fresh(){return i(this.id)}async getName(){return(await this.fresh())?.name||""}async totalJobs(){return(await this.fresh())?.total_jobs||0}async pendingJobs(){return(await this.fresh())?.pending_jobs||0}async failedJobs(){return(await this.fresh())?.failed_jobs||0}async completedJobs(){let L=await this.fresh();if(!L)return 0;return L.total_jobs-L.pending_jobs}async progress(){let L=await this.fresh();if(!L||L.total_jobs===0)return 0;let $=L.total_jobs-L.pending_jobs;return Math.round($/L.total_jobs*100)}async finished(){return(await this.fresh())?.finished_at!==null}async cancelled(){return(await this.fresh())?.cancelled_at!==null}async hasFailures(){return((await this.fresh())?.failed_jobs||0)>0}async failedJobIds(){let L=await this.fresh();if(!L)return[];try{return JSON.parse(L.failed_job_ids||"[]")}catch{return[]}}async cancel(){if(g()==="redis")await x2(this.id);else await W2(this.id);E.info(`[Batch] Cancelled batch ${this.id}`);let $=F1(this.id);if($)for(let V of $.finallyCallbacks)try{await V(this)}catch(Y){E.error(`[Batch] Error in finally callback for batch ${this.id}:`,Y)}}async add(L){let $=await this.fresh();if(!$)throw Error(`Batch ${this.id} not found`);if($.cancelled_at)throw Error(`Batch ${this.id} has been cancelled`);if($.finished_at)throw Error(`Batch ${this.id} has already finished`);let V=L.map((X)=>("job"in X)?X:{job:X}),Y=$.total_jobs+V.length,G=$.pending_jobs+V.length;await l4(this.id,{total_jobs:Y,pending_jobs:G});let U=JSON.parse($.options||"{}");for(let X=0;X<V.length;X++){let _=V[X];if(!_)continue;let{job:K,payload:z}=_,O={...z,_batchId:this.id,_batchIndex:$.total_jobs+X};if(U.queue&&!K.queue)K.queue=U.queue;await K.dispatch(O)}E.info(`[Batch] Added ${V.length} jobs to batch ${this.id}`)}async delete(){await z2(this.id),k1(this.id)}}class s1{static create(L){return new I1(L)}static async find(L){if(!await i(L))return null;return new p(L)}static async all(){return(await K2()).map(($)=>new p($.id))}static async prune(L=24){return _2(L)}}function U2(L,$){t1.set(L,$)}function F1(L){return t1.get(L)}function k1(L){t1.delete(L)}async function X2(L){if(g()==="redis")await O2(L);else await c4(L)}async function i(L){if(g()==="redis")return F2(L);return i4(L)}async function K2(){if(g()==="redis")return r4();return d4()}async function l4(L,$){if(g()==="redis")await a4(L,$);else await r1(L,$)}async function z2(L){if(g()==="redis")await o4(L);else await n4(L)}async function _2(L){if(g()==="redis")return H2(L);return s4(L)}async function c4(L){let{db:$}=await import("@stacksjs/database");await $.insertInto("job_batches").values({id:L.id,name:L.name,total_jobs:L.total_jobs,pending_jobs:L.pending_jobs,failed_jobs:L.failed_jobs,failed_job_ids:L.failed_job_ids,options:L.options,cancelled_at:L.cancelled_at,created_at:L.created_at,finished_at:L.finished_at}).execute()}async function i4(L){let{db:$}=await import("@stacksjs/database");return await $.selectFrom("job_batches").where("id","=",L).selectAll().executeTakeFirst()||null}async function d4(){let{db:L}=await import("@stacksjs/database");return await L.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function r1(L,$){let{db:V}=await import("@stacksjs/database");await V.updateTable("job_batches").set($).where("id","=",L).execute()}async function n4(L){let{db:$}=await import("@stacksjs/database");await $.deleteFrom("job_batches").where("id","=",L).execute()}async function W2(L){await r1(L,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function s4(L){let{db:$}=await import("@stacksjs/database"),V=new Date(Date.now()-L*60*60*1000).toISOString().slice(0,19).replace("T"," "),Y=await $.deleteFrom("job_batches").where("finished_at","is not",null).where("finished_at","<",V).executeTakeFirst();return Number(Y?.numDeletedRows??0)}async function Z2(){let{queue:L}=await import("@stacksjs/config"),$=L?.connections?.redis?.redis;if($?.url)return $.url;let V=$?.password?`:${encodeURIComponent($.password)}@`:"",Y=$?.db?`/${$.db}`:"";return`redis://${V}${$?.host||"localhost"}:${$?.port||6379}${Y}`}async function H1(){let L=new Y2(await Z2());return await L.connect(),L}async function O2(L){try{let $=await H1(),V=`${x1}${L.id}`;await $.hset(V,{id:L.id,name:L.name,total_jobs:String(L.total_jobs),pending_jobs:String(L.pending_jobs),failed_jobs:String(L.failed_jobs),failed_job_ids:L.failed_job_ids,options:L.options,cancelled_at:L.cancelled_at||"",created_at:L.created_at,finished_at:L.finished_at||""}),await $.sadd(a1,L.id),$.close()}catch{await c4(L)}}function t4(L){if(!L?.id)return null;return{id:L.id,name:L.name??"",total_jobs:Number(L.total_jobs),pending_jobs:Number(L.pending_jobs),failed_jobs:Number(L.failed_jobs),failed_job_ids:L.failed_job_ids??"",options:L.options??"",cancelled_at:L.cancelled_at||null,created_at:L.created_at??"",finished_at:L.finished_at||null}}async function F2(L){try{let $=await H1(),V=`${x1}${L}`,Y=await $.hgetall(V);return $.close(),t4(Y)}catch{return i4(L)}}async function r4(){try{let L=await H1(),$=await L.smembers(a1),V=[];for(let Y of $){let G=t4(await L.hgetall(`${x1}${Y}`));if(G)V.push(G)}return L.close(),V}catch{return d4()}}async function a4(L,$){try{let V=await H1(),Y=`${x1}${L}`,G={};for(let[U,X]of Object.entries($))G[U]=X===null?"":String(X);await V.hset(Y,G),V.close()}catch{await r1(L,$)}}async function o4(L){try{let $=await H1();await $.del(`${x1}${L}`),await $.srem(a1,L),$.close()}catch{await n4(L)}}async function x2(L){let $=new Date().toISOString().slice(0,19).replace("T"," ");await a4(L,{cancelled_at:$,finished_at:$})}async function H2(L){try{let $=await r4(),V=Date.now()-L*60*60*1000,Y=0;for(let G of $)if(G.finished_at){if(new Date(G.finished_at).getTime()<V)await o4(G.id),Y++}return Y}catch{return s4(L)}}function $1(L){if(!L)return null;try{let $=JSON.parse(L);if($&&($.kind==="job"||$.kind==="module"))return $;return E.warn(`[Batch] handler JSON has unknown kind '${$?.kind}' \u2014 skipping`),null}catch($){return E.warn(`[Batch] failed to parse persistent handler: ${$.message}`),null}}async function V1(L,$){try{if(L.kind==="job"){let{Jobs:G}=await Promise.resolve().then(() => (Q1(),y1));await G.dispatch(L.name,{...L.payload??{},_batchId:$});return}let V=await import(L.module).catch((G)=>{return E.warn(`[Batch] persistent handler module not found: ${L.module} (${G.message})`),null});if(!V)return;let Y=V[L.export];if(typeof Y!=="function"){E.warn(`[Batch] persistent handler export '${L.export}' is not a function on ${L.module}`);return}await Y(L.payload,$)}catch(V){E.error(`[Batch] persistent handler threw for batch ${$}:`,V)}}async function o1(L){let{db:$,sql:V}=await import("@stacksjs/database");await $.updateTable("job_batches").set({pending_jobs:V`GREATEST(pending_jobs - 1, 0)`}).where("id","=",L).where("pending_jobs",">",0).execute();let Y=new Date().toISOString().slice(0,19).replace("T"," "),G=await $.updateTable("job_batches").set({finished_at:Y}).where("id","=",L).where("pending_jobs","=",0).where("finished_at","is",null).executeTakeFirst(),U=e(G)>0,X=F1(L),_=new p(L);if(X)for(let K of X.progressCallbacks)try{await K(_)}catch(z){E.error(`[Batch] Error in progress callback for batch ${L}:`,z)}if(U){try{let{emitQueueEvent:Z}=await Promise.resolve().then(() => (v(),h));await Z("batch:completed",{jobId:L})}catch{}let K=await i(L);if(!K){k1(L),E.info(`[Batch] Batch ${L} finished (record vanished)`);return}let z=JSON.parse(K.options||"{}"),Q=!((K.failed_jobs||0)>0)||z.allowFailures;if(X){if(Q)for(let Z of X.thenCallbacks)try{await Z(_)}catch(W){E.error(`[Batch] Error in then callback for batch ${L}:`,W)}for(let Z of X.finallyCallbacks)try{await Z(_)}catch(W){E.error(`[Batch] Error in finally callback for batch ${L}:`,W)}k1(L)}if(Q){let Z=$1(K.then_handler);if(Z)await V1(Z,L)}else{let Z=$1(K.catch_handler);if(Z)await V1(Z,L)}let P=$1(K.finally_handler);if(P)await V1(P,L);E.info(`[Batch] Batch ${L} finished`)}}async function e1(L,$,V){let{db:Y,sql:G}=await import("@stacksjs/database"),U=await i(L);if(!U)return;let X=JSON.parse(U.options||"{}");await Y.updateTable("job_batches").set({pending_jobs:G`GREATEST(pending_jobs - 1, 0)`,failed_jobs:G`failed_jobs + 1`}).where("id","=",L).where("pending_jobs",">",0).execute();try{let W=await i(L);if(W){let x=[];try{x=JSON.parse(W.failed_job_ids||"[]")}catch{x=[]}x.push($),await l4(L,{failed_job_ids:JSON.stringify(x)})}}catch{}let _=new p(L),K=F1(L);if(K)for(let W of K.catchCallbacks)try{await W(_,V)}catch(x){E.error(`[Batch] Error in catch callback for batch ${L}:`,x)}try{let{emitQueueEvent:W}=await Promise.resolve().then(() => (v(),h));await W("batch:failed",{jobId:L,error:V})}catch{}let z=new Date().toISOString().slice(0,19).replace("T"," "),O=Y.updateTable("job_batches").set(X.allowFailures?{finished_at:z}:{finished_at:z,cancelled_at:z}).where("id","=",L).where("finished_at","is",null);if(X.allowFailures)O=O.where("pending_jobs","=",0);let Q=await O.executeTakeFirst();if(e(Q)===0)return;let P=!!X.allowFailures;if(K){if(P)for(let W of K.thenCallbacks)try{await W(_)}catch(x){E.error(`[Batch] Error in then callback for batch ${L}:`,x)}for(let W of K.finallyCallbacks)try{await W(_)}catch(x){E.error(`[Batch] Error in finally callback for batch ${L}:`,x)}k1(L)}let Z=await i(L);if(Z){if(P){let x=$1(Z.then_handler);if(x)await V1(x,L)}else{let x=$1(Z.catch_handler);if(x)await V1(x,L)}let W=$1(Z.finally_handler);if(W)await V1(W,L)}E.info(`[Batch] Batch ${L} finished with failure(s)`)}async function e4(L){return(await i(L))?.cancelled_at!==null}var t1,x1="stacks:batch:",a1="stacks:batches";var w=m(()=>{t1=new Map});var y1={};f(y1,{runJob:()=>w1,jobBatch:()=>YL,job:()=>$L,Jobs:()=>VL});import{appPath as Q2}from"@stacksjs/path";import{env as A2}from"@stacksjs/env";import{enqueueAfterCommit as D2,isInTransaction as P2}from"@stacksjs/database";function M2(){return A2.QUEUE_DRIVER||"sync"}function B2(){if(LL)return;LL=!0,console.warn("[queue] .afterCommit() was called outside of `db.transaction(...)`. Dispatching immediately. Wrap the call in a transaction or drop .afterCommit() to silence this message.")}class k{name;payload;options={};txMode="auto";constructor(L,$){this.name=L;this.payload=$}onQueue(L){return this.options.queue=L,this}delay(L){return this.options.delay=L,this}tries(L){return this.options.tries=L,this}timeout(L){return this.options.timeout=L,this}backoff(L){return this.options.backoff=L,this}withContext(L){return this.options.context=L,this}withIdempotencyKey(L){return this.options.idempotencyKey=L,this}afterCommit(){return this.txMode="after",this}withoutCommit(){return this.txMode="immediate",this}async dispatch(){let{isFaked:L,getFakeQueue:$}=await Promise.resolve().then(() => g1);if(L()){$()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(P2()){let Y=this.runDispatchPipeline.bind(this);if(D2(async()=>{await Y()}))return}else if(this.txMode==="after")B2()}await this.runDispatchPipeline()}async runDispatchPipeline(){let L=!1;if(this.options.idempotencyKey){let $=await l1(this.options.idempotencyKey,this.name,this.options.queue);if($==="duplicate")return;L=$==="claimed"}try{if(await J1(this.name,this.payload)){let V=j(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await N1({queue:this.options.queue||"default",payload:JSON.stringify(V),exception:`quarantined: ${this.name}`},"poison-detected"))return}let $=M2();if($==="database")await this.dispatchToDatabase();else if($==="redis")await this.dispatchToRedis();else if($==="sync")await w1(this.name,{payload:this.payload,context:this.options.context});else if($==="sqs"||$==="memory"||$==="beanstalkd")throw Error(`[queue] Driver "${$}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);else throw Error(`[queue] Unknown QUEUE_DRIVER "${$}". Allowed values: redis, database, sync.`)}catch($){if(L&&this.options.idempotencyKey)await c1(this.options.idempotencyKey);throw $}}async dispatchIf(L){if(L)await this.dispatch()}async dispatchUnless(L){if(!L)await this.dispatch()}async dispatchToDatabase(){let L=Math.floor(Date.now()/1000),$=this.options.delay?L+this.options.delay:L,V=j(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff}),{db:Y}=await import("@stacksjs/database");await Y.insertInto("jobs").values({queue:this.options.queue||"default",payload:JSON.stringify(V),attempts:0,reserved_at:null,available_at:$,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){let{RedisQueue:L}=await Promise.resolve().then(() => (_1(),z1)),{queue:$}=await import("@stacksjs/config"),V=$?.connections?.redis;if(!V)throw Error("Redis queue connection is not configured. Check config/queue.ts");let Y=new L(this.options.queue||"default",V),G=j(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});await Y.add(G,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await w1(this.name,{payload:this.payload,context:this.options.context})}}function $L(L,$){return new k(L,$)}function YL(L){let{PendingBatch:$}=(w(),mL(u));return new $(L)}async function w1(L,$={}){let{withTraceId:V}=await import("@stacksjs/router"),Y=$.traceId??`job:${L}:${Math.random().toString(36).slice(2,10)}`;await V(Y,async()=>{let X=(await import(Q2(`Jobs/${L}.ts`))).default;if(!X)throw Error(`Job ${L} does not export a default`);if(typeof X.handle==="function")await X.handle($.payload);else if(typeof X.action==="string"){let{runAction:_}=await import("@stacksjs/actions");await _(X.action)}else if(typeof X.action==="function")await X.action();else if(typeof X==="function")await X($.payload,$.context);else throw Error(`Job ${L} does not have a valid handler`)})}var LL=!1,VL;var Q1=m(()=>{S1();X1();R1();VL={make(L,$){return new k(L,$)},async dispatch(L,$){await new k(L,$).dispatch()},async dispatchIf(L,$,V){if(L)await new k($,V).dispatch()},async dispatchUnless(L,$,V){if(!L)await new k($,V).dispatch()},async dispatchNow(L,$){await new k(L,$).dispatchNow()},dispatchAfter(L,$,V){return new k($,V).delay(L)},async dispatchOnce(L,$,V){await new k($,V).withIdempotencyKey(L).dispatch()},async dispatchAfterCommit(L,$){await new k(L,$).afterCommit().dispatch()}}});var f1={};f(f1,{resumeQueue:()=>_L,recordCircuitSuccess:()=>XL,recordCircuitFailure:()=>KL,pauseQueue:()=>zL,listCircuitState:()=>WL,isCircuitOpen:()=>UL});import{db as J}from"@stacksjs/database";function d(){if(GL)return;GL=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing \u2014 circuit breaker disabled. "+"Run migrations to enable.")}async function $4(L,$){try{let V=await J.selectFrom("queue_circuit_state").where("queue_name","=",L).selectAll().executeTakeFirst();if(V)return V;return await J.insertInto("queue_circuit_state").values({queue_name:L,success_count:0,failure_count:0,window_start:$,paused_at:null,resume_at:null}).execute(),{queue_name:L,success_count:0,failure_count:0,window_start:$,paused_at:null,resume_at:null}}catch(V){if(B(V))return d(),null;let Y=V?.message??"";if(Y.includes("UNIQUE constraint")||Y.includes("Duplicate entry"))return await J.selectFrom("queue_circuit_state").where("queue_name","=",L).selectAll().executeTakeFirst();throw V}}async function UL(L){try{let $=await J.selectFrom("queue_circuit_state").where("queue_name","=",L).select(["paused_at","resume_at"]).executeTakeFirst();if(!$||!$.paused_at)return!1;if($.resume_at){let V=Date.parse($.resume_at.replace(" ","T")+"Z");if(Number.isFinite(V)&&Date.now()>=V)return await J.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",L).execute(),!1}return!0}catch($){if(B($))return d(),!1;throw $}}async function XL(L,$={}){let V=$.windowSeconds??300,Y=new Date().toISOString().slice(0,19).replace("T"," "),G=await $4(L,Y);if(!G)return;try{if(L4(G.window_start,V)){await J.updateTable("queue_circuit_state").set({success_count:1,failure_count:0,window_start:Y}).where("queue_name","=",L).execute();return}await J.updateTable("queue_circuit_state").set({success_count:G.success_count+1}).where("queue_name","=",L).execute()}catch(U){if(B(U)){d();return}throw U}}async function KL(L,$={}){let V=$.failureRateThreshold??0.5,Y=$.windowSeconds??300,G=$.pauseSeconds??300,U=$.minObservations??10,X=new Date,_=X.toISOString().slice(0,19).replace("T"," "),K=await $4(L,_);if(!K)return!1;if(K.paused_at)return!1;try{let{success_count:z,failure_count:O}=K;if(L4(K.window_start,Y))z=0,O=0;O+=1;let Q=z+O,P=Q===0?0:O/Q;if(Q>=U&&P>=V){let W=new Date(X.getTime()+G*1000).toISOString().slice(0,19).replace("T"," ");return await J.updateTable("queue_circuit_state").set({success_count:z,failure_count:O,window_start:_,paused_at:_,resume_at:W}).where("queue_name","=",L).execute(),!0}return await J.updateTable("queue_circuit_state").set({success_count:z,failure_count:O,window_start:L4(K.window_start,Y)?_:K.window_start}).where("queue_name","=",L).execute(),!1}catch(z){if(B(z))return d(),!1;throw z}}function L4(L,$){if(!L)return!0;let V=Date.parse(L.replace(" ","T")+"Z");if(!Number.isFinite(V))return!0;return Date.now()-V>$*1000}async function zL(L,$=300){let V=new Date,Y=V.toISOString().slice(0,19).replace("T"," "),G=new Date(V.getTime()+$*1000).toISOString().slice(0,19).replace("T"," ");await $4(L,Y);try{await J.updateTable("queue_circuit_state").set({paused_at:Y,resume_at:G}).where("queue_name","=",L).execute()}catch(U){if(B(U)){d();return}throw U}}async function _L(L){try{await J.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",L).execute()}catch($){if(B($)){d();return}throw $}}async function WL(){try{return await J.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch(L){if(B(L))return d(),[];throw L}}var GL=!1;var A1=()=>{};X1();import{env as aL}from"@stacksjs/env";function S4(){return aL.QUEUE_DRIVER||"sync"}class J4{name;description;action;handle;queue;rate;tries;timeout;backoff;backoffConfig;enabled;constructor(L){this.name=L.name,this.description=L.description,this.handle=L.handle,this.queue=L.queue,this.rate=L.rate,this.action=L.action,this.tries=L.tries,this.timeout=L.timeout,this.backoff=L.backoff,this.backoffConfig=L.backoffConfig,this.enabled=L.enabled}async dispatch(L){let{isFaked:$,getFakeQueue:V}=await Promise.resolve().then(() => g1);if($()){V()?.dispatch(this.name||"UnknownJob",L,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let Y=S4();if(Y==="sync")return this.dispatchNow(L);if(Y==="redis")return this.dispatchToRedis(L);if(Y==="database")return this.dispatchToDatabase(L);if(Y==="sqs"||Y==="memory"||Y==="beanstalkd")throw Error(`[queue] Driver "${Y}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);throw Error(`[queue] Unknown QUEUE_DRIVER "${Y}". Allowed values: redis, database, sync.`)}async dispatchIf(L,$){if(L)return this.dispatch($)}async dispatchUnless(L,$){if(!L)return this.dispatch($)}async dispatchAfter(L,$){let V=S4();if(V==="redis")return this.dispatchToRedis($,{delay:L});if(V==="database")return this.dispatchToDatabase($,{delay:L});return await new Promise((Y)=>setTimeout(Y,L*1000)),await this.dispatchNow($)}async dispatchNow(L){if(typeof this.handle==="function")await this.handle(L);else if(typeof this.action==="string"){let{runAction:$}=await import("@stacksjs/actions");await $(this.action)}else if(typeof this.action==="function")await this.action();else throw Error(`Job ${this.name} does not have a valid handler`)}async dispatchToDatabase(L,$){let V=Math.floor(Date.now()/1000),Y=$?.delay?V+$.delay:V,G=j(this.name??this.constructor.name,L,{queue:this.queue??"default",tries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0}),{db:U}=await import("@stacksjs/database");await U.insertInto("jobs").values({queue:this.queue||"default",payload:JSON.stringify(G),attempts:0,reserved_at:null,available_at:Y,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(L,$){let{RedisQueue:V}=await Promise.resolve().then(() => (_1(),z1)),{queue:Y}=await import("@stacksjs/config"),G=Y?.connections?.redis;if(!G)throw Error("Redis queue connection is not configured. Check config/queue.ts");let U=new V(this.queue||"default",G),X=j(this.name??this.constructor.name,L,{queue:this.queue??"default",tries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0});await U.add(X,{delay:$?.delay,maxTries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0})}}Q1();S1();R1();A1();X1();var V4=(L)=>`__job_progress__:${L}`,Y4=(L)=>`__job_cancel__:${L}`;function E2(L){return Number.isFinite(L)?Math.max(0,Math.min(100,L)):0}async function T2(L,$,V){let{cache:Y}=await import("@stacksjs/cache");await Y.set(V4(L),{percent:E2($),message:V,updatedAt:Date.now()},3600)}async function N2(L){let{cache:$}=await import("@stacksjs/cache");return await $.get(V4(L))??null}async function S2(L){let{cache:$}=await import("@stacksjs/cache");await $.set(Y4(L),1,3600)}async function J2(L){let{cache:$}=await import("@stacksjs/cache");return Boolean(await $.get(Y4(L)))}async function R2(L){let{cache:$}=await import("@stacksjs/cache");await Promise.all([$.del(V4(L)),$.del(Y4(L))])}import{log as Y1}from"@stacksjs/logging";import*as ZL from"@stacksjs/path";class OL{jobs=new Map;initialized=!1;register(L){this.jobs.set(L.name,L),Y1.debug(`Registered job: ${L.name} (${L.type})`)}get(L){return this.jobs.get(L)}all(){return Array.from(this.jobs.values())}byQueue(L){return this.all().filter(($)=>$.config.queue===L)}scheduled(){return this.all().filter((L)=>L.config.rate||L.config.schedule)}has(L){return this.jobs.has(L)}clear(){this.jobs.clear(),this.initialized=!1}setInitialized(L){this.initialized=L}isInitialized(){return this.initialized}}var n=new OL;async function G4(L){let $=L||ZL.userJobsPath(),V=[];try{let Y=new Bun.Glob("**/*.{ts,js}"),G={cwd:$,onlyFiles:!0,absolute:!0};for await(let U of Y.scan(G)){if(U.includes(".test.")||U.includes(".spec.")||U.endsWith("index.ts")||U.endsWith("index.js"))continue;try{let X=await C2(U);if(X)V.push(X),n.register(X)}catch(X){Y1.warn(`Failed to load job from ${U}: ${X.message}`)}}return n.setInitialized(!0),Y1.info(`Discovered ${V.length} jobs from ${$}`),V}catch(Y){return Y1.error(`Failed to discover jobs: ${Y.message}`),[]}}async function C2(L){try{let $=await import(L),V=L.split("/").pop()?.replace(/\.(ts|js)$/,"")||"UnknownJob";if($.default&&typeof $.default==="function"){let Y=$.default;if(typeof Y.handle==="function"||typeof Y.prototype?.handle==="function"){let G=Y.config||{};return{name:G.name||V,path:L,config:{name:G.name||V,description:G.description,queue:G.queue||"default",tries:G.retries||3,timeout:G.timeout,withoutOverlapping:G.withoutOverlapping,schedule:G.schedule,retryAfter:G.retryAfter},type:"class",module:Y}}}if($.default&&typeof $.default==="object"){let Y=$.default;if(typeof Y.handle==="function"||typeof Y.action==="string")return{name:Y.name||V,path:L,config:{name:Y.name||V,description:Y.description,queue:Y.queue||"default",tries:Y.tries||3,backoff:Y.backoff,rate:Y.rate,timeout:Y.timeout||Y.timeOut,backoffConfig:Y.backoffConfig},type:"function",module:Y}}return null}catch($){return Y1.debug(`Could not load job from ${L}: ${$.message}`),null}}function q2(L){return n.get(L)}function v2(){return n.all()}function U4(){return n.scheduled()}async function k2(L,$){let V=n.get(L);if(!V)throw Error(`Job "${L}" not found. Did you run discoverJobs()?`);try{if(V.type==="class"){if(typeof V.module.handle==="function")return await V.module.handle($);return await new V.module().handle($)}else{if(typeof V.module.handle==="function")return await V.module.handle($);throw Error(`Job "${L}" does not have a handle method`)}}catch(Y){throw Y1.error(`Failed to execute job "${L}": ${Y.message}`),Y}}function I2(L){let $=L.config;return{name:$.name,queue:$.queue,tries:$.tries,backoff:$.backoff,timeout:$.timeout,backoffConfig:$.backoffConfig,rate:$.rate}}import{log as T}from"@stacksjs/logging";v();import{log as y2}from"@stacksjs/logging";var FL=!1;async function xL(){if(FL)return!0;try{let{db:L}=await import("@stacksjs/database");return await L.unsafe("CREATE TABLE IF NOT EXISTS scheduled_job_runs (job_name VARCHAR(255) PRIMARY KEY, last_run_at VARCHAR(64) NOT NULL)").execute(),FL=!0,!0}catch(L){return y2.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${L instanceof Error?L.message:String(L)}`),!1}}async function HL(L){if(!await xL())return null;try{let{db:$}=await import("@stacksjs/database"),V=await $.selectFrom("scheduled_job_runs").where("job_name","=",L).select(["last_run_at"]).executeTakeFirst();if(!V?.last_run_at)return null;let Y=new Date(V.last_run_at);return Number.isNaN(Y.getTime())?null:Y}catch{return null}}async function QL(L,$){if(!await xL())return;try{let{db:V}=await import("@stacksjs/database"),Y=$.toISOString();await V.deleteFrom("scheduled_job_runs").where("job_name","=",L).execute(),await V.insertInto("scheduled_job_runs").values({job_name:L,last_run_at:Y}).execute()}catch{}}var BL={checkInterval:60000,preventOverlapping:!0},D={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...BL}},AL=new Set;function w2(L,$){if(AL.has(L))return;AL.add(L),T.warn(`[scheduler] Cron expression "${L}" specifies seconds="${$}" but the scheduler `+"ticks at minute granularity \u2014 the seconds field is being ignored. Use a 5-field expression "+"to avoid this warning, or wait for sub-minute scheduling support.")}var DL=!1,PL=new Map;function f2(L){let $=PL.get(L);if(!$)$=new Intl.DateTimeFormat("en-US",{timeZone:L,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),PL.set(L,$);return $}function X4(L,$){if($&&$!=="local"&&$!=="system")try{let V=f2($).formatToParts(L),Y=(X)=>V.find((_)=>_.type===X)?.value??"",G={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},U=Number(Y("hour"));if(U===24)U=0;return{minute:Number(Y("minute")),hour:U,day:Number(Y("day")),month:Number(Y("month")),dayOfWeek:G[Y("weekday")]??L.getDay()}}catch{if(!DL)DL=!0,T.warn(`[scheduler] Invalid timezone "${$}"; falling back to system local time.`)}return{minute:L.getMinutes(),hour:L.getHours(),day:L.getDate(),month:L.getMonth()+1,dayOfWeek:L.getDay()}}function m2(L,$,V){let Y=X4(new Date,V),G=Y.minute,U=Y.hour,X=Y.day,_=Y.month,K=Y.dayOfWeek;if($){let x=X4($,V);if(x.minute===G&&x.hour===U&&x.day===X)return!1}let z=L.trim().split(/\s+/);if(z.length===6){let x=z[0];if(x&&x!=="0"&&x!=="*")w2(L,x);z=z.slice(1)}if(z.length<5)return T.warn(`Invalid cron expression: ${L}`),!1;let[O,Q,P,Z,W]=z;return I(O,G,0,59)&&I(Q,U,0,23)&&I(P,X,1,31)&&I(Z,_,1,12)&&I(W,K,0,6)}function I(L,$,V,Y){if(L==="*")return!0;if(L.includes(","))return L.split(",").map((X)=>Number.parseInt(X.trim(),10)).includes($);if(L.includes("-")){let[U,X]=L.split("-").map((_)=>Number.parseInt(_.trim(),10));return $>=U&&$<=X}if(L.includes("/")){let[U,X]=L.split("/"),_=Number.parseInt(X,10);if(U==="*")return $%_===0;if(U.includes("-")){let[K,z]=U.split("-").map((O)=>Number.parseInt(O.trim(),10));return $>=K&&$<=z&&($-K)%_===0}}let G=Number.parseInt(L,10);return!Number.isNaN(G)&&$===G}function EL(L){let V={"@yearly":"0 0 1 1 *","@annually":"0 0 1 1 *","@monthly":"0 0 1 * *","@weekly":"0 0 * * 0","@daily":"0 0 * * *","@midnight":"0 0 * * *","@hourly":"0 * * * *"}[L.toLowerCase()];if(V)return V;let Y=L.match(/^Every\.(\w+)$/i);if(Y&&Y[1]!==void 0){let U=Y[1].toLowerCase();return{second:"* * * * *",fiveseconds:"* * * * *",tenseconds:"* * * * *",thirtyseconds:"* * * * *",minute:"* * * * *",fiveminutes:"*/5 * * * *",tenminutes:"*/10 * * * *",fifteenminutes:"*/15 * * * *",thirtyminutes:"*/30 * * * *",hour:"0 * * * *",twohours:"0 */2 * * *",sixhours:"0 */6 * * *",twelvehours:"0 */12 * * *",day:"0 0 * * *",week:"0 0 * * 0",month:"0 0 1 * *"}[U]||null}let G=L.split(/\s+/).length;if(G>=5&&G<=6)return L;return null}function TL(L,$){let V=L.trim().split(/\s+/),Y=V.length===6?V.slice(1):V;if(Y.length<5)return null;let[G,U,X,_,K]=Y,z=new Date;z.setSeconds(0,0);let O=527040;for(let Q=1;Q<=O;Q++){let P=new Date(z.getTime()+Q*60000),Z=X4(P,$);if(I(G,Z.minute,0,59)&&I(U,Z.hour,0,23)&&I(X,Z.day,1,31)&&I(_,Z.month,1,12)&&I(K,Z.dayOfWeek,0,6))return P}return null}async function j2(L={}){if(D.isRunning){T.warn("Scheduler is already running");return}D.config={...BL,...L},D.isRunning=!0,D.isShuttingDown=!1,await G4();let $=U4();for(let G of $){let U=G.config.rate||G.config.schedule;if(U){let X=EL(U);if(X){let _=await HL(G.name);D.jobs.set(G.name,{job:G,lastRun:_,nextRun:TL(X,D.config.timezone),isRunning:!1}),T.info(`Registered scheduled job: ${G.name} (${X})`)}else T.warn(`Invalid schedule for job ${G.name}: ${U}`)}}if(D.jobs.size===0){T.info("No scheduled jobs found");return}T.info(`Scheduler started with ${D.jobs.size} job(s)`),process.on("SIGINT",()=>K4()),process.on("SIGTERM",()=>K4());let V=!1,Y=()=>{if(D.isShuttingDown)return;let G=D.config.checkInterval,U=G-Date.now()%G;D.checkInterval=setTimeout(()=>{if(!D.isShuttingDown&&!V)V=!0,ML().catch((X)=>T.error("Scheduler check failed:",X)).finally(()=>{V=!1});Y()},U),D.checkInterval?.unref?.()};Y(),await ML()}async function ML(){for(let[L,$]of D.jobs){let V=$.job.config.rate||$.job.config.schedule;if(!V)continue;let Y=EL(V);if(!Y)continue;if(m2(Y,$.lastRun,D.config.timezone)){if(D.config.preventOverlapping&&$.isRunning){T.debug(`Skipping ${L}: previous execution still running`);continue}if($.job.config.withoutOverlapping&&$.isRunning){T.debug(`Skipping ${L}: withoutOverlapping is enabled`);continue}try{$.isRunning=!0,$.lastRun=new Date,$.nextRun=TL(Y,D.config.timezone),await QL(L,$.lastRun),T.info(`Dispatching scheduled job: ${L}`),await c("job:added",{jobId:`scheduled-${L}-${Date.now()}`,queueName:$.job.config.queue||"default",jobName:L}),await i1(L,{queue:$.job.config.queue||"default",payload:{},maxTries:$.job.config.tries||3,timeout:$.job.config.timeout||60}),$.isRunning=!1,T.info(`Scheduled job ${L} dispatched to queue`)}catch(G){$.isRunning=!1,T.error(`Failed to dispatch scheduled job ${L}:`,G)}}}}async function K4(){if(!D.isRunning)return;if(T.info("Stopping scheduler..."),D.isShuttingDown=!0,D.checkInterval)clearTimeout(D.checkInterval),D.checkInterval=null;D.isRunning=!1,D.jobs.clear(),T.info("Scheduler stopped")}function h2(){return{isRunning:D.isRunning,jobCount:D.jobs.size,jobs:Array.from(D.jobs.entries()).map(([L,$])=>({name:L,schedule:$.job.config.rate||$.job.config.schedule,lastRun:$.lastRun,nextRun:$.nextRun,isRunning:$.isRunning}))}}function p2(){return D.isRunning}function g2(){return new Map(D.jobs)}async function u2(L){let $=D.jobs.get(L);if(!$)throw Error(`Scheduled job "${L}" not found`);T.info(`Manually triggering scheduled job: ${L}`),await i1(L,{queue:$.job.config.queue||"default",payload:{},maxTries:$.job.config.tries||3,timeout:$.job.config.timeout||60})}v();v();import{log as b2}from"@stacksjs/logging";var l2={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function z4(L={}){let $={...l2,...L},V=[],Y=new Date,G=Math.floor(Y.getTime()/1000);try{let{db:U}=await import("@stacksjs/database"),X=await U.selectFrom("jobs").selectAll().execute(),_=await U.selectFrom("failed_jobs").selectAll().execute(),K=new Map;for(let H of X){let M=H.queue||"default";if(!K.has(M))K.set(M,{pending:0,processing:0,delayed:0});let S=K.get(M);if(H.reserved_at)S.processing++;else if(H.available_at&&H.available_at>G)S.delayed++;else if(S.pending++,H.created_at){let q=typeof H.created_at==="number"?H.created_at:Math.floor(new Date(H.created_at).getTime()/1000),x4=G-q;if(!S.oldestAge||x4>S.oldestAge)S.oldestAge=x4}}let z=new Map;for(let H of _){let M=H.queue||"default";z.set(M,(z.get(M)||0)+1)}let O=[],Q=new Set([...K.keys(),...z.keys()]),P=$.queues?[...Q].filter((H)=>$.queues.includes(H)):[...Q],Z=0,W=0,x=0,t=0;for(let H of P){let M=K.get(H)||{pending:0,processing:0,delayed:0},S=z.get(H)||0;Z+=M.pending,W+=M.processing,x+=M.delayed,t+=S;let q="healthy";if(M.pending>=$.maxPendingCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${H}" has ${M.pending} pending jobs (threshold: ${$.maxPendingCritical})`,queue:H,timestamp:Y.toISOString()});else if(M.pending>=$.maxPendingWarning)q="degraded",V.push({level:"warning",message:`Queue "${H}" has ${M.pending} pending jobs (threshold: ${$.maxPendingWarning})`,queue:H,timestamp:Y.toISOString()});if(S>=$.maxFailedCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${H}" has ${S} failed jobs (threshold: ${$.maxFailedCritical})`,queue:H,timestamp:Y.toISOString()});else if(S>=$.maxFailedWarning){if(q==="healthy")q="degraded";V.push({level:"warning",message:`Queue "${H}" has ${S} failed jobs (threshold: ${$.maxFailedWarning})`,queue:H,timestamp:Y.toISOString()})}if(M.oldestAge){if(M.oldestAge>=$.maxJobAgeCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${H}" has a job waiting for ${Math.floor(M.oldestAge/3600)} hours`,queue:H,timestamp:Y.toISOString()});else if(M.oldestAge>=$.maxJobAgeWarning){if(q==="healthy")q="degraded";V.push({level:"warning",message:`Queue "${H}" has a job waiting for ${Math.floor(M.oldestAge/60)} minutes`,queue:H,timestamp:Y.toISOString()})}}O.push({name:H,status:q,pending:M.pending,processing:M.processing,delayed:M.delayed,failed:S,oldestJobAge:M.oldestAge})}let O4=v1().getMetrics(),qL=O4.throughputPerMinute,vL=O4.averageDuration,F4=Z+W+x+t,U1=F4>0?t/F4:0,r="healthy";if(O.some((H)=>H.status==="unhealthy"))r="unhealthy";else if(O.some((H)=>H.status==="degraded"))r="degraded";if(U1>=$.maxErrorRateCritical)r="unhealthy",V.push({level:"critical",message:`Overall error rate is ${(U1*100).toFixed(1)}% (threshold: ${$.maxErrorRateCritical*100}%)`,timestamp:Y.toISOString()});else if(U1>=$.maxErrorRateWarning){if(r==="healthy")r="degraded";V.push({level:"warning",message:`Overall error rate is ${(U1*100).toFixed(1)}% (threshold: ${$.maxErrorRateWarning*100}%)`,timestamp:Y.toISOString()})}let kL=O1().getAll().map((H)=>({id:H.id,status:H.status,queue:H.queue,processedCount:H.processedCount,failedCount:H.failedCount,lastActivityAt:H.lastActivityAt}));return{status:r,timestamp:Y.toISOString(),queues:O,workers:kL,metrics:{totalPending:Z,totalProcessing:W,totalDelayed:x,totalFailed:t,throughputPerMinute:qL,averageProcessingTime:vL,errorRate:U1},alerts:V}}catch(U){b2.error("Failed to perform queue health check:",U);let _=O1().getAll().map((K)=>({id:K.id,status:K.status,queue:K.queue,processedCount:K.processedCount,failedCount:K.failedCount,lastActivityAt:K.lastActivityAt}));return{status:"unhealthy",timestamp:Y.toISOString(),queues:[],workers:_,metrics:{totalPending:0,totalProcessing:0,totalDelayed:0,totalFailed:0,throughputPerMinute:0,averageProcessingTime:0,errorRate:0},alerts:[{level:"critical",message:`Health check failed: ${U.message}`,timestamp:Y.toISOString()}]}}}function c2(L={}){return async($)=>{let V=await z4(L),Y=V.status==="healthy"?200:V.status==="degraded"?207:503;return new Response(JSON.stringify(V,null,2),{status:Y,headers:{"Content-Type":"application/json","Cache-Control":"no-store"}})}}async function i2(L={}){return(await z4(L)).status==="healthy"}import{log as C}from"@stacksjs/logging";var d2={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function D1(L){return String(L).replace(/[&<>"']/g,($)=>d2[$]??$)}class _4{config;notificationCount=0;lastResetTime=Date.now();pendingBatch=[];batchTimeout=null;activeFlush=null;constructor(L){this.config=L}async notify(L){if(this.config.filter&&!this.config.filter(L))return;if(this.config.rateLimit){let $=Date.now();if($-this.lastResetTime>3600000)this.notificationCount=0,this.lastResetTime=$;if(this.notificationCount>=this.config.rateLimit){C.debug("Rate limit reached for failed job notifications");return}}if(this.config.batch){if(this.pendingBatch.push(L),!this.batchTimeout)this.batchTimeout=setTimeout(()=>{this.batchTimeout=null,this.activeFlush=this.flushBatch().catch(($)=>C.error("Failed to flush notification batch:",$)).finally(()=>{this.activeFlush=null})},this.config.batchInterval||60000),this.batchTimeout.unref?.();return}await this.sendNotifications([L])}async flushBatch(){if(this.pendingBatch.length===0)return;let L=[...this.pendingBatch];if(this.pendingBatch=[],this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;await this.sendNotifications(L)}async shutdown(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.activeFlush)await this.activeFlush.catch(()=>{});await this.flushBatch().catch((L)=>C.error("Failed to flush notification batch on shutdown:",L))}async sendNotifications(L){let $=[];for(let V of this.config.channels)switch(V){case"email":if(this.config.email)$.push(this.sendEmail(L));break;case"slack":if(this.config.slack)$.push(this.sendSlack(L));break;case"discord":if(this.config.discord)$.push(this.sendDiscord(L));break;case"webhook":if(this.config.webhook)$.push(this.sendWebhook(L));break;case"log":this.logFailures(L);break}try{await Promise.all($)}catch(V){C.error("Failed to send job failure notifications:",V)}finally{this.notificationCount+=L.length}}async sendEmail(L){let $=this.config.email,V=$.subject||`[Queue] ${L.length} Job(s) Failed`,Y=this.formatEmailBody(L);try{let{mail:G}=await import("@stacksjs/email");await G.send({to:Array.isArray($.to)?$.to:[$.to],from:$.from,subject:V,html:Y})}catch(G){C.error("Failed to send email notification:",G)}}formatEmailBody(L){let $=L.map((V)=>`
2
+ var{defineProperty:m1,getOwnPropertyNames:yL,getOwnPropertyDescriptor:wL}=Object,fL=Object.prototype.hasOwnProperty;function mL(L){return this[L]}var jL=(L)=>{var $=(Q4??=new WeakMap).get(L),V;if($)return $;if($=m1({},"__esModule",{value:!0}),L&&typeof L==="object"||typeof L==="function"){for(var Y of yL(L))if(!fL.call($,Y))m1($,Y,{get:mL.bind(L,Y),enumerable:!(V=wL(L,Y))||V.enumerable})}return Q4.set(L,$),$},Q4;var hL=(L)=>L;function pL(L,$){this[L]=hL.bind(null,$)}var m=(L,$)=>{for(var V in $)m1(L,V,{get:$[V],enumerable:!0,configurable:!0,set:pL.bind($,V)})};var y=(L,$)=>()=>(L&&($=L(L=0)),$);var F=import.meta.require;function j(L,$,V){return{jobName:L,payload:$,options:V,envelopeVersion:1,dispatchedAt:new Date().toISOString()}}function A4(L,$){if(j1.has(L))return;j1.add(L),console.warn($)}function uL(){j1.clear()}function B1(L){let $;if(typeof L==="string")try{$=JSON.parse(L)}catch{return{ok:!1,reason:"malformed",detail:"not valid JSON"}}else if(L&&typeof L==="object")$=L;else return{ok:!1,reason:"malformed",detail:`expected string or object, got ${typeof L}`};if($.envelopeVersion===1){if(typeof $.jobName!=="string")return{ok:!1,reason:"missing-job-name"};return{ok:!0,envelope:{jobName:$.jobName,payload:$.payload,options:$.options??void 0,envelopeVersion:1,dispatchedAt:typeof $.dispatchedAt==="string"?$.dispatchedAt:new Date(0).toISOString()},source:"v1"}}if(typeof $.envelopeVersion==="number"&&$.envelopeVersion>1)return{ok:!1,reason:"unknown-version",detail:`envelopeVersion=${$.envelopeVersion}, this worker speaks v1`};if(typeof $.jobName==="string")return A4("v0-implicit","[queue/envelope] Processing pre-#1884 job envelope without envelopeVersion. In-flight jobs from before the upgrade will continue to work; new dispatches use the v1 shape automatically."),{ok:!0,envelope:{jobName:$.jobName,payload:$.payload,options:$.options??void 0,envelopeVersion:1,dispatchedAt:typeof $.dispatchedAt==="string"?$.dispatchedAt:new Date(0).toISOString()},source:"v0-implicit"};if(typeof $.job==="string")return A4("laravel-legacy","[queue/envelope] Processing Laravel-legacy job envelope (`{ job, data }` shape). "+"Will continue to process but the queue table contains migration-era rows \u2014 "+"consider flushing once they drain."),{ok:!0,envelope:{jobName:$.job.replace(/^App\\+Jobs\\+/,"").replace(/^.*\\/,""),payload:$.data,options:void 0,envelopeVersion:1,dispatchedAt:new Date(0).toISOString()},source:"laravel-legacy"};return{ok:!1,reason:"missing-job-name"}}var gL=1,j1;var U1=y(()=>{j1=new Set});var g1={};m(g1,{runJob:()=>E4,restore:()=>p1,isFaked:()=>M4,getFakeQueue:()=>P4,fake:()=>h1,expectJobToFail:()=>T4,createQueueTester:()=>B4,QueueTester:()=>E1});class D4{dispatchedJobs=[];pushedJobs=[];processedJobs=[];failedJobs=[];dispatch(L,$,V={}){this.dispatchedJobs.push({name:L,data:$,options:V,dispatchedAt:new Date,queue:V.queue||"default"})}push(L,$,V={}){this.pushedJobs.push({name:L,data:$,options:V,dispatchedAt:new Date,queue:V.queue||"default"})}dispatched(L){if(L)return this.dispatchedJobs.filter(($)=>$.name===L);return[...this.dispatchedJobs]}pushed(L){if(L)return this.pushedJobs.filter(($)=>$.name===L);return[...this.pushedJobs]}assertDispatched(L,$){let V=this.dispatched(L);if(V.length===0)throw Error(`Expected job "${L}" to be dispatched, but it was not.`);if($){if(V.filter($).length===0)throw Error(`Expected job "${L}" to be dispatched matching the callback, but no matching jobs were found.`)}}assertNotDispatched(L){let $=this.dispatched(L);if($.length>0)throw Error(`Expected job "${L}" to not be dispatched, but it was dispatched ${$.length} time(s).`)}assertDispatchedTimes(L,$){let V=this.dispatched(L);if(V.length!==$)throw Error(`Expected job "${L}" to be dispatched ${$} time(s), but it was dispatched ${V.length} time(s).`)}assertNothingDispatched(){if(this.dispatchedJobs.length>0){let L=[...new Set(this.dispatchedJobs.map(($)=>$.name))].join(", ");throw Error(`Expected no jobs to be dispatched, but found: ${L}`)}}assertPushed(L,$){let V=this.pushed(L);if(V.length===0)throw Error(`Expected job "${L}" to be pushed, but it was not.`);if($){if(V.filter($).length===0)throw Error(`Expected job "${L}" to be pushed matching the callback, but no matching jobs were found.`)}}assertPushedWithDelay(L,$){if(this.pushed(L).filter((G)=>G.options.delay===$).length===0)throw Error(`Expected job "${L}" to be pushed with delay ${$}ms, but no matching jobs were found.`)}assertPushedOn(L,$){if(this.pushed($).filter((Y)=>Y.queue===L).length===0)throw Error(`Expected job "${$}" to be pushed on queue "${L}", but it was not.`)}async processJob(L,$){let V=this.dispatchedJobs.find((Y)=>Y.name===L);if(!V)throw Error(`No dispatched job found with name "${L}"`);try{await $(V.data),this.processedJobs.push(V)}catch(Y){throw this.failedJobs.push({job:V,error:Y}),Y}}processed(L){if(L)return this.processedJobs.filter(($)=>$.name===L);return[...this.processedJobs]}failed(L){if(L)return this.failedJobs.filter(($)=>$.job.name===L);return[...this.failedJobs]}reset(){this.dispatchedJobs=[],this.pushedJobs=[],this.processedJobs=[],this.failedJobs=[]}}function h1(){return X1=new D4,X1}function P4(){return X1}function M4(){return X1!==null}function p1(){X1=null}class E1{queue;constructor(){this.queue=h1()}dispatch(L,$,V={}){return this.queue.dispatch(L,$,V),this}push(L,$,V={}){return this.queue.push(L,$,V),this}assertDispatched(L,$){return this.queue.assertDispatched(L,$),this}assertNotDispatched(L){return this.queue.assertNotDispatched(L),this}assertDispatchedTimes(L,$){return this.queue.assertDispatchedTimes(L,$),this}assertNothingDispatched(){return this.queue.assertNothingDispatched(),this}dispatched(L){return this.queue.dispatched(L)}reset(){return this.queue.reset(),this}cleanup(){p1()}}function B4(){return new E1}async function E4(L,$){return await L.handle($)}async function T4(L,$,V){try{throw await L.handle($),Error("Expected job to fail, but it succeeded")}catch(Y){if(Y.message==="Expected job to fail, but it succeeded")throw Y;if(V){let G=Y.message;if(typeof V==="string"){if(!G.includes(V))throw Error(`Expected error to contain "${V}", got "${G}"`)}else if(!V.test(G))throw Error(`Expected error to match ${V}, got "${G}"`)}return Y}}var X1=null;var z1={};m(z1,{setQueueManager:()=>rL,getQueueManager:()=>sL,dispatchSync:()=>nL,dispatchAfter:()=>dL,dispatch:()=>iL,createRedisDispatcher:()=>aL,chain:()=>cL,batch:()=>lL,StacksQueueManager:()=>N4,RedisQueue:()=>r,RedisJob:()=>S4,QueueManager:()=>tL});import{Queue as bL,batch as lL,chain as cL,dispatch as iL,dispatchAfter as dL,dispatchSync as nL,getQueueManager as sL,QueueManager as tL,setQueueManager as rL}from"@stacksjs/bun-queue";import{log as u1}from"@stacksjs/logging";class r{queue;config;isProcessing=!1;constructor(L,$){this.config=$,this.queue=new bL(L,{driver:"redis",prefix:$.prefix,redis:$.redis?{url:$.redis.url||this.buildRedisUrl($.redis)}:void 0,defaultJobOptions:$.defaultJobOptions?{delay:$.defaultJobOptions.delay,attempts:$.defaultJobOptions.attempts,backoff:$.defaultJobOptions.backoff,removeOnComplete:$.defaultJobOptions.removeOnComplete,removeOnFail:$.defaultJobOptions.removeOnFail,priority:$.defaultJobOptions.priority,lifo:$.defaultJobOptions.lifo,timeout:$.defaultJobOptions.timeout,jobId:$.defaultJobOptions.jobId,dependsOn:$.defaultJobOptions.dependsOn,keepJobs:$.defaultJobOptions.keepJobs,deadLetter:$.defaultJobOptions.deadLetter}:void 0,limiter:$.limiter,metrics:$.metrics,stalledJobCheckInterval:$.stalledJobCheckInterval,maxStalledJobRetries:$.maxStalledJobRetries,distributedLock:$.distributedLock,defaultDeadLetterOptions:$.defaultDeadLetterOptions,horizontalScaling:$.horizontalScaling,logLevel:$.logLevel}),u1.debug(`Redis queue "${L}" initialized`)}buildRedisUrl(L){let $=L.host||"localhost",V=L.port||6379,Y=L.password?`:${encodeURIComponent(L.password)}@`:"",G=Number.isFinite(L.db)&&L.db>=0?L.db:0;return`redis://${Y}${$}:${V}/${G}`}async add(L,$){let V={delay:$?.delay?$.delay*1000:void 0,attempts:$?.maxTries,priority:$?.priority,timeout:$?.timeout?$.timeout*1000:void 0,backoff:Array.isArray($?.backoff)?$.backoff.map((Y)=>(Number(Y)||1)*1000):$?.backoff};return this.queue.add(L,V)}process(L,$){if(this.isProcessing){u1.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process(L,$),u1.info(`Started processing queue with concurrency ${L}`)}async getJob(L){return this.queue.getJob(L)}async getJobs(L){return this.queue.getJobs(L)}async getJobCounts(){return this.queue.getJobCounts()}async removeJob(L){return this.queue.removeJob(L)}async pause(){return this.queue.pause()}async resume(){return this.queue.resume()}async empty(){return this.queue.empty()}async close(){return this.isProcessing=!1,this.queue.close()}async getMetrics(){return this.queue.getMetrics()}async ping(){return this.queue.ping()}async scheduleCron(L){return this.queue.scheduleCron({cronExpression:L.cron,data:L.data,timezone:L.tz,jobId:L.name})}async unscheduleCron(L){return this.queue.unscheduleCron(L)}async getDeadLetterJobs(){return this.queue.getDeadLetterJobs()}async republishDeadLetterJob(L){return this.queue.republishDeadLetterJob(L)}async clearDeadLetterQueue(){return this.queue.clearDeadLetterQueue()}async bulkRemove(L){return this.queue.bulkRemove(L)}async getClusterInfo(){return this.queue.getClusterInfo()}isLeader(){return this.queue.isLeader()}getQueue(){return this.queue}on(L,$){this.queue.events.on(L,$)}}class N4{config;queues=new Map;defaultConnection="default";constructor(L){this.config=L;for(let[$,V]of Object.entries(L))if(V.driver==="redis")this.queues.set($,new r($,V))}queue(L){let $=L||this.defaultConnection,V=this.queues.get($);if(!V){let Y=this.config[$];if(!Y)throw Error(`Queue "${$}" not configured`);V=new r($,Y),this.queues.set($,V)}return V}setDefaultConnection(L){this.defaultConnection=L}async closeAll(){let L=Array.from(this.queues.values()).map(($)=>$.close());await Promise.all(L),this.queues.clear()}}function aL(L,$){let V=new r(L,$);return async(Y,G)=>{return V.add(Y,G)}}class S4{data;options={};queue;constructor(L,$,V){this.data=V;this.queue=new r(L,$)}async dispatch(){await this.queue.add(this.data,this.options)}async dispatchNow(){await this.queue.add(this.data,{...this.options,immediate:!0})}delay(L){return this.options.delay=L,this}afterResponse(){return this.options.afterResponse=!0,this}chain(L){return this.options.chainedJobs=L,this}onQueue(L){return this.options.queue=L,this}priority(L){return this.options.priority=L,this}tries(L){return this.options.maxTries=L,this}timeout(L){return this.options.timeout=L,this}backoff(L){return this.options.backoff=L,this}}var _1=()=>{};function B(L){let $=L,V=$?.errno;if(typeof V==="string"&&V.toUpperCase()==="42P01")return!0;if(typeof V==="number"&&V===1146)return!0;if((typeof $?.code==="string"?$.code.toUpperCase():"")==="42P01")return!0;let G=$?.message??"";return G.includes("no such table")||G.includes("doesn't exist")||G.includes("does not exist")}var I4={};m(I4,{retryDeadLetterJob:()=>v4,purgeDeadLetterJobs:()=>k4,moveToDeadLetter:()=>N1,listDeadLetterJobs:()=>q4});import{db as a}from"@stacksjs/database";function T1(){if(C4)return;C4=!0,console.warn("[queue/dlq] dead_letter_jobs table missing \u2014 DLQ disabled. "+"Run migrations to enable poison-message isolation.")}async function N1(L,$,V=1){let Y=new Date().toISOString().slice(0,19).replace("T"," ");try{return await a.insertInto("dead_letter_jobs").values({uuid:L.uuid??crypto.randomUUID(),connection:L.connection??"database",queue:L.queue??"default",payload:L.payload??"{}",exception:L.exception??"unknown",reason:$,total_failures:V,first_failed_at:L.failed_at??Y,last_failed_at:Y,dead_lettered_at:Y}).execute(),!0}catch(G){if(B(G))return T1(),!1;throw G}}async function q4(L={}){try{let $=a.selectFrom("dead_letter_jobs").selectAll();if(L.queue)$=$.where("queue","=",L.queue);if(L.reason)$=$.where("reason","=",L.reason);if(L.sinceCutoffMs){let Y=new Date(L.sinceCutoffMs).toISOString().slice(0,19).replace("T"," ");$=$.where("dead_lettered_at",">=",Y)}if(L.limit&&L.limit>0)$=$.limit(L.limit);return await $.execute()??[]}catch($){if(B($))return T1(),[];throw $}}async function v4(L){try{let $=await a.selectFrom("dead_letter_jobs").where("id","=",L).selectAll().executeTakeFirst();if(!$)return!1;let V=Math.floor(Date.now()/1000);return await a.insertInto("jobs").values({queue:$.queue,payload:$.payload,attempts:0,reserved_at:null,available_at:V,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),await a.deleteFrom("dead_letter_jobs").where("id","=",L).execute(),!0}catch($){if(B($))return T1(),!1;throw $}}async function k4(L=30){try{let $=new Date(Date.now()-L*24*60*60*1000).toISOString().slice(0,19).replace("T"," "),V=await a.deleteFrom("dead_letter_jobs").where("dead_lettered_at","<",$).execute();return Number(V?.numDeletedRows??V?.[0]?.numDeletedRows??V?.affectedRows??0)}catch($){if(B($))return T1(),0;throw $}}var C4=!1;var S1=()=>{};function b1(){if(y4)return;y4=!0,console.warn("[queue/idempotency] job_idempotency table missing \u2014 idempotency keys are accepted but NOT enforced. "+"Run migrations to enable dedup.")}async function eL(L){try{let{db:$}=await import("@stacksjs/database"),V=await $.selectFrom("job_idempotency").where("idempotency_key","=",L).select(["idempotency_key"]).executeTakeFirst();return Boolean(V)}catch($){if(B($))return b1(),!1;throw $}}async function L2(L,$,V){try{let{db:Y}=await import("@stacksjs/database");await Y.insertInto("job_idempotency").values({idempotency_key:L,job_name:$,queue:V??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}catch(Y){if(B(Y)){b1();return}let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return;throw Y}}async function l1(L,$,V){try{let{db:Y}=await import("@stacksjs/database");return await Y.insertInto("job_idempotency").values({idempotency_key:L,job_name:$,queue:V??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),"claimed"}catch(Y){if(B(Y))return b1(),"unenforced";let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return"duplicate";throw Y}}async function c1(L){try{let{db:$}=await import("@stacksjs/database");await $.deleteFrom("job_idempotency").where("idempotency_key","=",L).execute()}catch{}}var y4=!1;var i1=()=>{};var p4={};m(p4,{unquarantineJob:()=>j4,recordFailureForPoison:()=>f4,quarantineJob:()=>m4,listQuarantined:()=>h4,isQuarantined:()=>J1,hashPayload:()=>Z1});import{createHash as $2}from"crypto";import{db as R}from"@stacksjs/database";function W1(){if(w4)return;w4=!0,console.warn("[queue/poison] job_quarantine table missing \u2014 poison detection disabled. "+"Run migrations to enable.")}function Z1(L){let $;try{$=typeof L==="string"?L:JSON.stringify(L??null)}catch{$=String(L)}return $2("sha256").update($).digest("hex").slice(0,32)}async function f4(L,$,V={}){let Y=V.maxFailures??5,G=V.windowMinutes??60,U=Z1($),X=new Date,_=X.toISOString().slice(0,19).replace("T"," ");try{let K=await R.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","=",U).selectAll().executeTakeFirst();if(!K)return await R.insertInto("job_quarantine").values({job_name:L,payload_hash:U,failure_count:1,window_start:_,quarantined_at:null}).execute(),!1;if(K.quarantined_at)return!0;let z=Date.parse(K.window_start.replace(" ","T")+"Z"),O=X.getTime()-z,Q=G*60*1000;if(Number.isFinite(z)&&O>Q)return await R.updateTable("job_quarantine").set({failure_count:1,window_start:_}).where("id","=",K.id).execute(),!1;let P=K.failure_count+1;if(P>=Y)return await R.updateTable("job_quarantine").set({failure_count:P,quarantined_at:_}).where("id","=",K.id).execute(),!0;return await R.updateTable("job_quarantine").set({failure_count:P}).where("id","=",K.id).execute(),!1}catch(K){if(B(K))return W1(),!1;throw K}}async function J1(L,$){let V=Z1($);try{let Y=await R.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","in",[V,"*"]).where("quarantined_at","is not",null).select(["id"]).executeTakeFirst();return Boolean(Y)}catch(Y){if(B(Y))return W1(),!1;throw Y}}async function m4(L,$){let V=$===void 0?"*":Z1($),Y=new Date().toISOString().slice(0,19).replace("T"," ");try{let G=await R.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","=",V).select(["id"]).executeTakeFirst();if(G)await R.updateTable("job_quarantine").set({quarantined_at:Y}).where("id","=",G.id).execute();else await R.insertInto("job_quarantine").values({job_name:L,payload_hash:V,failure_count:0,window_start:Y,quarantined_at:Y}).execute()}catch(G){if(B(G)){W1();return}throw G}}async function j4(L){try{await R.deleteFrom("job_quarantine").where("job_name","=",L).execute()}catch($){if(B($)){W1();return}throw $}}async function h4(){try{return await R.selectFrom("job_quarantine").where("quarantined_at","is not",null).selectAll().execute()??[]}catch(L){if(B(L))return W1(),[];throw L}}var w4=!1;var R1=()=>{};function o(L){let $=L?.numUpdatedRows;if($===null||$===void 0)return 0;if(typeof $==="object")return Number($.changes??0);return Number($)}async function d1(L,$){let V=JSON.stringify({jobName:L,payload:$.payload||{},options:{queue:$.queue,tries:$.maxTries,timeout:$.timeout,backoff:$.backoff}}),{db:Y}=await import("@stacksjs/database");await Y.insertInto("jobs").values({queue:$.queue||"default",payload:V,attempts:0,available_at:V2($.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}function V2(L){let $=Date.now();return Math.floor($/1000+L)}var h={};m(h,{withEvents:()=>u4,onQueueEvent:()=>g4,getWorkerTracker:()=>O1,getQueueEvents:()=>e,getGlobalMetrics:()=>v1,emitQueueEvent:()=>l,QueueMetrics:()=>q1,QueueEvents:()=>C1,OnQueueEvent:()=>b4});import{log as w}from"@stacksjs/logging";class C1{handlers=new Map;wildcardHandlers=new Set;on(L,$){if(!this.handlers.has(L))this.handlers.set(L,new Set);return this.handlers.get(L).add($),()=>{this.handlers.get(L)?.delete($)}}onAny(L){return this.wildcardHandlers.add(L),()=>{this.wildcardHandlers.delete(L)}}once(L,$){let V=async(Y)=>{this.handlers.get(L)?.delete(V),await $(Y)};return this.on(L,V)}async emit(L,$){let V={...$,timestamp:Date.now()};this.logEvent(L,V);let Y=this.handlers.get(L);if(Y)for(let G of Y)try{await G(V)}catch(U){w.error(`Error in queue event handler for ${L}:`,U)}for(let G of this.wildcardHandlers)try{await G(L,V)}catch(U){w.error("Error in wildcard queue event handler:",U)}}logEvent(L,$){let V=$.jobId?`[${$.jobId}]`:"",Y=$.queueName?`on ${$.queueName}`:"";switch(L){case"job:added":w.debug(`Job added ${V} ${Y}`);break;case"job:processing":w.debug(`Job processing ${V} ${Y}`);break;case"job:completed":w.info(`Job completed ${V} ${Y} in ${$.duration}ms`);break;case"job:failed":w.error(`Job failed ${V} ${Y}:`,$.error);break;case"job:retrying":w.warn(`Job retrying ${V} ${Y} (attempt ${$.attemptsMade})`);break;case"job:stalled":w.warn(`Job stalled ${V} ${Y}`);break;case"queue:error":w.error(`Queue error ${Y}:`,$.error);break}}off(L){this.handlers.delete(L)}removeAllListeners(){this.handlers.clear(),this.wildcardHandlers.clear()}}function e(){if(!n1)n1=new C1;return n1}function g4(L,$){let V=e();if(L==="*")return V.onAny($);return V.on(L,$)}function l(L,$){return e().emit(L,$)}function u4(L,$){return async(...V)=>{let Y=V[0]?.id||"unknown",G=Date.now();await l("job:processing",{jobId:Y,queueName:L,data:V[0]?.data});try{let U=await $(...V);return await l("job:completed",{jobId:Y,queueName:L,result:U,duration:Date.now()-G}),U}catch(U){throw await l("job:failed",{jobId:Y,queueName:L,error:U,duration:Date.now()-G}),U}}}function b4(L){return function($,V,Y){let G=Y.value;return e().on(L,G),Y}}class q1{jobCounts={added:0,completed:0,failed:0,processing:0};completions=[];errors=[];unsubscribe=[];constructor(){this.setupListeners()}setupListeners(){let L=e();this.unsubscribe.push(L.on("job:added",()=>{this.jobCounts.added++}),L.on("job:processing",()=>{this.jobCounts.processing++}),L.on("job:completed",($)=>{this.jobCounts.completed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1);let V=$.duration||0;if(this.completions.push({timestamp:Date.now(),duration:V}),this.completions.length>1000)this.completions.shift()}),L.on("job:failed",($)=>{if(this.jobCounts.failed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1),$.error){if(this.errors.push({error:$.error,timestamp:Date.now()}),this.errors.length>100)this.errors.shift()}}))}getThroughputPerMinute(){let L=Date.now()-60000;return this.completions.filter((V)=>V.timestamp>=L).length}getAverageProcessingTime(){let L=Date.now()-60000,$=this.completions.filter((V)=>V.timestamp>=L);if($.length===0)return 0;return $.reduce((V,Y)=>V+Y.duration,0)/$.length}getMetrics(){return{counts:{...this.jobCounts},averageDuration:this.getAverageProcessingTime(),recentErrors:[...this.errors],throughputPerMinute:this.getThroughputPerMinute()}}reset(){this.jobCounts={added:0,completed:0,failed:0,processing:0},this.completions=[],this.errors=[]}stop(){this.unsubscribe.forEach((L)=>L()),this.unsubscribe=[]}}function v1(){if(!s1)s1=new q1;return s1}class l4{workers=new Map;register(L,$){this.workers.set(L,{id:L,status:"idle",queue:$,processedCount:0,failedCount:0,lastActivityAt:new Date().toISOString(),startedAt:new Date().toISOString()})}markActive(L){let $=this.workers.get(L);if($)$.status="active",$.lastActivityAt=new Date().toISOString()}markIdle(L){let $=this.workers.get(L);if($)$.status="idle",$.lastActivityAt=new Date().toISOString()}recordCompletion(L){let $=this.workers.get(L);if($)$.processedCount++,$.lastActivityAt=new Date().toISOString()}recordFailure(L){let $=this.workers.get(L);if($)$.failedCount++,$.lastActivityAt=new Date().toISOString()}unregister(L){let $=this.workers.get(L);if($)$.status="stopped"}getAll(){return Array.from(this.workers.values())}clear(){this.workers.clear()}}function O1(){return Y2}var n1=null,s1=null,Y2;var v=y(()=>{Y2=new l4});var u={};m(u,{recordBatchJobFailure:()=>L4,recordBatchJobCompletion:()=>e1,isBatchCancelled:()=>LL,getBatchCallbacks:()=>F1,PendingBatch:()=>I1,DispatchedBatch:()=>p,Batch:()=>t1});var{RedisClient:G2}=globalThis.Bun;import{log as E}from"@stacksjs/logging";import{env as U2}from"@stacksjs/env";function g(){return U2.QUEUE_DRIVER||"sync"}class I1{jobs;options={thenCallbacks:[],catchCallbacks:[],finallyCallbacks:[],progressCallbacks:[]};constructor(L){this.jobs=L.map(($)=>("job"in $)?$:{job:$})}name(L){return this.options.name=L,this}onQueue(L){return this.options.queue=L,this}allowFailures(){return this.options.allowFailures=!0,this}then(L){return this.options.thenCallbacks.push(L),this}catch(L){return this.options.catchCallbacks.push(L),this}finally(L){return this.options.finallyCallbacks.push(L),this}progress(L){return this.options.progressCallbacks.push(L),this}thenHandler(L){return this.options.thenHandler=L,this}catchHandler(L){return this.options.catchHandler=L,this}finallyHandler(L){return this.options.finallyHandler=L,this}async dispatch(){let L=crypto.randomUUID(),$=this.jobs.length;if($===0)throw Error("Cannot dispatch an empty batch");let V=g();await K2({id:L,name:this.options.name||"",total_jobs:$,pending_jobs:$,failed_jobs:0,failed_job_ids:"[]",options:JSON.stringify({queue:this.options.queue,allowFailures:this.options.allowFailures||!1}),cancelled_at:null,created_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:null,then_handler:this.options.thenHandler?JSON.stringify(this.options.thenHandler):null,catch_handler:this.options.catchHandler?JSON.stringify(this.options.catchHandler):null,finally_handler:this.options.finallyHandler?JSON.stringify(this.options.finallyHandler):null}),X2(L,this.options);try{let{emitQueueEvent:Y}=await Promise.resolve().then(() => (v(),h));await Y("batch:added",{jobId:L,data:{name:this.options.name,totalJobs:$}})}catch{}for(let Y=0;Y<this.jobs.length;Y++){let G=this.jobs[Y];if(!G)continue;let{job:U,payload:X}=G,_={...X,_batchId:L,_batchIndex:Y};if(this.options.queue&&!U.queue)U.queue=this.options.queue;if(V==="sync")try{await U.dispatchNow(_),await e1(L)}catch(K){await L4(L,`${L}:${Y}`,K)}else await U.dispatch(_)}return E.info(`[Batch] Dispatched batch "${this.options.name||L}" with ${$} jobs`),new p(L)}getJobs(){return[...this.jobs]}getOptions(){return this.options}}class p{id;constructor(L){this.id=L}async fresh(){return c(this.id)}async getName(){return(await this.fresh())?.name||""}async totalJobs(){return(await this.fresh())?.total_jobs||0}async pendingJobs(){return(await this.fresh())?.pending_jobs||0}async failedJobs(){return(await this.fresh())?.failed_jobs||0}async completedJobs(){let L=await this.fresh();if(!L)return 0;return L.total_jobs-L.pending_jobs}async progress(){let L=await this.fresh();if(!L||L.total_jobs===0)return 0;let $=L.total_jobs-L.pending_jobs;return Math.round($/L.total_jobs*100)}async finished(){return(await this.fresh())?.finished_at!==null}async cancelled(){return(await this.fresh())?.cancelled_at!==null}async hasFailures(){return((await this.fresh())?.failed_jobs||0)>0}async failedJobIds(){let L=await this.fresh();if(!L)return[];try{return JSON.parse(L.failed_job_ids||"[]")}catch{return[]}}async cancel(){if(g()==="redis")await H2(this.id);else await Z2(this.id);E.info(`[Batch] Cancelled batch ${this.id}`);let $=F1(this.id);if($)for(let V of $.finallyCallbacks)try{await V(this)}catch(Y){E.error(`[Batch] Error in finally callback for batch ${this.id}:`,Y)}}async add(L){let $=await this.fresh();if(!$)throw Error(`Batch ${this.id} not found`);if($.cancelled_at)throw Error(`Batch ${this.id} has been cancelled`);if($.finished_at)throw Error(`Batch ${this.id} has already finished`);let V=L.map((X)=>("job"in X)?X:{job:X}),Y=$.total_jobs+V.length,G=$.pending_jobs+V.length;await c4(this.id,{total_jobs:Y,pending_jobs:G});let U=JSON.parse($.options||"{}");for(let X=0;X<V.length;X++){let _=V[X];if(!_)continue;let{job:K,payload:z}=_,O={...z,_batchId:this.id,_batchIndex:$.total_jobs+X};if(U.queue&&!K.queue)K.queue=U.queue;await K.dispatch(O)}E.info(`[Batch] Added ${V.length} jobs to batch ${this.id}`)}async delete(){await _2(this.id),k1(this.id)}}class t1{static create(L){return new I1(L)}static async find(L){if(!await c(L))return null;return new p(L)}static async all(){return(await z2()).map(($)=>new p($.id))}static async prune(L=24){return W2(L)}}function X2(L,$){r1.set(L,$)}function F1(L){return r1.get(L)}function k1(L){r1.delete(L)}async function K2(L){if(g()==="redis")await F2(L);else await i4(L)}async function c(L){if(g()==="redis")return x2(L);return d4(L)}async function z2(){if(g()==="redis")return a4();return n4()}async function c4(L,$){if(g()==="redis")await o4(L,$);else await a1(L,$)}async function _2(L){if(g()==="redis")await e4(L);else await s4(L)}async function W2(L){if(g()==="redis")return Q2(L);return t4(L)}async function i4(L){let{db:$}=await import("@stacksjs/database");await $.insertInto("job_batches").values({id:L.id,name:L.name,total_jobs:L.total_jobs,pending_jobs:L.pending_jobs,failed_jobs:L.failed_jobs,failed_job_ids:L.failed_job_ids,options:L.options,cancelled_at:L.cancelled_at,created_at:L.created_at,finished_at:L.finished_at}).execute()}async function d4(L){let{db:$}=await import("@stacksjs/database");return await $.selectFrom("job_batches").where("id","=",L).selectAll().executeTakeFirst()||null}async function n4(){let{db:L}=await import("@stacksjs/database");return await L.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function a1(L,$){let{db:V}=await import("@stacksjs/database");await V.updateTable("job_batches").set($).where("id","=",L).execute()}async function s4(L){let{db:$}=await import("@stacksjs/database");await $.deleteFrom("job_batches").where("id","=",L).execute()}async function Z2(L){await a1(L,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function t4(L){let{db:$}=await import("@stacksjs/database"),V=new Date(Date.now()-L*60*60*1000).toISOString().slice(0,19).replace("T"," "),Y=await $.deleteFrom("job_batches").where("finished_at","is not",null).where("finished_at","<",V).executeTakeFirst();return Number(Y?.numDeletedRows??0)}async function O2(){let{queue:L}=await import("@stacksjs/config"),$=L?.connections?.redis?.redis;if($?.url)return $.url;let V=$?.password?`:${encodeURIComponent($.password)}@`:"",Y=$?.db?`/${$.db}`:"";return`redis://${V}${$?.host||"localhost"}:${$?.port||6379}${Y}`}async function H1(){let L=new G2(await O2());return await L.connect(),L}async function F2(L){try{let $=await H1(),V=`${x1}${L.id}`;await $.hset(V,{id:L.id,name:L.name,total_jobs:String(L.total_jobs),pending_jobs:String(L.pending_jobs),failed_jobs:String(L.failed_jobs),failed_job_ids:L.failed_job_ids,options:L.options,cancelled_at:L.cancelled_at||"",created_at:L.created_at,finished_at:L.finished_at||""}),await $.sadd(o1,L.id),$.close()}catch{await i4(L)}}function r4(L){if(!L?.id)return null;return{id:L.id,name:L.name??"",total_jobs:Number(L.total_jobs),pending_jobs:Number(L.pending_jobs),failed_jobs:Number(L.failed_jobs),failed_job_ids:L.failed_job_ids??"",options:L.options??"",cancelled_at:L.cancelled_at||null,created_at:L.created_at??"",finished_at:L.finished_at||null}}async function x2(L){try{let $=await H1(),V=`${x1}${L}`,Y=await $.hgetall(V);return $.close(),r4(Y)}catch{return d4(L)}}async function a4(){try{let L=await H1(),$=await L.smembers(o1),V=[];for(let Y of $){let G=r4(await L.hgetall(`${x1}${Y}`));if(G)V.push(G)}return L.close(),V}catch{return n4()}}async function o4(L,$){try{let V=await H1(),Y=`${x1}${L}`,G={};for(let[U,X]of Object.entries($))G[U]=X===null?"":String(X);await V.hset(Y,G),V.close()}catch{await a1(L,$)}}async function e4(L){try{let $=await H1();await $.del(`${x1}${L}`),await $.srem(o1,L),$.close()}catch{await s4(L)}}async function H2(L){let $=new Date().toISOString().slice(0,19).replace("T"," ");await o4(L,{cancelled_at:$,finished_at:$})}async function Q2(L){try{let $=await a4(),V=Date.now()-L*60*60*1000,Y=0;for(let G of $)if(G.finished_at){if(new Date(G.finished_at).getTime()<V)await e4(G.id),Y++}return Y}catch{return t4(L)}}function L1(L){if(!L)return null;try{let $=JSON.parse(L);if($&&($.kind==="job"||$.kind==="module"))return $;return E.warn(`[Batch] handler JSON has unknown kind '${$?.kind}' \u2014 skipping`),null}catch($){return E.warn(`[Batch] failed to parse persistent handler: ${$.message}`),null}}async function $1(L,$){try{if(L.kind==="job"){let{Jobs:G}=await Promise.resolve().then(() => (Q1(),y1));await G.dispatch(L.name,{...L.payload??{},_batchId:$});return}let V=await import(L.module).catch((G)=>{return E.warn(`[Batch] persistent handler module not found: ${L.module} (${G.message})`),null});if(!V)return;let Y=V[L.export];if(typeof Y!=="function"){E.warn(`[Batch] persistent handler export '${L.export}' is not a function on ${L.module}`);return}await Y(L.payload,$)}catch(V){E.error(`[Batch] persistent handler threw for batch ${$}:`,V)}}async function e1(L){let{db:$,sql:V}=await import("@stacksjs/database");await $.updateTable("job_batches").set({pending_jobs:V`GREATEST(pending_jobs - 1, 0)`}).where("id","=",L).where("pending_jobs",">",0).execute();let Y=new Date().toISOString().slice(0,19).replace("T"," "),G=await $.updateTable("job_batches").set({finished_at:Y}).where("id","=",L).where("pending_jobs","=",0).where("finished_at","is",null).executeTakeFirst(),U=o(G)>0,X=F1(L),_=new p(L);if(X)for(let K of X.progressCallbacks)try{await K(_)}catch(z){E.error(`[Batch] Error in progress callback for batch ${L}:`,z)}if(U){try{let{emitQueueEvent:Z}=await Promise.resolve().then(() => (v(),h));await Z("batch:completed",{jobId:L})}catch{}let K=await c(L);if(!K){k1(L),E.info(`[Batch] Batch ${L} finished (record vanished)`);return}let z=JSON.parse(K.options||"{}"),Q=!((K.failed_jobs||0)>0)||z.allowFailures;if(X){if(Q)for(let Z of X.thenCallbacks)try{await Z(_)}catch(W){E.error(`[Batch] Error in then callback for batch ${L}:`,W)}for(let Z of X.finallyCallbacks)try{await Z(_)}catch(W){E.error(`[Batch] Error in finally callback for batch ${L}:`,W)}k1(L)}if(Q){let Z=L1(K.then_handler);if(Z)await $1(Z,L)}else{let Z=L1(K.catch_handler);if(Z)await $1(Z,L)}let P=L1(K.finally_handler);if(P)await $1(P,L);E.info(`[Batch] Batch ${L} finished`)}}async function L4(L,$,V){let{db:Y,sql:G}=await import("@stacksjs/database"),U=await c(L);if(!U)return;let X=JSON.parse(U.options||"{}");await Y.updateTable("job_batches").set({pending_jobs:G`GREATEST(pending_jobs - 1, 0)`,failed_jobs:G`failed_jobs + 1`}).where("id","=",L).where("pending_jobs",">",0).execute();try{let W=await c(L);if(W){let x=[];try{x=JSON.parse(W.failed_job_ids||"[]")}catch{x=[]}x.push($),await c4(L,{failed_job_ids:JSON.stringify(x)})}}catch{}let _=new p(L),K=F1(L);if(K)for(let W of K.catchCallbacks)try{await W(_,V)}catch(x){E.error(`[Batch] Error in catch callback for batch ${L}:`,x)}try{let{emitQueueEvent:W}=await Promise.resolve().then(() => (v(),h));await W("batch:failed",{jobId:L,error:V})}catch{}let z=new Date().toISOString().slice(0,19).replace("T"," "),O=Y.updateTable("job_batches").set(X.allowFailures?{finished_at:z}:{finished_at:z,cancelled_at:z}).where("id","=",L).where("finished_at","is",null);if(X.allowFailures)O=O.where("pending_jobs","=",0);let Q=await O.executeTakeFirst();if(o(Q)===0)return;let P=!!X.allowFailures;if(K){if(P)for(let W of K.thenCallbacks)try{await W(_)}catch(x){E.error(`[Batch] Error in then callback for batch ${L}:`,x)}for(let W of K.finallyCallbacks)try{await W(_)}catch(x){E.error(`[Batch] Error in finally callback for batch ${L}:`,x)}k1(L)}let Z=await c(L);if(Z){if(P){let x=L1(Z.then_handler);if(x)await $1(x,L)}else{let x=L1(Z.catch_handler);if(x)await $1(x,L)}let W=L1(Z.finally_handler);if(W)await $1(W,L)}E.info(`[Batch] Batch ${L} finished with failure(s)`)}async function LL(L){return(await c(L))?.cancelled_at!==null}var r1,x1="stacks:batch:",o1="stacks:batches";var f=y(()=>{r1=new Map});var y1={};m(y1,{runJob:()=>w1,jobBatch:()=>GL,job:()=>VL,Jobs:()=>YL});import{appPath as A2}from"@stacksjs/path";import{env as D2}from"@stacksjs/env";import{enqueueAfterCommit as P2,isInTransaction as M2}from"@stacksjs/database";function B2(){return D2.QUEUE_DRIVER||"sync"}function E2(){if($L)return;$L=!0,console.warn("[queue] .afterCommit() was called outside of `db.transaction(...)`. Dispatching immediately. Wrap the call in a transaction or drop .afterCommit() to silence this message.")}class k{name;payload;options={};txMode="auto";constructor(L,$){this.name=L;this.payload=$}onQueue(L){return this.options.queue=L,this}delay(L){return this.options.delay=L,this}tries(L){return this.options.tries=L,this}timeout(L){return this.options.timeout=L,this}backoff(L){return this.options.backoff=L,this}withContext(L){return this.options.context=L,this}withIdempotencyKey(L){return this.options.idempotencyKey=L,this}afterCommit(){return this.txMode="after",this}withoutCommit(){return this.txMode="immediate",this}async dispatch(){let{isFaked:L,getFakeQueue:$}=await Promise.resolve().then(() => g1);if(L()){$()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(M2()){let Y=this.runDispatchPipeline.bind(this);if(P2(async()=>{await Y()}))return}else if(this.txMode==="after")E2()}await this.runDispatchPipeline()}async runDispatchPipeline(){let L=!1;if(this.options.idempotencyKey){let $=await l1(this.options.idempotencyKey,this.name,this.options.queue);if($==="duplicate")return;L=$==="claimed"}try{if(await J1(this.name,this.payload)){let V=j(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await N1({queue:this.options.queue||"default",payload:JSON.stringify(V),exception:`quarantined: ${this.name}`},"poison-detected"))return}let $=B2();if($==="database")await this.dispatchToDatabase();else if($==="redis")await this.dispatchToRedis();else if($==="sync")await w1(this.name,{payload:this.payload,context:this.options.context});else if($==="sqs"||$==="memory"||$==="beanstalkd")throw Error(`[queue] Driver "${$}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);else throw Error(`[queue] Unknown QUEUE_DRIVER "${$}". Allowed values: redis, database, sync.`)}catch($){if(L&&this.options.idempotencyKey)await c1(this.options.idempotencyKey);throw $}}async dispatchIf(L){if(L)await this.dispatch()}async dispatchUnless(L){if(!L)await this.dispatch()}async dispatchToDatabase(){let L=Math.floor(Date.now()/1000),$=this.options.delay?L+this.options.delay:L,V=j(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff}),{db:Y}=await import("@stacksjs/database");await Y.insertInto("jobs").values({queue:this.options.queue||"default",payload:JSON.stringify(V),attempts:0,reserved_at:null,available_at:$,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){let{RedisQueue:L}=await Promise.resolve().then(() => (_1(),z1)),{queue:$}=await import("@stacksjs/config"),V=$?.connections?.redis;if(!V)throw Error("Redis queue connection is not configured. Check config/queue.ts");let Y=new L(this.options.queue||"default",V),G=j(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});await Y.add(G,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await w1(this.name,{payload:this.payload,context:this.options.context})}}function VL(L,$){return new k(L,$)}function GL(L){let{PendingBatch:$}=(f(),jL(u));return new $(L)}async function w1(L,$={}){let{withTraceId:V}=await import("@stacksjs/router"),Y=$.traceId??`job:${L}:${Math.random().toString(36).slice(2,10)}`;await V(Y,async()=>{let X=(await import(A2(`Jobs/${L}.ts`))).default;if(!X)throw Error(`Job ${L} does not export a default`);if(typeof X.handle==="function")await X.handle($.payload);else if(typeof X.action==="string"){let{runAction:_}=await import("@stacksjs/actions");await _(X.action)}else if(typeof X.action==="function")await X.action();else if(typeof X==="function")await X($.payload,$.context);else throw Error(`Job ${L} does not have a valid handler`)})}var $L=!1,YL;var Q1=y(()=>{S1();U1();i1();R1();YL={make(L,$){return new k(L,$)},async dispatch(L,$){await new k(L,$).dispatch()},async dispatchIf(L,$,V){if(L)await new k($,V).dispatch()},async dispatchUnless(L,$,V){if(!L)await new k($,V).dispatch()},async dispatchNow(L,$){await new k(L,$).dispatchNow()},dispatchAfter(L,$,V){return new k($,V).delay(L)},async dispatchOnce(L,$,V){await new k($,V).withIdempotencyKey(L).dispatch()},async dispatchAfterCommit(L,$){await new k(L,$).afterCommit().dispatch()}}});var f1={};m(f1,{resumeQueue:()=>WL,recordCircuitSuccess:()=>KL,recordCircuitFailure:()=>zL,pauseQueue:()=>_L,listCircuitState:()=>ZL,isCircuitOpen:()=>XL});import{db as J}from"@stacksjs/database";function i(){if(UL)return;UL=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing \u2014 circuit breaker disabled. "+"Run migrations to enable.")}async function V4(L,$){try{let V=await J.selectFrom("queue_circuit_state").where("queue_name","=",L).selectAll().executeTakeFirst();if(V)return V;return await J.insertInto("queue_circuit_state").values({queue_name:L,success_count:0,failure_count:0,window_start:$,paused_at:null,resume_at:null}).execute(),{queue_name:L,success_count:0,failure_count:0,window_start:$,paused_at:null,resume_at:null}}catch(V){if(B(V))return i(),null;let Y=V?.message??"";if(Y.includes("UNIQUE constraint")||Y.includes("Duplicate entry"))return await J.selectFrom("queue_circuit_state").where("queue_name","=",L).selectAll().executeTakeFirst();throw V}}async function XL(L){try{let $=await J.selectFrom("queue_circuit_state").where("queue_name","=",L).select(["paused_at","resume_at"]).executeTakeFirst();if(!$||!$.paused_at)return!1;if($.resume_at){let V=Date.parse($.resume_at.replace(" ","T")+"Z");if(Number.isFinite(V)&&Date.now()>=V)return await J.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",L).execute(),!1}return!0}catch($){if(B($))return i(),!1;throw $}}async function KL(L,$={}){let V=$.windowSeconds??300,Y=new Date().toISOString().slice(0,19).replace("T"," "),G=await V4(L,Y);if(!G)return;try{if($4(G.window_start,V)){await J.updateTable("queue_circuit_state").set({success_count:1,failure_count:0,window_start:Y}).where("queue_name","=",L).execute();return}await J.updateTable("queue_circuit_state").set({success_count:G.success_count+1}).where("queue_name","=",L).execute()}catch(U){if(B(U)){i();return}throw U}}async function zL(L,$={}){let V=$.failureRateThreshold??0.5,Y=$.windowSeconds??300,G=$.pauseSeconds??300,U=$.minObservations??10,X=new Date,_=X.toISOString().slice(0,19).replace("T"," "),K=await V4(L,_);if(!K)return!1;if(K.paused_at)return!1;try{let{success_count:z,failure_count:O}=K;if($4(K.window_start,Y))z=0,O=0;O+=1;let Q=z+O,P=Q===0?0:O/Q;if(Q>=U&&P>=V){let W=new Date(X.getTime()+G*1000).toISOString().slice(0,19).replace("T"," ");return await J.updateTable("queue_circuit_state").set({success_count:z,failure_count:O,window_start:_,paused_at:_,resume_at:W}).where("queue_name","=",L).execute(),!0}return await J.updateTable("queue_circuit_state").set({success_count:z,failure_count:O,window_start:$4(K.window_start,Y)?_:K.window_start}).where("queue_name","=",L).execute(),!1}catch(z){if(B(z))return i(),!1;throw z}}function $4(L,$){if(!L)return!0;let V=Date.parse(L.replace(" ","T")+"Z");if(!Number.isFinite(V))return!0;return Date.now()-V>$*1000}async function _L(L,$=300){let V=new Date,Y=V.toISOString().slice(0,19).replace("T"," "),G=new Date(V.getTime()+$*1000).toISOString().slice(0,19).replace("T"," ");await V4(L,Y);try{await J.updateTable("queue_circuit_state").set({paused_at:Y,resume_at:G}).where("queue_name","=",L).execute()}catch(U){if(B(U)){i();return}throw U}}async function WL(L){try{await J.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",L).execute()}catch($){if(B($)){i();return}throw $}}async function ZL(){try{return await J.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch(L){if(B(L))return i(),[];throw L}}var UL=!1;var A1=()=>{};U1();import{env as oL}from"@stacksjs/env";function J4(){return oL.QUEUE_DRIVER||"sync"}class R4{name;description;action;handle;queue;rate;tries;timeout;backoff;backoffConfig;enabled;constructor(L){this.name=L.name,this.description=L.description,this.handle=L.handle,this.queue=L.queue,this.rate=L.rate,this.action=L.action,this.tries=L.tries,this.timeout=L.timeout,this.backoff=L.backoff,this.backoffConfig=L.backoffConfig,this.enabled=L.enabled}async dispatch(L){let{isFaked:$,getFakeQueue:V}=await Promise.resolve().then(() => g1);if($()){V()?.dispatch(this.name||"UnknownJob",L,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let Y=J4();if(Y==="sync")return this.dispatchNow(L);if(Y==="redis")return this.dispatchToRedis(L);if(Y==="database")return this.dispatchToDatabase(L);if(Y==="sqs"||Y==="memory"||Y==="beanstalkd")throw Error(`[queue] Driver "${Y}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);throw Error(`[queue] Unknown QUEUE_DRIVER "${Y}". Allowed values: redis, database, sync.`)}async dispatchIf(L,$){if(L)return this.dispatch($)}async dispatchUnless(L,$){if(!L)return this.dispatch($)}async dispatchAfter(L,$){let V=J4();if(V==="redis")return this.dispatchToRedis($,{delay:L});if(V==="database")return this.dispatchToDatabase($,{delay:L});return await new Promise((Y)=>setTimeout(Y,L*1000)),await this.dispatchNow($)}async dispatchNow(L){if(typeof this.handle==="function")await this.handle(L);else if(typeof this.action==="string"){let{runAction:$}=await import("@stacksjs/actions");await $(this.action)}else if(typeof this.action==="function")await this.action();else throw Error(`Job ${this.name} does not have a valid handler`)}async dispatchToDatabase(L,$){let V=Math.floor(Date.now()/1000),Y=$?.delay?V+$.delay:V,G=j(this.name??this.constructor.name,L,{queue:this.queue??"default",tries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0}),{db:U}=await import("@stacksjs/database");await U.insertInto("jobs").values({queue:this.queue||"default",payload:JSON.stringify(G),attempts:0,reserved_at:null,available_at:Y,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(L,$){let{RedisQueue:V}=await Promise.resolve().then(() => (_1(),z1)),{queue:Y}=await import("@stacksjs/config"),G=Y?.connections?.redis;if(!G)throw Error("Redis queue connection is not configured. Check config/queue.ts");let U=new V(this.queue||"default",G),X=j(this.name??this.constructor.name,L,{queue:this.queue??"default",tries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0});await U.add(X,{delay:$?.delay,maxTries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0})}}Q1();i1();S1();R1();A1();U1();var Y4=(L)=>`__job_progress__:${L}`,G4=(L)=>`__job_cancel__:${L}`;function T2(L){return Number.isFinite(L)?Math.max(0,Math.min(100,L)):0}async function N2(L,$,V){let{cache:Y}=await import("@stacksjs/cache");await Y.set(Y4(L),{percent:T2($),message:V,updatedAt:Date.now()},3600)}async function S2(L){let{cache:$}=await import("@stacksjs/cache");return await $.get(Y4(L))??null}async function J2(L){let{cache:$}=await import("@stacksjs/cache");await $.set(G4(L),1,3600)}async function R2(L){let{cache:$}=await import("@stacksjs/cache");return Boolean(await $.get(G4(L)))}async function C2(L){let{cache:$}=await import("@stacksjs/cache");await Promise.all([$.del(Y4(L)),$.del(G4(L))])}import{log as V1}from"@stacksjs/logging";import*as OL from"@stacksjs/path";class FL{jobs=new Map;initialized=!1;register(L){this.jobs.set(L.name,L),V1.debug(`Registered job: ${L.name} (${L.type})`)}get(L){return this.jobs.get(L)}all(){return Array.from(this.jobs.values())}byQueue(L){return this.all().filter(($)=>$.config.queue===L)}scheduled(){return this.all().filter((L)=>L.config.rate||L.config.schedule)}has(L){return this.jobs.has(L)}clear(){this.jobs.clear(),this.initialized=!1}setInitialized(L){this.initialized=L}isInitialized(){return this.initialized}}var d=new FL;async function U4(L){let $=L||OL.userJobsPath(),V=[];try{let Y=new Bun.Glob("**/*.{ts,js}"),G={cwd:$,onlyFiles:!0,absolute:!0};for await(let U of Y.scan(G)){if(U.includes(".test.")||U.includes(".spec.")||U.endsWith("index.ts")||U.endsWith("index.js"))continue;try{let X=await q2(U);if(X)V.push(X),d.register(X)}catch(X){V1.warn(`Failed to load job from ${U}: ${X.message}`)}}return d.setInitialized(!0),V1.info(`Discovered ${V.length} jobs from ${$}`),V}catch(Y){return V1.error(`Failed to discover jobs: ${Y.message}`),[]}}async function q2(L){try{let $=await import(L),V=L.split("/").pop()?.replace(/\.(ts|js)$/,"")||"UnknownJob";if($.default&&typeof $.default==="function"){let Y=$.default;if(typeof Y.handle==="function"||typeof Y.prototype?.handle==="function"){let G=Y.config||{};return{name:G.name||V,path:L,config:{name:G.name||V,description:G.description,queue:G.queue||"default",tries:G.retries||3,timeout:G.timeout,withoutOverlapping:G.withoutOverlapping,schedule:G.schedule,retryAfter:G.retryAfter},type:"class",module:Y}}}if($.default&&typeof $.default==="object"){let Y=$.default;if(typeof Y.handle==="function"||typeof Y.action==="string")return{name:Y.name||V,path:L,config:{name:Y.name||V,description:Y.description,queue:Y.queue||"default",tries:Y.tries||3,backoff:Y.backoff,rate:Y.rate,timeout:Y.timeout||Y.timeOut,backoffConfig:Y.backoffConfig},type:"function",module:Y}}return null}catch($){return V1.debug(`Could not load job from ${L}: ${$.message}`),null}}function v2(L){return d.get(L)}function k2(){return d.all()}function X4(){return d.scheduled()}async function I2(L,$){let V=d.get(L);if(!V)throw Error(`Job "${L}" not found. Did you run discoverJobs()?`);try{if(V.type==="class"){if(typeof V.module.handle==="function")return await V.module.handle($);return await new V.module().handle($)}else{if(typeof V.module.handle==="function")return await V.module.handle($);throw Error(`Job "${L}" does not have a handle method`)}}catch(Y){throw V1.error(`Failed to execute job "${L}": ${Y.message}`),Y}}function y2(L){let $=L.config;return{name:$.name,queue:$.queue,tries:$.tries,backoff:$.backoff,timeout:$.timeout,backoffConfig:$.backoffConfig,rate:$.rate}}import{log as T}from"@stacksjs/logging";v();import{log as w2}from"@stacksjs/logging";var xL=!1;async function HL(){if(xL)return!0;try{let{db:L}=await import("@stacksjs/database");return await L.unsafe("CREATE TABLE IF NOT EXISTS scheduled_job_runs (job_name VARCHAR(255) PRIMARY KEY, last_run_at VARCHAR(64) NOT NULL)").execute(),xL=!0,!0}catch(L){return w2.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${L instanceof Error?L.message:String(L)}`),!1}}async function QL(L){if(!await HL())return null;try{let{db:$}=await import("@stacksjs/database"),V=await $.selectFrom("scheduled_job_runs").where("job_name","=",L).select(["last_run_at"]).executeTakeFirst();if(!V?.last_run_at)return null;let Y=new Date(V.last_run_at);return Number.isNaN(Y.getTime())?null:Y}catch{return null}}async function AL(L,$){if(!await HL())return;try{let{db:V}=await import("@stacksjs/database"),Y=$.toISOString();await V.deleteFrom("scheduled_job_runs").where("job_name","=",L).execute(),await V.insertInto("scheduled_job_runs").values({job_name:L,last_run_at:Y}).execute()}catch{}}var EL={checkInterval:60000,preventOverlapping:!0},D={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...EL}},DL=new Set;function f2(L,$){if(DL.has(L))return;DL.add(L),T.warn(`[scheduler] Cron expression "${L}" specifies seconds="${$}" but the scheduler `+"ticks at minute granularity \u2014 the seconds field is being ignored. Use a 5-field expression "+"to avoid this warning, or wait for sub-minute scheduling support.")}var PL=!1,ML=new Map;function m2(L){let $=ML.get(L);if(!$)$=new Intl.DateTimeFormat("en-US",{timeZone:L,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),ML.set(L,$);return $}function K4(L,$){if($&&$!=="local"&&$!=="system")try{let V=m2($).formatToParts(L),Y=(X)=>V.find((_)=>_.type===X)?.value??"",G={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},U=Number(Y("hour"));if(U===24)U=0;return{minute:Number(Y("minute")),hour:U,day:Number(Y("day")),month:Number(Y("month")),dayOfWeek:G[Y("weekday")]??L.getDay()}}catch{if(!PL)PL=!0,T.warn(`[scheduler] Invalid timezone "${$}"; falling back to system local time.`)}return{minute:L.getMinutes(),hour:L.getHours(),day:L.getDate(),month:L.getMonth()+1,dayOfWeek:L.getDay()}}function j2(L,$,V){let Y=K4(new Date,V),G=Y.minute,U=Y.hour,X=Y.day,_=Y.month,K=Y.dayOfWeek;if($){let x=K4($,V);if(x.minute===G&&x.hour===U&&x.day===X)return!1}let z=L.trim().split(/\s+/);if(z.length===6){let x=z[0];if(x&&x!=="0"&&x!=="*")f2(L,x);z=z.slice(1)}if(z.length<5)return T.warn(`Invalid cron expression: ${L}`),!1;let[O,Q,P,Z,W]=z;return I(O,G,0,59)&&I(Q,U,0,23)&&I(P,X,1,31)&&I(Z,_,1,12)&&I(W,K,0,6)}function I(L,$,V,Y){if(L==="*")return!0;if(L.includes(","))return L.split(",").map((X)=>Number.parseInt(X.trim(),10)).includes($);if(L.includes("-")){let[U,X]=L.split("-").map((_)=>Number.parseInt(_.trim(),10));return $>=U&&$<=X}if(L.includes("/")){let[U,X]=L.split("/"),_=Number.parseInt(X,10);if(U==="*")return $%_===0;if(U.includes("-")){let[K,z]=U.split("-").map((O)=>Number.parseInt(O.trim(),10));return $>=K&&$<=z&&($-K)%_===0}}let G=Number.parseInt(L,10);return!Number.isNaN(G)&&$===G}function TL(L){let V={"@yearly":"0 0 1 1 *","@annually":"0 0 1 1 *","@monthly":"0 0 1 * *","@weekly":"0 0 * * 0","@daily":"0 0 * * *","@midnight":"0 0 * * *","@hourly":"0 * * * *"}[L.toLowerCase()];if(V)return V;let Y=L.match(/^Every\.(\w+)$/i);if(Y&&Y[1]!==void 0){let U=Y[1].toLowerCase();return{second:"* * * * *",fiveseconds:"* * * * *",tenseconds:"* * * * *",thirtyseconds:"* * * * *",minute:"* * * * *",fiveminutes:"*/5 * * * *",tenminutes:"*/10 * * * *",fifteenminutes:"*/15 * * * *",thirtyminutes:"*/30 * * * *",hour:"0 * * * *",twohours:"0 */2 * * *",sixhours:"0 */6 * * *",twelvehours:"0 */12 * * *",day:"0 0 * * *",week:"0 0 * * 0",month:"0 0 1 * *"}[U]||null}let G=L.split(/\s+/).length;if(G>=5&&G<=6)return L;return null}function NL(L,$){let V=L.trim().split(/\s+/),Y=V.length===6?V.slice(1):V;if(Y.length<5)return null;let[G,U,X,_,K]=Y,z=new Date;z.setSeconds(0,0);let O=527040;for(let Q=1;Q<=O;Q++){let P=new Date(z.getTime()+Q*60000),Z=K4(P,$);if(I(G,Z.minute,0,59)&&I(U,Z.hour,0,23)&&I(X,Z.day,1,31)&&I(_,Z.month,1,12)&&I(K,Z.dayOfWeek,0,6))return P}return null}async function h2(L={}){if(D.isRunning){T.warn("Scheduler is already running");return}D.config={...EL,...L},D.isRunning=!0,D.isShuttingDown=!1,await U4();let $=X4();for(let G of $){let U=G.config.rate||G.config.schedule;if(U){let X=TL(U);if(X){let _=await QL(G.name);D.jobs.set(G.name,{job:G,lastRun:_,nextRun:NL(X,D.config.timezone),isRunning:!1}),T.info(`Registered scheduled job: ${G.name} (${X})`)}else T.warn(`Invalid schedule for job ${G.name}: ${U}`)}}if(D.jobs.size===0){T.info("No scheduled jobs found");return}T.info(`Scheduler started with ${D.jobs.size} job(s)`),process.on("SIGINT",()=>z4()),process.on("SIGTERM",()=>z4());let V=!1,Y=()=>{if(D.isShuttingDown)return;let G=D.config.checkInterval,U=G-Date.now()%G;D.checkInterval=setTimeout(()=>{if(!D.isShuttingDown&&!V)V=!0,BL().catch((X)=>T.error("Scheduler check failed:",X)).finally(()=>{V=!1});Y()},U),D.checkInterval?.unref?.()};Y(),await BL()}async function BL(){for(let[L,$]of D.jobs){let V=$.job.config.rate||$.job.config.schedule;if(!V)continue;let Y=TL(V);if(!Y)continue;if(j2(Y,$.lastRun,D.config.timezone)){if(D.config.preventOverlapping&&$.isRunning){T.debug(`Skipping ${L}: previous execution still running`);continue}if($.job.config.withoutOverlapping&&$.isRunning){T.debug(`Skipping ${L}: withoutOverlapping is enabled`);continue}try{$.isRunning=!0,$.lastRun=new Date,$.nextRun=NL(Y,D.config.timezone),await AL(L,$.lastRun),T.info(`Dispatching scheduled job: ${L}`),await l("job:added",{jobId:`scheduled-${L}-${Date.now()}`,queueName:$.job.config.queue||"default",jobName:L}),await d1(L,{queue:$.job.config.queue||"default",payload:{},maxTries:$.job.config.tries||3,timeout:$.job.config.timeout||60}),$.isRunning=!1,T.info(`Scheduled job ${L} dispatched to queue`)}catch(G){$.isRunning=!1,T.error(`Failed to dispatch scheduled job ${L}:`,G)}}}}async function z4(){if(!D.isRunning)return;if(T.info("Stopping scheduler..."),D.isShuttingDown=!0,D.checkInterval)clearTimeout(D.checkInterval),D.checkInterval=null;D.isRunning=!1,D.jobs.clear(),T.info("Scheduler stopped")}function p2(){return{isRunning:D.isRunning,jobCount:D.jobs.size,jobs:Array.from(D.jobs.entries()).map(([L,$])=>({name:L,schedule:$.job.config.rate||$.job.config.schedule,lastRun:$.lastRun,nextRun:$.nextRun,isRunning:$.isRunning}))}}function g2(){return D.isRunning}function u2(){return new Map(D.jobs)}async function b2(L){let $=D.jobs.get(L);if(!$)throw Error(`Scheduled job "${L}" not found`);T.info(`Manually triggering scheduled job: ${L}`),await d1(L,{queue:$.job.config.queue||"default",payload:{},maxTries:$.job.config.tries||3,timeout:$.job.config.timeout||60})}v();v();import{log as l2}from"@stacksjs/logging";var c2={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function _4(L={}){let $={...c2,...L},V=[],Y=new Date,G=Math.floor(Y.getTime()/1000);try{let{db:U}=await import("@stacksjs/database"),X=await U.selectFrom("jobs").selectAll().execute(),_=await U.selectFrom("failed_jobs").selectAll().execute(),K=new Map;for(let H of X){let M=H.queue||"default";if(!K.has(M))K.set(M,{pending:0,processing:0,delayed:0});let S=K.get(M);if(H.reserved_at)S.processing++;else if(H.available_at&&H.available_at>G)S.delayed++;else if(S.pending++,H.created_at){let q=typeof H.created_at==="number"?H.created_at:Math.floor(new Date(H.created_at).getTime()/1000),H4=G-q;if(!S.oldestAge||H4>S.oldestAge)S.oldestAge=H4}}let z=new Map;for(let H of _){let M=H.queue||"default";z.set(M,(z.get(M)||0)+1)}let O=[],Q=new Set([...K.keys(),...z.keys()]),P=$.queues?[...Q].filter((H)=>$.queues.includes(H)):[...Q],Z=0,W=0,x=0,s=0;for(let H of P){let M=K.get(H)||{pending:0,processing:0,delayed:0},S=z.get(H)||0;Z+=M.pending,W+=M.processing,x+=M.delayed,s+=S;let q="healthy";if(M.pending>=$.maxPendingCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${H}" has ${M.pending} pending jobs (threshold: ${$.maxPendingCritical})`,queue:H,timestamp:Y.toISOString()});else if(M.pending>=$.maxPendingWarning)q="degraded",V.push({level:"warning",message:`Queue "${H}" has ${M.pending} pending jobs (threshold: ${$.maxPendingWarning})`,queue:H,timestamp:Y.toISOString()});if(S>=$.maxFailedCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${H}" has ${S} failed jobs (threshold: ${$.maxFailedCritical})`,queue:H,timestamp:Y.toISOString()});else if(S>=$.maxFailedWarning){if(q==="healthy")q="degraded";V.push({level:"warning",message:`Queue "${H}" has ${S} failed jobs (threshold: ${$.maxFailedWarning})`,queue:H,timestamp:Y.toISOString()})}if(M.oldestAge){if(M.oldestAge>=$.maxJobAgeCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${H}" has a job waiting for ${Math.floor(M.oldestAge/3600)} hours`,queue:H,timestamp:Y.toISOString()});else if(M.oldestAge>=$.maxJobAgeWarning){if(q==="healthy")q="degraded";V.push({level:"warning",message:`Queue "${H}" has a job waiting for ${Math.floor(M.oldestAge/60)} minutes`,queue:H,timestamp:Y.toISOString()})}}O.push({name:H,status:q,pending:M.pending,processing:M.processing,delayed:M.delayed,failed:S,oldestJobAge:M.oldestAge})}let F4=v1().getMetrics(),vL=F4.throughputPerMinute,kL=F4.averageDuration,x4=Z+W+x+s,G1=x4>0?s/x4:0,t="healthy";if(O.some((H)=>H.status==="unhealthy"))t="unhealthy";else if(O.some((H)=>H.status==="degraded"))t="degraded";if(G1>=$.maxErrorRateCritical)t="unhealthy",V.push({level:"critical",message:`Overall error rate is ${(G1*100).toFixed(1)}% (threshold: ${$.maxErrorRateCritical*100}%)`,timestamp:Y.toISOString()});else if(G1>=$.maxErrorRateWarning){if(t==="healthy")t="degraded";V.push({level:"warning",message:`Overall error rate is ${(G1*100).toFixed(1)}% (threshold: ${$.maxErrorRateWarning*100}%)`,timestamp:Y.toISOString()})}let IL=O1().getAll().map((H)=>({id:H.id,status:H.status,queue:H.queue,processedCount:H.processedCount,failedCount:H.failedCount,lastActivityAt:H.lastActivityAt}));return{status:t,timestamp:Y.toISOString(),queues:O,workers:IL,metrics:{totalPending:Z,totalProcessing:W,totalDelayed:x,totalFailed:s,throughputPerMinute:vL,averageProcessingTime:kL,errorRate:G1},alerts:V}}catch(U){l2.error("Failed to perform queue health check:",U);let _=O1().getAll().map((K)=>({id:K.id,status:K.status,queue:K.queue,processedCount:K.processedCount,failedCount:K.failedCount,lastActivityAt:K.lastActivityAt}));return{status:"unhealthy",timestamp:Y.toISOString(),queues:[],workers:_,metrics:{totalPending:0,totalProcessing:0,totalDelayed:0,totalFailed:0,throughputPerMinute:0,averageProcessingTime:0,errorRate:0},alerts:[{level:"critical",message:`Health check failed: ${U.message}`,timestamp:Y.toISOString()}]}}}function i2(L={}){return async($)=>{let V=await _4(L),Y=V.status==="healthy"?200:V.status==="degraded"?207:503;return new Response(JSON.stringify(V,null,2),{status:Y,headers:{"Content-Type":"application/json","Cache-Control":"no-store"}})}}async function d2(L={}){return(await _4(L)).status==="healthy"}import{log as C}from"@stacksjs/logging";var n2={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function D1(L){return String(L).replace(/[&<>"']/g,($)=>n2[$]??$)}class W4{config;notificationCount=0;lastResetTime=Date.now();pendingBatch=[];batchTimeout=null;activeFlush=null;constructor(L){this.config=L}async notify(L){if(this.config.filter&&!this.config.filter(L))return;if(this.config.rateLimit){let $=Date.now();if($-this.lastResetTime>3600000)this.notificationCount=0,this.lastResetTime=$;if(this.notificationCount>=this.config.rateLimit){C.debug("Rate limit reached for failed job notifications");return}}if(this.config.batch){if(this.pendingBatch.push(L),!this.batchTimeout)this.batchTimeout=setTimeout(()=>{this.batchTimeout=null,this.activeFlush=this.flushBatch().catch(($)=>C.error("Failed to flush notification batch:",$)).finally(()=>{this.activeFlush=null})},this.config.batchInterval||60000),this.batchTimeout.unref?.();return}await this.sendNotifications([L])}async flushBatch(){if(this.pendingBatch.length===0)return;let L=[...this.pendingBatch];if(this.pendingBatch=[],this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;await this.sendNotifications(L)}async shutdown(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.activeFlush)await this.activeFlush.catch(()=>{});await this.flushBatch().catch((L)=>C.error("Failed to flush notification batch on shutdown:",L))}async sendNotifications(L){let $=[];for(let V of this.config.channels)switch(V){case"email":if(this.config.email)$.push(this.sendEmail(L));break;case"slack":if(this.config.slack)$.push(this.sendSlack(L));break;case"discord":if(this.config.discord)$.push(this.sendDiscord(L));break;case"webhook":if(this.config.webhook)$.push(this.sendWebhook(L));break;case"log":this.logFailures(L);break}try{await Promise.all($)}catch(V){C.error("Failed to send job failure notifications:",V)}finally{this.notificationCount+=L.length}}async sendEmail(L){let $=this.config.email,V=$.subject||`[Queue] ${L.length} Job(s) Failed`,Y=this.formatEmailBody(L);try{let{mail:G}=await import("@stacksjs/email");await G.send({to:Array.isArray($.to)?$.to:[$.to],from:$.from,subject:V,html:Y})}catch(G){C.error("Failed to send email notification:",G)}}formatEmailBody(L){let $=L.map((V)=>`
3
3
  <tr>
4
4
  <td style="padding: 8px; border: 1px solid #ddd;">${D1(String(V.id))}</td>
5
5
  <td style="padding: 8px; border: 1px solid #ddd;">${D1(V.name)}</td>
@@ -28,4 +28,4 @@ var{defineProperty:m1,getOwnPropertyNames:IL,getOwnPropertyDescriptor:yL}=Object
28
28
  </table>
29
29
  `}async sendSlack(L){let $=this.config.slack,V=[{type:"header",text:{type:"plain_text",text:`\uD83D\uDEA8 ${L.length} Job(s) Failed`,emoji:!0}},...L.slice(0,10).map((Y)=>({type:"section",text:{type:"mrkdwn",text:`*${Y.name}*
30
30
  Queue: ${Y.queue} | Attempts: ${Y.attempts}/${Y.maxAttempts}
31
- \`\`\`${Y.exception.slice(0,200)}\`\`\``}}))];if(L.length>10)V.push({type:"section",text:{type:"mrkdwn",text:`_...and ${L.length-10} more failed jobs_`}});try{let Y=await fetch($.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:$.channel,username:$.username||"Queue Monitor",icon_emoji:$.iconEmoji||":warning:",blocks:V})});if(!Y.ok)C.error(`Slack notification failed with status ${Y.status}`)}catch(Y){C.error("Failed to send Slack notification:",Y)}}async sendDiscord(L){let $=this.config.discord,V=L.slice(0,10).map((Y)=>({title:`\u274C ${Y.name}`,color:15158332,fields:[{name:"Queue",value:Y.queue,inline:!0},{name:"Attempts",value:`${Y.attempts}/${Y.maxAttempts}`,inline:!0},{name:"Failed At",value:Y.failedAt.toISOString(),inline:!0},{name:"Exception",value:`\`\`\`${Y.exception.slice(0,500)}\`\`\``}],timestamp:Y.failedAt.toISOString()}));try{let Y=await fetch($.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:$.username||"Queue Monitor",avatar_url:$.avatarUrl,content:`\uD83D\uDEA8 **${L.length} Job(s) Failed**`,embeds:V})});if(!Y.ok)C.error(`Discord notification failed with status ${Y.status}`)}catch(Y){C.error("Failed to send Discord notification:",Y)}}async sendWebhook(L){let $=this.config.webhook,V={event:"jobs.failed",timestamp:new Date().toISOString(),count:L.length,jobs:L.map((G)=>({id:G.id,name:G.name,queue:G.queue,attempts:G.attempts,maxAttempts:G.maxAttempts,exception:G.exception,failedAt:G.failedAt.toISOString()}))},Y={"Content-Type":"application/json",...$.headers};if($.secret){let G=JSON.stringify(V),U=await this.generateSignature(G,$.secret);Y["X-Signature"]=U}try{let G=await fetch($.url,{method:"POST",headers:Y,body:JSON.stringify(V)});if(!G.ok)C.error(`Webhook notification failed with status ${G.status}`)}catch(G){C.error("Failed to send webhook notification:",G)}}async generateSignature(L,$){let V=new TextEncoder,Y=await crypto.subtle.importKey("raw",V.encode($),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),G=await crypto.subtle.sign("HMAC",Y,V.encode(L));return Array.from(new Uint8Array(G)).map((U)=>U.toString(16).padStart(2,"0")).join("")}logFailures(L){for(let $ of L)C.error(`[Queue] Job "${$.name}" failed on queue "${$.queue}" after ${$.attempts} attempts: ${$.exception}`)}async cleanup(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.pendingBatch.length>0)await this.flushBatch()}}var P1=null;function n2(L){return P1=new _4(L),P1}function s2(){return P1}async function t2(L){if(P1)await P1.notify(L)}w();X1();import{err as r2,ok as NL}from"@stacksjs/error-handling";import{log as A}from"@stacksjs/logging";import M1 from"process";import{env as a2}from"@stacksjs/env";M1.on("unhandledRejection",(L,$)=>{A.error(`Unhandled Rejection: ${L}`)});M1.on("uncaughtException",(L)=>{A.error(`Uncaught Exception: ${L.message}`)});var b=0,G1=!1,N="",s=new Set;function o2(L){return s.add(L),L.finally(()=>s.delete(L)),L}function e2(){let L=M1.env.STACKS_QUEUE_RESERVATION_TTL_SEC,$=L===void 0?Number.NaN:Number.parseInt(L,10);return Number.isFinite($)&&$>0?$:3600}function L5(){let L=M1.env.STACKS_QUEUE_SWEEP_INTERVAL_SEC,$=L===void 0?Number.NaN:Number.parseInt(L,10);return(Number.isFinite($)&&$>0?$:60)*1000}async function SL(){let L=e2(),$=Math.floor(Date.now()/1000)-L,V=Math.floor(Date.now()/1000);try{let{db:Y}=await import("@stacksjs/database"),G=await Y.updateTable("jobs").set({reserved_at:null,available_at:V}).where("reserved_at","<=",$).executeTakeFirst(),U=e(G);if(U>0)A.warn(`[queue] Requeued ${U} job(s) whose reservation exceeded the ${L}s TTL \u2014 likely victims of a worker crash. Set `+"STACKS_QUEUE_RESERVATION_TTL_SEC to tune.");return U}catch(Y){return A.error("[queue] Reservation sweep failed",{reason:Y instanceof Error?Y.message:String(Y)}),0}}function $5(L){if(!L||typeof L!=="object")return!1;let $=L.status??L.statusCode;if(typeof $==="number"&&$>=400&&$<500)return!0;let V=L.name;if(typeof V==="string"&&(V==="ValidationError"||V==="ModelNotFoundError"))return!0;return!1}function V5(){return a2.QUEUE_DRIVER||"sync"}async function Y5(L,$={}){try{A.info("Starting queue processor..."),G1=!0,N=`worker-${M1.pid}-${Date.now()}`;let V=$.concurrency||1,Y=V5(),{getWorkerTracker:G,getGlobalMetrics:U}=await Promise.resolve().then(() => (v(),h));if(U(),G().register(N,L||"default"),Y==="redis")return A.info("Using Redis queue driver (bun-queue)"),await Z5(L||"default",V),NL(void 0);let X;if(L)X=[L];else if(X=await RL(),X.length===0)X=["default"];return A.info(`Processing queues: ${X.join(", ")}`),await G5(X,V),NL(void 0)}catch(V){return G1=!1,r2(V)}}async function RL(){try{let{db:L}=await import("@stacksjs/database"),V=(await L.selectFrom("jobs").select("queue").distinct().execute()).map((Y)=>Y.queue).filter((Y)=>Boolean(Y));return V.length>0?V:["default"]}catch{return["default"]}}async function G5(L,$){A.info("Listening for jobs...");let V=L,Y=Date.now(),G=1e4,U=Date.now(),X=L5();await SL();while(G1)try{let _=Date.now();if(_-Y>G){try{let K=await RL();if(K.length>0)V=K}catch{}Y=_}if(_-U>X)await SL(),U=_;for(let K of V){try{let{isCircuitOpen:O}=await Promise.resolve().then(() => (A1(),f1));if(await O(K))continue}catch{}let z=[];try{z=await U5(K,$)}catch{continue}await Promise.all(z.map(async(O)=>{try{A.info(`Processing job ${O.id} from queue "${K}"`),await o2(X5(O))}catch{A.error(`Unexpected error processing job ${O.id}`)}}))}await Z4(1000)}catch{await Z4(3000)}}async function U5(L,$){let V=Math.floor(Date.now()/1000),{db:Y}=await import("@stacksjs/database"),G=[];for(let U=0;U<$;U++){let X=await Y.selectFrom("jobs").where("queue","=",L).whereNull("reserved_at").where("available_at","<=",V).orderBy("id","asc").limit(1).selectAll().executeTakeFirst();if(!X)break;let _=await Y.updateTable("jobs").set({reserved_at:V,attempts:(X.attempts||0)+1}).where("id","=",X.id).where("reserved_at","is",null).executeTakeFirst();if(e(_)>0)G.push(X)}return G}async function X5(L){let $=L.id,V=L.queue||"default";b++;let Y=Date.now(),{emitQueueEvent:G,getWorkerTracker:U}=await Promise.resolve().then(() => (v(),h)),X=U();X.markActive(N);let _;try{_=JSON.parse(L.payload||"{}").jobName}catch{}await G("job:processing",{jobId:String($),queueName:V,jobName:_});try{let O=JSON.parse(L.payload||"{}").payload?._batchId;if(O){let{isBatchCancelled:Q}=await Promise.resolve().then(() => (w(),u));if(await Q(O)){A.info(`[Queue] Skipping job ${$} - batch ${O} has been cancelled`),await W4($),b--,X.markIdle(N);return}}}catch{}let K=null;try{let z=JSON.parse(L.payload||"{}"),O=_5(z);if(O===void 0)await JL(z);else await W5(JL(z),O*1000,`Job ${$} exceeded ${O}s timeout`)}catch(z){K=z instanceof Error?z:Error(String(z))}if(!K)try{await W4($),A.info(`[Queue] Job ${$} completed`),X.recordCompletion(N);try{let{recordCircuitSuccess:z}=await Promise.resolve().then(() => (A1(),f1));await z(L.queue??"default")}catch{}await G("job:completed",{jobId:String($),queueName:V,duration:Date.now()-Y});try{let O=JSON.parse(L.payload||"{}").payload?._batchId;if(O){let{recordBatchJobCompletion:Q}=await Promise.resolve().then(() => (w(),u));await Q(O)}}catch{}}catch{A.info(`[Queue] Failed to delete completed job ${$}`)}else{let z=K.message;A.info(`[Queue] Job ${$} failed: ${z}`),X.recordFailure(N),await G("job:failed",{jobId:String($),queueName:V,error:K,duration:Date.now()-Y,attemptsMade:(L.attempts||0)+1});let O=1,Q={};try{Q=JSON.parse(L.payload||"{}"),O=Q.options?.tries||1}catch{}let P=(L.attempts||0)+1;if(P>=O){A.info(`[Queue] Job ${$} exceeded max attempts (${P}/${O})`);let Z=!1;if(Q?._retriedFromFailed===!0)try{let{moveToDeadLetter:W}=await Promise.resolve().then(() => (S1(),k4));if(Z=await W({queue:L.queue,payload:L.payload,exception:K.stack||K.message},"repeat-failure",2),Z)A.info(`[Queue] Job ${$} re-failed after retry \u2014 moved to dead_letter_jobs`)}catch{Z=!1}if(!Z){A.info(`[Queue] Moving job ${$} to failed_jobs`);try{Z=await z5(L,K)}catch{Z=!1}}if(Z)try{await W4($)}catch{A.info(`[Queue] Failed to delete failed job ${$}`)}else A.error(`[Queue] Job ${$} exhausted its retries but could NOT be persisted to failed_jobs \u2014 leaving it in the queue to avoid data loss (the reservation sweep will retry it). Check that the failed_jobs table exists and is writable.`);try{let{recordFailureForPoison:W}=await Promise.resolve().then(() => (R1(),h4)),{recordCircuitFailure:x}=await Promise.resolve().then(() => (A1(),f1)),t=Q?.jobName??"unknown";await W(t,Q?.payload),await x(L.queue??"default")}catch{}try{let W=Q.payload?._batchId;if(W){let{recordBatchJobFailure:x}=await Promise.resolve().then(() => (w(),u));await x(W,String($),K)}}catch{}}else{let Z=Q.options?.backoff,W=30;if(Array.isArray(Z)&&Z.length>0){let x=Math.min(P-1,Z.length-1);W=Z[x]}else if(typeof Z==="number"&&Z>0)W=Z;if(W=Number(W),!Number.isFinite(W)||W<0)W=30;A.info(`[Queue] Job ${$} will be retried in ${W}s (attempt ${P}/${O})`);try{await K5($,W),A.info(`[Queue] Job ${$} released for retry`)}catch{A.info(`[Queue] Failed to release job ${$} for retry`)}}}b--,X.markIdle(N)}async function W4(L){let{db:$}=await import("@stacksjs/database");await $.deleteFrom("jobs").where("id","=",L).execute()}async function K5(L,$=30){let V=Math.floor(Date.now()/1000)+$;A.debug(`Releasing job ${L} for retry at ${V}`);try{let{db:Y}=await import("@stacksjs/database");await Y.updateTable("jobs").set({reserved_at:null,available_at:V}).where("id","=",L).execute(),A.debug(`Job ${L} released successfully`)}catch{A.error(`Failed to release job ${L}`)}}async function z5(L,$){try{let V=new Date().toISOString().slice(0,19).replace("T"," "),Y=crypto.randomUUID(),G=$.stack||$.message,{db:U}=await import("@stacksjs/database");return await U.insertInto("failed_jobs").values({uuid:Y,connection:"database",queue:L.queue,payload:L.payload,exception:G,failed_at:V}).execute(),!0}catch(V){return A.error("Failed to log failed job:",V),!1}}function _5(L){if(!L||typeof L!=="object")return;let V=L.options?.timeout;if(typeof V!=="number"||!Number.isFinite(V)||V<=0)return;return V}async function W5(L,$,V){let Y,G=new Promise((U,X)=>{Y=setTimeout(()=>X(Error(V)),$)});try{return await Promise.race([L,G])}finally{if(Y!==void 0)clearTimeout(Y)}}async function JL(L){let $=B1(L);if(!$.ok)throw Error(`[queue] Cannot deserialize job envelope: ${$.reason}`+($.detail?` (${$.detail})`:""));let{runJob:V}=await Promise.resolve().then(() => (Q1(),y1));await V($.envelope.jobName,{payload:$.envelope.payload})}async function Z5(L,$){let{RedisQueue:V}=await Promise.resolve().then(() => (_1(),z1)),{queue:Y}=await import("@stacksjs/config"),G=Y?.connections?.redis;if(!G)throw Error("Redis queue connection is not configured. Check config/queue.ts");let U=new V(L,G),{emitQueueEvent:X,getWorkerTracker:_}=await Promise.resolve().then(() => (v(),h)),K=_();U.process($,async(z)=>{b++,K.markActive(N);let O=Date.now(),Q=B1(z.data);if(!Q.ok){b--,K.markIdle(N),A.error(`[Queue] Skipping Redis job ${z.id} - unparseable envelope: ${Q.reason}${Q.detail?` (${Q.detail})`:""}`);return}let P=Q.envelope,Z=P.payload?._batchId;if(Z)try{let{isBatchCancelled:W}=await Promise.resolve().then(() => (w(),u));if(await W(Z)){A.info(`[Queue] Skipping Redis job ${z.id} - batch ${Z} has been cancelled`),b--,K.markIdle(N);return}}catch{}await X("job:processing",{jobId:String(z.id),queueName:L});try{let{runJob:W}=await Promise.resolve().then(() => (Q1(),y1));if(await W(P.jobName,{payload:P.payload}),K.recordCompletion(N),await X("job:completed",{jobId:String(z.id),queueName:L,duration:Date.now()-O}),Z)try{let{recordBatchJobCompletion:x}=await Promise.resolve().then(() => (w(),u));await x(Z)}catch{}A.info(`[Queue] Redis job ${z.id} completed`)}catch(W){if(K.recordFailure(N),await X("job:failed",{jobId:String(z.id),queueName:L,error:W instanceof Error?W:Error(String(W)),duration:Date.now()-O}),Z)try{let{recordBatchJobFailure:x}=await Promise.resolve().then(() => (w(),u));await x(Z,String(z.id),W instanceof Error?W:Error(String(W)))}catch{}if(A.error(`[Queue] Redis job ${z.id} failed: ${W}`),$5(W)){A.info(`[Queue] Redis job ${z.id} hit a non-retryable error \u2014 skipping retry`);return}throw W}finally{b--,K.markIdle(N)}}),A.info(`Listening for Redis jobs on queue "${L}" with concurrency ${$}...`);while(G1)await Z4(1000);await U.close()}async function O5(L={}){let $=L.graceMs??1e4;if(G1=!1,s.size>0&&$>0){A.info(`[queue] Draining ${s.size} in-flight job(s) (grace ${$}ms)`);let V=Promise.allSettled([...s]),Y=new Promise((U)=>setTimeout(()=>U("timeout"),$));if(await Promise.race([V.then(()=>"drained"),Y])==="timeout"&&s.size>0)A.warn(`[queue] Drain timed out with ${s.size} job(s) still active. Their reservations will be reclaimed by the next worker's sweep (Q-2).`)}if(N){let{getWorkerTracker:V}=await Promise.resolve().then(() => (v(),h));V().unregister(N)}A.info("Queue processor stopped")}async function F5(){let{db:L}=await import("@stacksjs/database"),$=await L.selectFrom("failed_jobs").selectAll().execute();for(let V of $)await CL(Number(V.id))}async function CL(L){let $=Math.floor(Date.now()/1000),V=new Date().toISOString().slice(0,19).replace("T"," "),{db:Y}=await import("@stacksjs/database"),U=(await Y.selectFrom("failed_jobs").where("id","=",L).selectAll().execute())[0];if(!U)throw Error(`Failed job ${L} not found`);let X=U.payload;try{let _=JSON.parse(U.payload||"{}");_._retriedFromFailed=!0,X=JSON.stringify(_)}catch{}await Y.insertInto("jobs").values({queue:U.queue,payload:X,attempts:0,reserved_at:null,available_at:$,created_at:V}).execute(),await Y.deleteFrom("failed_jobs").where("id","=",L).execute(),A.info(`Failed job ${L} has been re-queued`)}function x5(){return b}function H5(){return G1}function Z4(L){return new Promise(($)=>setTimeout($,L))}async function Q8(){let{RedisQueue:L}=await Promise.resolve().then(() => (_1(),z1));return L}export{g4 as withEvents,m4 as unquarantineJob,u2 as triggerJob,I2 as toJobOptions,K4 as stopScheduler,O5 as stopProcessor,j2 as startScheduler,Y5 as startProcessor,T2 as setJobProgress,B4 as runTestJob,w1 as runJob,CL as retryFailedJob,q4 as retryDeadLetterJob,_L as resumeQueue,p1 as restore,c1 as releaseDispatchKey,w4 as recordFailureForPoison,eL as recordDispatchedKey,XL as recordCircuitSuccess,KL as recordCircuitFailure,e1 as recordBatchJobFailure,o1 as recordBatchJobCompletion,f4 as quarantineJob,v4 as purgeDeadLetterJobs,zL as pauseQueue,B1 as parseEnvelope,p4 as onQueueEvent,t2 as notifyJobFailed,N1 as moveToDeadLetter,j4 as listQuarantined,C4 as listDeadLetterJobs,WL as listCircuitState,n as jobRegistry,YL as jobBatch,$L as job,H5 as isWorkerRunning,p2 as isSchedulerRunning,i2 as isQueueHealthy,J1 as isQuarantined,J2 as isJobCancelled,P4 as isFaked,UL as isCircuitOpen,e4 as isBatchCancelled,Z1 as hashPayload,oL as hasDispatchedKey,O1 as getWorkerTracker,h2 as getSchedulerStatus,U4 as getScheduledJobs,g2 as getRegisteredJobs,Q8 as getRedisQueue,L1 as getQueueEvents,N2 as getJobProgress,q2 as getJob,v1 as getGlobalMetrics,D4 as getFakeQueue,s2 as getFailedJobNotifier,F1 as getBatchCallbacks,v2 as getAllJobs,x5 as getActiveJobCount,h1 as fake,E4 as expectJobToFail,k2 as executeJob,F5 as executeFailedJobs,c as emitQueueEvent,G4 as discoverJobs,M4 as createQueueTester,c2 as createHealthCheckHandler,j as createEnvelope,n2 as configureFailedJobNotifications,R2 as clearJobState,gL as clearEnvelopeWarnings,l1 as claimDispatchKey,z4 as checkQueueHealth,S2 as cancelJob,E1 as QueueTester,q1 as QueueMetrics,C1 as QueueEvents,I1 as PendingBatch,u4 as OnQueueEvent,VL as Jobs,J4 as Job,pL as JOB_ENVELOPE_VERSION,_4 as FailedJobNotifier,p as DispatchedBatch,s1 as Batch};
31
+ \`\`\`${Y.exception.slice(0,200)}\`\`\``}}))];if(L.length>10)V.push({type:"section",text:{type:"mrkdwn",text:`_...and ${L.length-10} more failed jobs_`}});try{let Y=await fetch($.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:$.channel,username:$.username||"Queue Monitor",icon_emoji:$.iconEmoji||":warning:",blocks:V})});if(!Y.ok)C.error(`Slack notification failed with status ${Y.status}`)}catch(Y){C.error("Failed to send Slack notification:",Y)}}async sendDiscord(L){let $=this.config.discord,V=L.slice(0,10).map((Y)=>({title:`\u274C ${Y.name}`,color:15158332,fields:[{name:"Queue",value:Y.queue,inline:!0},{name:"Attempts",value:`${Y.attempts}/${Y.maxAttempts}`,inline:!0},{name:"Failed At",value:Y.failedAt.toISOString(),inline:!0},{name:"Exception",value:`\`\`\`${Y.exception.slice(0,500)}\`\`\``}],timestamp:Y.failedAt.toISOString()}));try{let Y=await fetch($.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:$.username||"Queue Monitor",avatar_url:$.avatarUrl,content:`\uD83D\uDEA8 **${L.length} Job(s) Failed**`,embeds:V})});if(!Y.ok)C.error(`Discord notification failed with status ${Y.status}`)}catch(Y){C.error("Failed to send Discord notification:",Y)}}async sendWebhook(L){let $=this.config.webhook,V={event:"jobs.failed",timestamp:new Date().toISOString(),count:L.length,jobs:L.map((G)=>({id:G.id,name:G.name,queue:G.queue,attempts:G.attempts,maxAttempts:G.maxAttempts,exception:G.exception,failedAt:G.failedAt.toISOString()}))},Y={"Content-Type":"application/json",...$.headers};if($.secret){let G=JSON.stringify(V),U=await this.generateSignature(G,$.secret);Y["X-Signature"]=U}try{let G=await fetch($.url,{method:"POST",headers:Y,body:JSON.stringify(V)});if(!G.ok)C.error(`Webhook notification failed with status ${G.status}`)}catch(G){C.error("Failed to send webhook notification:",G)}}async generateSignature(L,$){let V=new TextEncoder,Y=await crypto.subtle.importKey("raw",V.encode($),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),G=await crypto.subtle.sign("HMAC",Y,V.encode(L));return Array.from(new Uint8Array(G)).map((U)=>U.toString(16).padStart(2,"0")).join("")}logFailures(L){for(let $ of L)C.error(`[Queue] Job "${$.name}" failed on queue "${$.queue}" after ${$.attempts} attempts: ${$.exception}`)}async cleanup(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.pendingBatch.length>0)await this.flushBatch()}}var P1=null;function s2(L){return P1=new W4(L),P1}function t2(){return P1}async function r2(L){if(P1)await P1.notify(L)}f();U1();import{err as a2,ok as SL}from"@stacksjs/error-handling";import{log as A}from"@stacksjs/logging";import M1 from"process";import{env as o2}from"@stacksjs/env";M1.on("unhandledRejection",(L,$)=>{A.error(`Unhandled Rejection: ${L}`)});M1.on("uncaughtException",(L)=>{A.error(`Uncaught Exception: ${L.message}`)});var b=0,Y1=!1,N="",n=new Set;function e2(L){return n.add(L),L.finally(()=>n.delete(L)),L}function L5(){let L=M1.env.STACKS_QUEUE_RESERVATION_TTL_SEC,$=L===void 0?Number.NaN:Number.parseInt(L,10);return Number.isFinite($)&&$>0?$:3600}function $5(){let L=M1.env.STACKS_QUEUE_SWEEP_INTERVAL_SEC,$=L===void 0?Number.NaN:Number.parseInt(L,10);return(Number.isFinite($)&&$>0?$:60)*1000}async function JL(){let L=L5(),$=Math.floor(Date.now()/1000)-L,V=Math.floor(Date.now()/1000);try{let{db:Y}=await import("@stacksjs/database"),G=await Y.updateTable("jobs").set({reserved_at:null,available_at:V}).where("reserved_at","<=",$).executeTakeFirst(),U=o(G);if(U>0)A.warn(`[queue] Requeued ${U} job(s) whose reservation exceeded the ${L}s TTL \u2014 likely victims of a worker crash. Set `+"STACKS_QUEUE_RESERVATION_TTL_SEC to tune.");return U}catch(Y){return A.error("[queue] Reservation sweep failed",{reason:Y instanceof Error?Y.message:String(Y)}),0}}function V5(L){if(!L||typeof L!=="object")return!1;let $=L.status??L.statusCode;if(typeof $==="number"&&$>=400&&$<500)return!0;let V=L.name;if(typeof V==="string"&&(V==="ValidationError"||V==="ModelNotFoundError"))return!0;return!1}function Y5(){return o2.QUEUE_DRIVER||"sync"}async function G5(L,$={}){try{A.info("Starting queue processor..."),Y1=!0,N=`worker-${M1.pid}-${Date.now()}`;let V=$.concurrency||1,Y=Y5(),{getWorkerTracker:G,getGlobalMetrics:U}=await Promise.resolve().then(() => (v(),h));if(U(),G().register(N,L||"default"),Y==="redis")return A.info("Using Redis queue driver (bun-queue)"),await O5(L||"default",V),SL(void 0);let X;if(L)X=[L];else if(X=await CL(),X.length===0)X=["default"];return A.info(`Processing queues: ${X.join(", ")}`),await U5(X,V),SL(void 0)}catch(V){return Y1=!1,a2(V)}}async function CL(){try{let{db:L}=await import("@stacksjs/database"),V=(await L.selectFrom("jobs").select("queue").distinct().execute()).map((Y)=>Y.queue).filter((Y)=>Boolean(Y));return V.length>0?V:["default"]}catch{return["default"]}}async function U5(L,$){A.info("Listening for jobs...");let V=L,Y=Date.now(),G=1e4,U=Date.now(),X=$5();await JL();while(Y1)try{let _=Date.now();if(_-Y>G){try{let K=await CL();if(K.length>0)V=K}catch{}Y=_}if(_-U>X)await JL(),U=_;for(let K of V){try{let{isCircuitOpen:O}=await Promise.resolve().then(() => (A1(),f1));if(await O(K))continue}catch{}let z=[];try{z=await X5(K,$)}catch{continue}await Promise.all(z.map(async(O)=>{try{A.info(`Processing job ${O.id} from queue "${K}"`),await e2(K5(O))}catch{A.error(`Unexpected error processing job ${O.id}`)}}))}await O4(1000)}catch{await O4(3000)}}async function X5(L,$){let V=Math.floor(Date.now()/1000),{db:Y}=await import("@stacksjs/database"),G=[];for(let U=0;U<$;U++){let X=await Y.selectFrom("jobs").where("queue","=",L).whereNull("reserved_at").where("available_at","<=",V).orderBy("id","asc").limit(1).selectAll().executeTakeFirst();if(!X)break;let _=await Y.updateTable("jobs").set({reserved_at:V,attempts:(X.attempts||0)+1}).where("id","=",X.id).where("reserved_at","is",null).executeTakeFirst();if(o(_)>0)G.push(X)}return G}async function K5(L){let $=L.id,V=L.queue||"default";b++;let Y=Date.now(),{emitQueueEvent:G,getWorkerTracker:U}=await Promise.resolve().then(() => (v(),h)),X=U();X.markActive(N);let _;try{_=JSON.parse(L.payload||"{}").jobName}catch{}await G("job:processing",{jobId:String($),queueName:V,jobName:_});try{let O=JSON.parse(L.payload||"{}").payload?._batchId;if(O){let{isBatchCancelled:Q}=await Promise.resolve().then(() => (f(),u));if(await Q(O)){A.info(`[Queue] Skipping job ${$} - batch ${O} has been cancelled`),await Z4($),b--,X.markIdle(N);return}}}catch{}let K=null;try{let z=JSON.parse(L.payload||"{}"),O=W5(z);if(O===void 0)await RL(z);else await Z5(RL(z),O*1000,`Job ${$} exceeded ${O}s timeout`)}catch(z){K=z instanceof Error?z:Error(String(z))}if(!K)try{await Z4($),A.info(`[Queue] Job ${$} completed`),X.recordCompletion(N);try{let{recordCircuitSuccess:z}=await Promise.resolve().then(() => (A1(),f1));await z(L.queue??"default")}catch{}await G("job:completed",{jobId:String($),queueName:V,duration:Date.now()-Y});try{let O=JSON.parse(L.payload||"{}").payload?._batchId;if(O){let{recordBatchJobCompletion:Q}=await Promise.resolve().then(() => (f(),u));await Q(O)}}catch{}}catch{A.info(`[Queue] Failed to delete completed job ${$}`)}else{let z=K.message;A.info(`[Queue] Job ${$} failed: ${z}`),X.recordFailure(N),await G("job:failed",{jobId:String($),queueName:V,error:K,duration:Date.now()-Y,attemptsMade:(L.attempts||0)+1});let O=1,Q={};try{Q=JSON.parse(L.payload||"{}"),O=Q.options?.tries||1}catch{}let P=(L.attempts||0)+1;if(P>=O){A.info(`[Queue] Job ${$} exceeded max attempts (${P}/${O})`);let Z=!1;if(Q?._retriedFromFailed===!0)try{let{moveToDeadLetter:W}=await Promise.resolve().then(() => (S1(),I4));if(Z=await W({queue:L.queue,payload:L.payload,exception:K.stack||K.message},"repeat-failure",2),Z)A.info(`[Queue] Job ${$} re-failed after retry \u2014 moved to dead_letter_jobs`)}catch{Z=!1}if(!Z){A.info(`[Queue] Moving job ${$} to failed_jobs`);try{Z=await _5(L,K)}catch{Z=!1}}if(Z)try{await Z4($)}catch{A.info(`[Queue] Failed to delete failed job ${$}`)}else A.error(`[Queue] Job ${$} exhausted its retries but could NOT be persisted to failed_jobs \u2014 leaving it in the queue to avoid data loss (the reservation sweep will retry it). Check that the failed_jobs table exists and is writable.`);try{let{recordFailureForPoison:W}=await Promise.resolve().then(() => (R1(),p4)),{recordCircuitFailure:x}=await Promise.resolve().then(() => (A1(),f1)),s=Q?.jobName??"unknown";await W(s,Q?.payload),await x(L.queue??"default")}catch{}try{let W=Q.payload?._batchId;if(W){let{recordBatchJobFailure:x}=await Promise.resolve().then(() => (f(),u));await x(W,String($),K)}}catch{}}else{let Z=Q.options?.backoff,W=30;if(Array.isArray(Z)&&Z.length>0){let x=Math.min(P-1,Z.length-1);W=Z[x]}else if(typeof Z==="number"&&Z>0)W=Z;if(W=Number(W),!Number.isFinite(W)||W<0)W=30;A.info(`[Queue] Job ${$} will be retried in ${W}s (attempt ${P}/${O})`);try{await z5($,W),A.info(`[Queue] Job ${$} released for retry`)}catch{A.info(`[Queue] Failed to release job ${$} for retry`)}}}b--,X.markIdle(N)}async function Z4(L){let{db:$}=await import("@stacksjs/database");await $.deleteFrom("jobs").where("id","=",L).execute()}async function z5(L,$=30){let V=Math.floor(Date.now()/1000)+$;A.debug(`Releasing job ${L} for retry at ${V}`);try{let{db:Y}=await import("@stacksjs/database");await Y.updateTable("jobs").set({reserved_at:null,available_at:V}).where("id","=",L).execute(),A.debug(`Job ${L} released successfully`)}catch{A.error(`Failed to release job ${L}`)}}async function _5(L,$){try{let V=new Date().toISOString().slice(0,19).replace("T"," "),Y=crypto.randomUUID(),G=$.stack||$.message,{db:U}=await import("@stacksjs/database");return await U.insertInto("failed_jobs").values({uuid:Y,connection:"database",queue:L.queue,payload:L.payload,exception:G,failed_at:V}).execute(),!0}catch(V){return A.error("Failed to log failed job:",V),!1}}function W5(L){if(!L||typeof L!=="object")return;let V=L.options?.timeout;if(typeof V!=="number"||!Number.isFinite(V)||V<=0)return;return V}async function Z5(L,$,V){let Y,G=new Promise((U,X)=>{Y=setTimeout(()=>X(Error(V)),$)});try{return await Promise.race([L,G])}finally{if(Y!==void 0)clearTimeout(Y)}}async function RL(L){let $=B1(L);if(!$.ok)throw Error(`[queue] Cannot deserialize job envelope: ${$.reason}`+($.detail?` (${$.detail})`:""));let{runJob:V}=await Promise.resolve().then(() => (Q1(),y1));await V($.envelope.jobName,{payload:$.envelope.payload})}async function O5(L,$){let{RedisQueue:V}=await Promise.resolve().then(() => (_1(),z1)),{queue:Y}=await import("@stacksjs/config"),G=Y?.connections?.redis;if(!G)throw Error("Redis queue connection is not configured. Check config/queue.ts");let U=new V(L,G),{emitQueueEvent:X,getWorkerTracker:_}=await Promise.resolve().then(() => (v(),h)),K=_();U.process($,async(z)=>{b++,K.markActive(N);let O=Date.now(),Q=B1(z.data);if(!Q.ok){b--,K.markIdle(N),A.error(`[Queue] Skipping Redis job ${z.id} - unparseable envelope: ${Q.reason}${Q.detail?` (${Q.detail})`:""}`);return}let P=Q.envelope,Z=P.payload?._batchId;if(Z)try{let{isBatchCancelled:W}=await Promise.resolve().then(() => (f(),u));if(await W(Z)){A.info(`[Queue] Skipping Redis job ${z.id} - batch ${Z} has been cancelled`),b--,K.markIdle(N);return}}catch{}await X("job:processing",{jobId:String(z.id),queueName:L});try{let{runJob:W}=await Promise.resolve().then(() => (Q1(),y1));if(await W(P.jobName,{payload:P.payload}),K.recordCompletion(N),await X("job:completed",{jobId:String(z.id),queueName:L,duration:Date.now()-O}),Z)try{let{recordBatchJobCompletion:x}=await Promise.resolve().then(() => (f(),u));await x(Z)}catch{}A.info(`[Queue] Redis job ${z.id} completed`)}catch(W){if(K.recordFailure(N),await X("job:failed",{jobId:String(z.id),queueName:L,error:W instanceof Error?W:Error(String(W)),duration:Date.now()-O}),Z)try{let{recordBatchJobFailure:x}=await Promise.resolve().then(() => (f(),u));await x(Z,String(z.id),W instanceof Error?W:Error(String(W)))}catch{}if(A.error(`[Queue] Redis job ${z.id} failed: ${W}`),V5(W)){A.info(`[Queue] Redis job ${z.id} hit a non-retryable error \u2014 skipping retry`);return}throw W}finally{b--,K.markIdle(N)}}),A.info(`Listening for Redis jobs on queue "${L}" with concurrency ${$}...`);while(Y1)await O4(1000);await U.close()}async function F5(L={}){let $=L.graceMs??1e4;if(Y1=!1,n.size>0&&$>0){A.info(`[queue] Draining ${n.size} in-flight job(s) (grace ${$}ms)`);let V=Promise.allSettled([...n]),Y=new Promise((U)=>setTimeout(()=>U("timeout"),$));if(await Promise.race([V.then(()=>"drained"),Y])==="timeout"&&n.size>0)A.warn(`[queue] Drain timed out with ${n.size} job(s) still active. Their reservations will be reclaimed by the next worker's sweep (Q-2).`)}if(N){let{getWorkerTracker:V}=await Promise.resolve().then(() => (v(),h));V().unregister(N)}A.info("Queue processor stopped")}async function x5(){let{db:L}=await import("@stacksjs/database"),$=await L.selectFrom("failed_jobs").selectAll().execute();for(let V of $)await qL(Number(V.id))}async function qL(L){let $=Math.floor(Date.now()/1000),V=new Date().toISOString().slice(0,19).replace("T"," "),{db:Y}=await import("@stacksjs/database"),U=(await Y.selectFrom("failed_jobs").where("id","=",L).selectAll().execute())[0];if(!U)throw Error(`Failed job ${L} not found`);let X=U.payload;try{let _=JSON.parse(U.payload||"{}");_._retriedFromFailed=!0,X=JSON.stringify(_)}catch{}await Y.insertInto("jobs").values({queue:U.queue,payload:X,attempts:0,reserved_at:null,available_at:$,created_at:V}).execute(),await Y.deleteFrom("failed_jobs").where("id","=",L).execute(),A.info(`Failed job ${L} has been re-queued`)}function H5(){return b}function Q5(){return Y1}function O4(L){return new Promise(($)=>setTimeout($,L))}async function D8(){let{RedisQueue:L}=await Promise.resolve().then(() => (_1(),z1));return L}export{u4 as withEvents,j4 as unquarantineJob,b2 as triggerJob,y2 as toJobOptions,z4 as stopScheduler,F5 as stopProcessor,h2 as startScheduler,G5 as startProcessor,N2 as setJobProgress,E4 as runTestJob,w1 as runJob,qL as retryFailedJob,v4 as retryDeadLetterJob,WL as resumeQueue,p1 as restore,c1 as releaseDispatchKey,f4 as recordFailureForPoison,L2 as recordDispatchedKey,KL as recordCircuitSuccess,zL as recordCircuitFailure,L4 as recordBatchJobFailure,e1 as recordBatchJobCompletion,m4 as quarantineJob,k4 as purgeDeadLetterJobs,_L as pauseQueue,B1 as parseEnvelope,g4 as onQueueEvent,r2 as notifyJobFailed,N1 as moveToDeadLetter,h4 as listQuarantined,q4 as listDeadLetterJobs,ZL as listCircuitState,d as jobRegistry,GL as jobBatch,VL as job,Q5 as isWorkerRunning,g2 as isSchedulerRunning,d2 as isQueueHealthy,J1 as isQuarantined,R2 as isJobCancelled,M4 as isFaked,XL as isCircuitOpen,LL as isBatchCancelled,Z1 as hashPayload,eL as hasDispatchedKey,O1 as getWorkerTracker,p2 as getSchedulerStatus,X4 as getScheduledJobs,u2 as getRegisteredJobs,D8 as getRedisQueue,e as getQueueEvents,S2 as getJobProgress,v2 as getJob,v1 as getGlobalMetrics,P4 as getFakeQueue,t2 as getFailedJobNotifier,F1 as getBatchCallbacks,k2 as getAllJobs,H5 as getActiveJobCount,h1 as fake,T4 as expectJobToFail,I2 as executeJob,x5 as executeFailedJobs,l as emitQueueEvent,U4 as discoverJobs,B4 as createQueueTester,i2 as createHealthCheckHandler,j as createEnvelope,s2 as configureFailedJobNotifications,C2 as clearJobState,uL as clearEnvelopeWarnings,l1 as claimDispatchKey,_4 as checkQueueHealth,J2 as cancelJob,E1 as QueueTester,q1 as QueueMetrics,C1 as QueueEvents,I1 as PendingBatch,b4 as OnQueueEvent,YL as Jobs,R4 as Job,gL as JOB_ENVELOPE_VERSION,W4 as FailedJobNotifier,p as DispatchedBatch,t1 as Batch};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/queue",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.206",
5
+ "version": "0.70.208",
6
6
  "description": "The Stacks Queue system powered by bun-queue.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [