@stacksjs/queue 0.70.296 → 0.70.298

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