@stacksjs/queue 0.70.165 → 0.70.167

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 +7 -7
  2. package/package.json +3 -12
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // @bun
2
- var{defineProperty:j5,getOwnPropertyNames:y9,getOwnPropertyDescriptor:w9}=Object,f9=Object.prototype.hasOwnProperty;function m9($){return this[$]}var j9=($)=>{var V=(A8??=new WeakMap).get($),Y;if(V)return V;if(V=j5({},"__esModule",{value:!0}),$&&typeof $==="object"||typeof $==="function"){for(var G of y9($))if(!f9.call(V,G))j5(V,G,{get:m9.bind($,G),enumerable:!(Y=w9($,G))||Y.enumerable})}return A8.set($,V),V},A8;var h9=($)=>$;function p9($,V){this[$]=h9.bind(null,V)}var w=($,V)=>{for(var Y in V)j5($,Y,{get:V[Y],enumerable:!0,configurable:!0,set:p9.bind(V,Y)})};var f=($,V)=>()=>($&&(V=$($=0)),V);var F=import.meta.require;function m($,V,Y){return{jobName:$,payload:V,options:Y,envelopeVersion:1,dispatchedAt:new Date().toISOString()}}function D8($,V){if(h5.has($))return;h5.add($),console.warn(V)}function b9(){h5.clear()}function B5($){let V;if(typeof $==="string")try{V=JSON.parse($)}catch{return{ok:!1,reason:"malformed",detail:"not valid JSON"}}else if($&&typeof $==="object")V=$;else return{ok:!1,reason:"malformed",detail:`expected string or object, got ${typeof $}`};if(V.envelopeVersion===1){if(typeof V.jobName!=="string")return{ok:!1,reason:"missing-job-name"};return{ok:!0,envelope:{jobName:V.jobName,payload:V.payload,options:V.options??void 0,envelopeVersion:1,dispatchedAt:typeof V.dispatchedAt==="string"?V.dispatchedAt:new Date(0).toISOString()},source:"v1"}}if(typeof V.envelopeVersion==="number"&&V.envelopeVersion>1)return{ok:!1,reason:"unknown-version",detail:`envelopeVersion=${V.envelopeVersion}, this worker speaks v1`};if(typeof V.jobName==="string")return D8("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:V.jobName,payload:V.payload,options:V.options??void 0,envelopeVersion:1,dispatchedAt:typeof V.dispatchedAt==="string"?V.dispatchedAt:new Date(0).toISOString()},source:"v0-implicit"};if(typeof V.job==="string")return D8("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:V.job.replace(/^App\\+Jobs\\+/,"").replace(/^.*\\/,""),payload:V.data,options:void 0,envelopeVersion:1,dispatchedAt:new Date(0).toISOString()},source:"laravel-legacy"};return{ok:!1,reason:"missing-job-name"}}var g9=1,h5;var K5=f(()=>{h5=new Set});var b5={};w(b5,{runJob:()=>E8,restore:()=>g5,isFaked:()=>B8,getFakeQueue:()=>M8,fake:()=>p5,expectJobToFail:()=>R8,createQueueTester:()=>T8,QueueTester:()=>T5});class P8{dispatchedJobs=[];pushedJobs=[];processedJobs=[];failedJobs=[];dispatch($,V,Y={}){this.dispatchedJobs.push({name:$,data:V,options:Y,dispatchedAt:new Date,queue:Y.queue||"default"})}push($,V,Y={}){this.pushedJobs.push({name:$,data:V,options:Y,dispatchedAt:new Date,queue:Y.queue||"default"})}dispatched($){if($)return this.dispatchedJobs.filter((V)=>V.name===$);return[...this.dispatchedJobs]}pushed($){if($)return this.pushedJobs.filter((V)=>V.name===$);return[...this.pushedJobs]}assertDispatched($,V){let Y=this.dispatched($);if(Y.length===0)throw Error(`Expected job "${$}" to be dispatched, but it was not.`);if(V){if(Y.filter(V).length===0)throw Error(`Expected job "${$}" to be dispatched matching the callback, but no matching jobs were found.`)}}assertNotDispatched($){let V=this.dispatched($);if(V.length>0)throw Error(`Expected job "${$}" to not be dispatched, but it was dispatched ${V.length} time(s).`)}assertDispatchedTimes($,V){let Y=this.dispatched($);if(Y.length!==V)throw Error(`Expected job "${$}" to be dispatched ${V} time(s), but it was dispatched ${Y.length} time(s).`)}assertNothingDispatched(){if(this.dispatchedJobs.length>0){let $=[...new Set(this.dispatchedJobs.map((V)=>V.name))].join(", ");throw Error(`Expected no jobs to be dispatched, but found: ${$}`)}}assertPushed($,V){let Y=this.pushed($);if(Y.length===0)throw Error(`Expected job "${$}" to be pushed, but it was not.`);if(V){if(Y.filter(V).length===0)throw Error(`Expected job "${$}" to be pushed matching the callback, but no matching jobs were found.`)}}assertPushedWithDelay($,V){if(this.pushed($).filter((X)=>X.options.delay===V).length===0)throw Error(`Expected job "${$}" to be pushed with delay ${V}ms, but no matching jobs were found.`)}assertPushedOn($,V){if(this.pushed(V).filter((G)=>G.queue===$).length===0)throw Error(`Expected job "${V}" to be pushed on queue "${$}", but it was not.`)}async processJob($,V){let Y=this.dispatchedJobs.find((G)=>G.name===$);if(!Y)throw Error(`No dispatched job found with name "${$}"`);try{await V(Y.data),this.processedJobs.push(Y)}catch(G){throw this.failedJobs.push({job:Y,error:G}),G}}processed($){if($)return this.processedJobs.filter((V)=>V.name===$);return[...this.processedJobs]}failed($){if($)return this.failedJobs.filter((V)=>V.job.name===$);return[...this.failedJobs]}reset(){this.dispatchedJobs=[],this.pushedJobs=[],this.processedJobs=[],this.failedJobs=[]}}function p5(){return z5=new P8,z5}function M8(){return z5}function B8(){return z5!==null}function g5(){z5=null}class T5{queue;constructor(){this.queue=p5()}dispatch($,V,Y={}){return this.queue.dispatch($,V,Y),this}push($,V,Y={}){return this.queue.push($,V,Y),this}assertDispatched($,V){return this.queue.assertDispatched($,V),this}assertNotDispatched($){return this.queue.assertNotDispatched($),this}assertDispatchedTimes($,V){return this.queue.assertDispatchedTimes($,V),this}assertNothingDispatched(){return this.queue.assertNothingDispatched(),this}dispatched($){return this.queue.dispatched($)}reset(){return this.queue.reset(),this}cleanup(){g5()}}function T8(){return new T5}async function E8($,V){return await $.handle(V)}async function R8($,V,Y){try{throw await $.handle(V),Error("Expected job to fail, but it succeeded")}catch(G){if(G.message==="Expected job to fail, but it succeeded")throw G;if(Y){let X=G.message;if(typeof Y==="string"){if(!X.includes(Y))throw Error(`Expected error to contain "${Y}", got "${X}"`)}else if(!Y.test(X))throw Error(`Expected error to match ${Y}, got "${X}"`)}return G}}var z5=null;var U5={};w(U5,{setQueueManager:()=>r9,getQueueManager:()=>s9,dispatchSync:()=>n9,dispatchAfter:()=>d9,dispatch:()=>i9,createRedisDispatcher:()=>a9,chain:()=>c9,batch:()=>l9,StacksQueueManager:()=>N8,RedisQueue:()=>r,RedisJob:()=>J8,QueueManager:()=>t9});import{Queue as u9,batch as l9,chain as c9,dispatch as i9,dispatchAfter as d9,dispatchSync as n9,getQueueManager as s9,QueueManager as t9,setQueueManager as r9}from"@stacksjs/bun-queue";import{log as u5}from"@stacksjs/logging";class r{queue;config;isProcessing=!1;constructor($,V){this.config=V,this.queue=new u9($,{driver:"redis",prefix:V.prefix,redis:V.redis?{url:V.redis.url||this.buildRedisUrl(V.redis)}:void 0,defaultJobOptions:V.defaultJobOptions?{delay:V.defaultJobOptions.delay,attempts:V.defaultJobOptions.attempts,backoff:V.defaultJobOptions.backoff,removeOnComplete:V.defaultJobOptions.removeOnComplete,removeOnFail:V.defaultJobOptions.removeOnFail,priority:V.defaultJobOptions.priority,lifo:V.defaultJobOptions.lifo,timeout:V.defaultJobOptions.timeout,jobId:V.defaultJobOptions.jobId,dependsOn:V.defaultJobOptions.dependsOn,keepJobs:V.defaultJobOptions.keepJobs,deadLetter:V.defaultJobOptions.deadLetter}:void 0,limiter:V.limiter,metrics:V.metrics,stalledJobCheckInterval:V.stalledJobCheckInterval,maxStalledJobRetries:V.maxStalledJobRetries,distributedLock:V.distributedLock,defaultDeadLetterOptions:V.defaultDeadLetterOptions,horizontalScaling:V.horizontalScaling,logLevel:V.logLevel}),u5.debug(`Redis queue "${$}" initialized`)}buildRedisUrl($){let V=$.host||"localhost",Y=$.port||6379,G=$.password?`:${encodeURIComponent($.password)}@`:"",X=Number.isFinite($.db)&&$.db>=0?$.db:0;return`redis://${G}${V}:${Y}/${X}`}async add($,V){let Y={delay:V?.delay?V.delay*1000:void 0,attempts:V?.maxTries,priority:V?.priority,timeout:V?.timeout?V.timeout*1000:void 0,backoff:Array.isArray(V?.backoff)?V.backoff.map((G)=>(Number(G)||1)*1000):V?.backoff};return this.queue.add($,Y)}process($,V){if(this.isProcessing){u5.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process($,V),u5.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({cron:$.cron,data:$.data,tz:$.tz,name:$.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($,V){this.queue.events.on($,V)}}class N8{config;queues=new Map;defaultConnection="default";constructor($){this.config=$;for(let[V,Y]of Object.entries($))if(Y.driver==="redis")this.queues.set(V,new r(V,Y))}queue($){let V=$||this.defaultConnection,Y=this.queues.get(V);if(!Y){let G=this.config[V];if(!G)throw Error(`Queue "${V}" not configured`);Y=new r(V,G),this.queues.set(V,Y)}return Y}setDefaultConnection($){this.defaultConnection=$}async closeAll(){let $=Array.from(this.queues.values()).map((V)=>V.close());await Promise.all($),this.queues.clear()}}function a9($,V){let Y=new r($,V);return async(G,X)=>{return Y.add(G,X)}}class J8{data;options={};queue;constructor($,V,Y){this.data=Y;this.queue=new r($,V)}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 W5=()=>{};var y8={};w(y8,{retryDeadLetterJob:()=>k8,purgeDeadLetterJobs:()=>I8,moveToDeadLetter:()=>N5,listDeadLetterJobs:()=>v8});import{db as a}from"@stacksjs/database";function E5(){if(q8)return;q8=!0,console.warn("[queue/dlq] dead_letter_jobs table missing \u2014 DLQ disabled. "+"Run migrations to enable poison-message isolation.")}function R5($){let V=$?.message??"";return V.includes("no such table")||V.includes("doesn't exist")}async function N5($,V,Y=1){let G=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:V,total_failures:Y,first_failed_at:$.failed_at??G,last_failed_at:G,dead_lettered_at:G}).execute(),!0}catch(X){if(R5(X))return E5(),!1;throw X}}async function v8($={}){try{let V=a.selectFrom("dead_letter_jobs").selectAll();if($.queue)V=V.where("queue","=",$.queue);if($.reason)V=V.where("reason","=",$.reason);if($.sinceCutoffMs){let G=new Date($.sinceCutoffMs).toISOString().slice(0,19).replace("T"," ");V=V.where("dead_lettered_at",">=",G)}if($.limit&&$.limit>0)V=V.limit($.limit);return await V.execute()??[]}catch(V){if(R5(V))return E5(),[];throw V}}async function k8($){try{let V=await a.selectFrom("dead_letter_jobs").where("id","=",$).selectAll().executeTakeFirst();if(!V)return!1;let Y=Math.floor(Date.now()/1000);return await a.insertInto("jobs").values({queue:V.queue,payload:V.payload,attempts:0,reserved_at:null,available_at:Y,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),await a.deleteFrom("dead_letter_jobs").where("id","=",$).execute(),!0}catch(V){if(R5(V))return E5(),!1;throw V}}async function I8($=30){try{let V=new Date(Date.now()-$*24*60*60*1000).toISOString().slice(0,19).replace("T"," "),Y=await a.deleteFrom("dead_letter_jobs").where("dead_lettered_at","<",V).execute();return Number(Y?.numDeletedRows??Y?.[0]?.numDeletedRows??Y?.affectedRows??0)}catch(V){if(R5(V))return E5(),0;throw V}}var q8=!1;var J5=()=>{};function l5(){if(w8)return;w8=!0,console.warn("[queue/idempotency] job_idempotency table missing \u2014 idempotency keys are accepted but NOT enforced. "+"Run migrations to enable dedup.")}function c5($){let V=$?.message??"";return V.includes("no such table")||V.includes("doesn't exist")}async function e9($){try{let{db:V}=await import("@stacksjs/database"),Y=await V.selectFrom("job_idempotency").where("idempotency_key","=",$).select(["idempotency_key"]).executeTakeFirst();return Boolean(Y)}catch(V){if(c5(V))return l5(),!1;throw V}}async function $$($,V,Y){try{let{db:G}=await import("@stacksjs/database");await G.insertInto("job_idempotency").values({idempotency_key:$,job_name:V,queue:Y??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}catch(G){if(c5(G)){l5();return}let X=G?.message??"";if(X.includes("UNIQUE constraint")||X.includes("Duplicate entry"))return;throw G}}async function i5($,V,Y){try{let{db:G}=await import("@stacksjs/database");return await G.insertInto("job_idempotency").values({idempotency_key:$,job_name:V,queue:Y??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),"claimed"}catch(G){if(c5(G))return l5(),"unenforced";let X=G?.message??"";if(X.includes("UNIQUE constraint")||X.includes("Duplicate entry"))return"duplicate";throw G}}async function d5($){try{let{db:V}=await import("@stacksjs/database");await V.deleteFrom("job_idempotency").where("idempotency_key","=",$).execute()}catch{}}var w8=!1;var g8={};w(g8,{unquarantineJob:()=>h8,recordFailureForPoison:()=>m8,quarantineJob:()=>j8,listQuarantined:()=>p8,isQuarantined:()=>S5,hashPayload:()=>O5});import{createHash as V$}from"crypto";import{db as J}from"@stacksjs/database";function Z5(){if(f8)return;f8=!0,console.warn("[queue/poison] job_quarantine table missing \u2014 poison detection disabled. "+"Run migrations to enable.")}function _5($){let V=$?.message??"";return V.includes("no such table")||V.includes("doesn't exist")}function O5($){let V;try{V=typeof $==="string"?$:JSON.stringify($??null)}catch{V=String($)}return V$("sha256").update(V).digest("hex").slice(0,32)}async function m8($,V,Y={}){let G=Y.maxFailures??5,X=Y.windowMinutes??60,K=O5(V),z=new Date,W=z.toISOString().slice(0,19).replace("T"," ");try{let L=await J.selectFrom("job_quarantine").where("job_name","=",$).where("payload_hash","=",K).selectAll().executeTakeFirst();if(!L)return await J.insertInto("job_quarantine").values({job_name:$,payload_hash:K,failure_count:1,window_start:W,quarantined_at:null}).execute(),!1;if(L.quarantined_at)return!0;let U=Date.parse(L.window_start.replace(" ","T")+"Z"),_=z.getTime()-U,Q=X*60*1000;if(Number.isFinite(U)&&_>Q)return await J.updateTable("job_quarantine").set({failure_count:1,window_start:W}).where("id","=",L.id).execute(),!1;let P=L.failure_count+1;if(P>=G)return await J.updateTable("job_quarantine").set({failure_count:P,quarantined_at:W}).where("id","=",L.id).execute(),!0;return await J.updateTable("job_quarantine").set({failure_count:P}).where("id","=",L.id).execute(),!1}catch(L){if(_5(L))return Z5(),!1;throw L}}async function S5($,V){let Y=O5(V);try{let G=await J.selectFrom("job_quarantine").where("job_name","=",$).where("payload_hash","in",[Y,"*"]).where("quarantined_at","is not",null).select(["id"]).executeTakeFirst();return Boolean(G)}catch(G){if(_5(G))return Z5(),!1;throw G}}async function j8($,V){let Y=V===void 0?"*":O5(V),G=new Date().toISOString().slice(0,19).replace("T"," ");try{let X=await J.selectFrom("job_quarantine").where("job_name","=",$).where("payload_hash","=",Y).select(["id"]).executeTakeFirst();if(X)await J.updateTable("job_quarantine").set({quarantined_at:G}).where("id","=",X.id).execute();else await J.insertInto("job_quarantine").values({job_name:$,payload_hash:Y,failure_count:0,window_start:G,quarantined_at:G}).execute()}catch(X){if(_5(X)){Z5();return}throw X}}async function h8($){try{await J.deleteFrom("job_quarantine").where("job_name","=",$).execute()}catch(V){if(_5(V)){Z5();return}throw V}}async function p8(){try{return await J.selectFrom("job_quarantine").where("quarantined_at","is not",null).selectAll().execute()??[]}catch($){if(_5($))return Z5(),[];throw $}}var f8=!1;var C5=()=>{};function o($){let V=$?.numUpdatedRows;if(V===null||V===void 0)return 0;if(typeof V==="object")return Number(V.changes??0);return Number(V)}async function n5($,V){let Y=JSON.stringify({jobName:$,payload:V.payload||{},options:{queue:V.queue,tries:V.maxTries,timeout:V.timeout,backoff:V.backoff}}),{db:G}=await import("@stacksjs/database");await G.insertInto("jobs").values({queue:V.queue||"default",payload:Y,attempts:0,available_at:Y$(V.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}function Y$($){let V=Date.now();return Math.floor(V/1000+$)}var j={};w(j,{withEvents:()=>u8,onQueueEvent:()=>b8,getWorkerTracker:()=>F5,getQueueEvents:()=>e,getGlobalMetrics:()=>k5,emitQueueEvent:()=>u,QueueMetrics:()=>v5,QueueEvents:()=>q5,OnQueueEvent:()=>l8});import{log as I}from"@stacksjs/logging";class q5{handlers=new Map;wildcardHandlers=new Set;on($,V){if(!this.handlers.has($))this.handlers.set($,new Set);return this.handlers.get($).add(V),()=>{this.handlers.get($)?.delete(V)}}onAny($){return this.wildcardHandlers.add($),()=>{this.wildcardHandlers.delete($)}}once($,V){let Y=async(G)=>{this.handlers.get($)?.delete(Y),await V(G)};return this.on($,Y)}async emit($,V){let Y={...V,timestamp:Date.now()};this.logEvent($,Y);let G=this.handlers.get($);if(G)for(let X of G)try{await X(Y)}catch(K){I.error(`Error in queue event handler for ${$}:`,K)}for(let X of this.wildcardHandlers)try{await X($,Y)}catch(K){I.error("Error in wildcard queue event handler:",K)}}logEvent($,V){let Y=V.jobId?`[${V.jobId}]`:"",G=V.queueName?`on ${V.queueName}`:"";switch($){case"job:added":I.debug(`Job added ${Y} ${G}`);break;case"job:processing":I.debug(`Job processing ${Y} ${G}`);break;case"job:completed":I.info(`Job completed ${Y} ${G} in ${V.duration}ms`);break;case"job:failed":I.error(`Job failed ${Y} ${G}:`,V.error);break;case"job:retrying":I.warn(`Job retrying ${Y} ${G} (attempt ${V.attemptsMade})`);break;case"job:stalled":I.warn(`Job stalled ${Y} ${G}`);break;case"queue:error":I.error(`Queue error ${G}:`,V.error);break}}off($){this.handlers.delete($)}removeAllListeners(){this.handlers.clear(),this.wildcardHandlers.clear()}}function e(){if(!s5)s5=new q5;return s5}function b8($,V){let Y=e();if($==="*")return Y.onAny(V);return Y.on($,V)}function u($,V){return e().emit($,V)}function u8($,V){return async(...Y)=>{let G=Y[0]?.id||"unknown",X=Date.now();await u("job:processing",{jobId:G,queueName:$,data:Y[0]?.data});try{let K=await V(...Y);return await u("job:completed",{jobId:G,queueName:$,result:K,duration:Date.now()-X}),K}catch(K){throw await u("job:failed",{jobId:G,queueName:$,error:K,duration:Date.now()-X}),K}}}function l8($){return function(V,Y,G){let X=G.value;return e().on($,X),G}}class v5{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",(V)=>{this.jobCounts.completed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1);let Y=V.duration||0;if(this.completions.push({timestamp:Date.now(),duration:Y}),this.completions.length>1000)this.completions.shift()}),$.on("job:failed",(V)=>{if(this.jobCounts.failed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1),V.error){if(this.errors.push({error:V.error,timestamp:Date.now()}),this.errors.length>100)this.errors.shift()}}))}getThroughputPerMinute(){let $=Date.now()-60000;return this.completions.filter((Y)=>Y.timestamp>=$).length}getAverageProcessingTime(){let $=Date.now()-60000,V=this.completions.filter((Y)=>Y.timestamp>=$);if(V.length===0)return 0;return V.reduce((Y,G)=>Y+G.duration,0)/V.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 k5(){if(!t5)t5=new v5;return t5}class c8{workers=new Map;register($,V){this.workers.set($,{id:$,status:"idle",queue:V,processedCount:0,failedCount:0,lastActivityAt:new Date().toISOString(),startedAt:new Date().toISOString()})}markActive($){let V=this.workers.get($);if(V)V.status="active",V.lastActivityAt=new Date().toISOString()}markIdle($){let V=this.workers.get($);if(V)V.status="idle",V.lastActivityAt=new Date().toISOString()}recordCompletion($){let V=this.workers.get($);if(V)V.processedCount++,V.lastActivityAt=new Date().toISOString()}recordFailure($){let V=this.workers.get($);if(V)V.failedCount++,V.lastActivityAt=new Date().toISOString()}unregister($){let V=this.workers.get($);if(V)V.status="stopped"}getAll(){return Array.from(this.workers.values())}clear(){this.workers.clear()}}function F5(){return G$}var s5=null,t5=null,G$;var q=f(()=>{G$=new c8});var g={};w(g,{recordBatchJobFailure:()=>V8,recordBatchJobCompletion:()=>$8,isBatchCancelled:()=>$9,getBatchCallbacks:()=>H5,PendingBatch:()=>y5,DispatchedBatch:()=>h,Batch:()=>r5});import{log as B}from"@stacksjs/logging";import{env as X$}from"@stacksjs/env";function p(){return X$.QUEUE_DRIVER||"sync"}class y5{jobs;options={thenCallbacks:[],catchCallbacks:[],finallyCallbacks:[],progressCallbacks:[]};constructor($){this.jobs=$.map((V)=>("job"in V)?V:{job:V})}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(),V=this.jobs.length;if(V===0)throw Error("Cannot dispatch an empty batch");let Y=p();await z$({id:$,name:this.options.name||"",total_jobs:V,pending_jobs:V,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}),K$($,this.options);try{let{emitQueueEvent:G}=await Promise.resolve().then(() => (q(),j));await G("batch:added",{jobId:$,data:{name:this.options.name,totalJobs:V}})}catch{}for(let G=0;G<this.jobs.length;G++){let X=this.jobs[G];if(!X)continue;let{job:K,payload:z}=X,W={...z,_batchId:$,_batchIndex:G};if(this.options.queue&&!K.queue)K.queue=this.options.queue;if(Y==="sync")try{await K.dispatchNow(W),await $8($)}catch(L){await V8($,`${$}:${G}`,L)}else await K.dispatch(W)}return B.info(`[Batch] Dispatched batch "${this.options.name||$}" with ${V} jobs`),new h($)}getJobs(){return[...this.jobs]}getOptions(){return this.options}}class h{id;constructor($){this.id=$}async fresh(){return l(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 V=$.total_jobs-$.pending_jobs;return Math.round(V/$.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(p()==="redis")await F$(this.id);else await Z$(this.id);B.info(`[Batch] Cancelled batch ${this.id}`);let V=H5(this.id);if(V)for(let Y of V.finallyCallbacks)try{await Y(this)}catch(G){B.error(`[Batch] Error in finally callback for batch ${this.id}:`,G)}}async add($){let V=await this.fresh();if(!V)throw Error(`Batch ${this.id} not found`);if(V.cancelled_at)throw Error(`Batch ${this.id} has been cancelled`);if(V.finished_at)throw Error(`Batch ${this.id} has already finished`);let Y=$.map((z)=>("job"in z)?z:{job:z}),G=V.total_jobs+Y.length,X=V.pending_jobs+Y.length;await i8(this.id,{total_jobs:G,pending_jobs:X});let K=JSON.parse(V.options||"{}");for(let z=0;z<Y.length;z++){let W=Y[z];if(!W)continue;let{job:L,payload:U}=W,_={...U,_batchId:this.id,_batchIndex:V.total_jobs+z};if(K.queue&&!L.queue)L.queue=K.queue;await L.dispatch(_)}B.info(`[Batch] Added ${Y.length} jobs to batch ${this.id}`)}async delete(){await U$(this.id),I5(this.id)}}class r5{static create($){return new y5($)}static async find($){if(!await l($))return null;return new h($)}static async all(){return(await L$()).map((V)=>new h(V.id))}static async prune($=24){return W$($)}}function K$($,V){a5.set($,V)}function H5($){return a5.get($)}function I5($){a5.delete($)}async function z$($){if(p()==="redis")await _$($);else await d8($)}async function l($){if(p()==="redis")return O$($);return n8($)}async function L$(){if(p()==="redis")return a8();return s8()}async function i8($,V){if(p()==="redis")await o8($,V);else await o5($,V)}async function U$($){if(p()==="redis")await e8($);else await t8($)}async function W$($){if(p()==="redis")return H$($);return r8($)}async function d8($){let{db:V}=await import("@stacksjs/database");await V.insertInto("job_batches").values({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}).execute()}async function n8($){let{db:V}=await import("@stacksjs/database");return await V.selectFrom("job_batches").where("id","=",$).selectAll().executeTakeFirst()||null}async function s8(){let{db:$}=await import("@stacksjs/database");return await $.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function o5($,V){let{db:Y}=await import("@stacksjs/database");await Y.updateTable("job_batches").set(V).where("id","=",$).execute()}async function t8($){let{db:V}=await import("@stacksjs/database");await V.deleteFrom("job_batches").where("id","=",$).execute()}async function Z$($){await o5($,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function r8($){let{db:V}=await import("@stacksjs/database"),Y=new Date(Date.now()-$*60*60*1000).toISOString().slice(0,19).replace("T"," "),G=await V.deleteFrom("job_batches").where("finished_at","is not",null).where("finished_at","<",Y).executeTakeFirst();return Number(G?.numDeletedRows??0)}async function _$($){try{let{createClient:V}=await import("redis"),{queue:Y}=await import("@stacksjs/config"),G=Y?.connections?.redis?.redis,X=G?.url||`redis://${G?.host||"localhost"}:${G?.port||6379}`,K=V({url:X});await K.connect();let z=`${x5}${$.id}`;await K.hSet(z,{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||""}),await K.sAdd(e5,$.id),await K.quit()}catch{await d8($)}}async function O$($){try{let{createClient:V}=await import("redis"),{queue:Y}=await import("@stacksjs/config"),G=Y?.connections?.redis?.redis,X=G?.url||`redis://${G?.host||"localhost"}:${G?.port||6379}`,K=V({url:X});await K.connect();let z=`${x5}${$}`,W=await K.hGetAll(z);if(await K.quit(),!W||!W.id)return null;return{id:W.id,name:W.name,total_jobs:Number(W.total_jobs),pending_jobs:Number(W.pending_jobs),failed_jobs:Number(W.failed_jobs),failed_job_ids:W.failed_job_ids,options:W.options,cancelled_at:W.cancelled_at||null,created_at:W.created_at,finished_at:W.finished_at||null}}catch{return n8($)}}async function a8(){try{let{createClient:$}=await import("redis"),{queue:V}=await import("@stacksjs/config"),Y=V?.connections?.redis?.redis,G=Y?.url||`redis://${Y?.host||"localhost"}:${Y?.port||6379}`,X=$({url:G});await X.connect();let K=await X.sMembers(e5),z=[];for(let W of K){let L=`${x5}${W}`,U=await X.hGetAll(L);if(U?.id)z.push({id:U.id,name:U.name,total_jobs:Number(U.total_jobs),pending_jobs:Number(U.pending_jobs),failed_jobs:Number(U.failed_jobs),failed_job_ids:U.failed_job_ids,options:U.options,cancelled_at:U.cancelled_at||null,created_at:U.created_at,finished_at:U.finished_at||null})}return await X.quit(),z}catch{return s8()}}async function o8($,V){try{let{createClient:Y}=await import("redis"),{queue:G}=await import("@stacksjs/config"),X=G?.connections?.redis?.redis,K=X?.url||`redis://${X?.host||"localhost"}:${X?.port||6379}`,z=Y({url:K});await z.connect();let W=`${x5}${$}`,L={};for(let[U,_]of Object.entries(V))L[U]=_===null?"":String(_);await z.hSet(W,L),await z.quit()}catch{await o5($,V)}}async function e8($){try{let{createClient:V}=await import("redis"),{queue:Y}=await import("@stacksjs/config"),G=Y?.connections?.redis?.redis,X=G?.url||`redis://${G?.host||"localhost"}:${G?.port||6379}`,K=V({url:X});await K.connect(),await K.del(`${x5}${$}`),await K.sRem(e5,$),await K.quit()}catch{await t8($)}}async function F$($){let V=new Date().toISOString().slice(0,19).replace("T"," ");await o8($,{cancelled_at:V,finished_at:V})}async function H$($){try{let V=await a8(),Y=Date.now()-$*60*60*1000,G=0;for(let X of V)if(X.finished_at){if(new Date(X.finished_at).getTime()<Y)await e8(X.id),G++}return G}catch{return r8($)}}function $5($){if(!$)return null;try{let V=JSON.parse($);if(V&&(V.kind==="job"||V.kind==="module"))return V;return B.warn(`[Batch] handler JSON has unknown kind '${V?.kind}' \u2014 skipping`),null}catch(V){return B.warn(`[Batch] failed to parse persistent handler: ${V.message}`),null}}async function V5($,V){try{if($.kind==="job"){let{Jobs:X}=await Promise.resolve().then(() => (Q5(),w5));await X.dispatch($.name,{...$.payload??{},_batchId:V});return}let Y=await import($.module).catch((X)=>{return B.warn(`[Batch] persistent handler module not found: ${$.module} (${X.message})`),null});if(!Y)return;let G=Y[$.export];if(typeof G!=="function"){B.warn(`[Batch] persistent handler export '${$.export}' is not a function on ${$.module}`);return}await G($.payload,V)}catch(Y){B.error(`[Batch] persistent handler threw for batch ${V}:`,Y)}}async function $8($){let{db:V,sql:Y}=await import("@stacksjs/database");await V.updateTable("job_batches").set({pending_jobs:Y`GREATEST(pending_jobs - 1, 0)`}).where("id","=",$).where("pending_jobs",">",0).execute();let G=new Date().toISOString().slice(0,19).replace("T"," "),X=await V.updateTable("job_batches").set({finished_at:G}).where("id","=",$).where("pending_jobs","=",0).where("finished_at","is",null).executeTakeFirst(),K=o(X)>0,z=H5($),W=new h($);if(z)for(let L of z.progressCallbacks)try{await L(W)}catch(U){B.error(`[Batch] Error in progress callback for batch ${$}:`,U)}if(K){try{let{emitQueueEvent:O}=await Promise.resolve().then(() => (q(),j));await O("batch:completed",{jobId:$})}catch{}let L=await l($);if(!L){I5($),B.info(`[Batch] Batch ${$} finished (record vanished)`);return}let U=JSON.parse(L.options||"{}"),Q=!((L.failed_jobs||0)>0)||U.allowFailures;if(z){if(Q)for(let O of z.thenCallbacks)try{await O(W)}catch(Z){B.error(`[Batch] Error in then callback for batch ${$}:`,Z)}for(let O of z.finallyCallbacks)try{await O(W)}catch(Z){B.error(`[Batch] Error in finally callback for batch ${$}:`,Z)}I5($)}if(Q){let O=$5(L.then_handler);if(O)await V5(O,$)}else{let O=$5(L.catch_handler);if(O)await V5(O,$)}let P=$5(L.finally_handler);if(P)await V5(P,$);B.info(`[Batch] Batch ${$} finished`)}}async function V8($,V,Y){let{db:G,sql:X}=await import("@stacksjs/database"),K=await l($);if(!K)return;let z=JSON.parse(K.options||"{}");await G.updateTable("job_batches").set({pending_jobs:X`GREATEST(pending_jobs - 1, 0)`,failed_jobs:X`failed_jobs + 1`}).where("id","=",$).where("pending_jobs",">",0).execute();try{let Z=await l($);if(Z){let H=[];try{H=JSON.parse(Z.failed_job_ids||"[]")}catch{H=[]}H.push(V),await i8($,{failed_job_ids:JSON.stringify(H)})}}catch{}let W=new h($),L=H5($);if(L)for(let Z of L.catchCallbacks)try{await Z(W,Y)}catch(H){B.error(`[Batch] Error in catch callback for batch ${$}:`,H)}try{let{emitQueueEvent:Z}=await Promise.resolve().then(() => (q(),j));await Z("batch:failed",{jobId:$,error:Y})}catch{}let U=new Date().toISOString().slice(0,19).replace("T"," "),_=G.updateTable("job_batches").set(z.allowFailures?{finished_at:U}:{finished_at:U,cancelled_at:U}).where("id","=",$).where("finished_at","is",null);if(z.allowFailures)_=_.where("pending_jobs","=",0);let Q=await _.executeTakeFirst();if(o(Q)===0)return;let P=!!z.allowFailures;if(L){if(P)for(let Z of L.thenCallbacks)try{await Z(W)}catch(H){B.error(`[Batch] Error in then callback for batch ${$}:`,H)}for(let Z of L.finallyCallbacks)try{await Z(W)}catch(H){B.error(`[Batch] Error in finally callback for batch ${$}:`,H)}I5($)}let O=await l($);if(O){if(P){let H=$5(O.then_handler);if(H)await V5(H,$)}else{let H=$5(O.catch_handler);if(H)await V5(H,$)}let Z=$5(O.finally_handler);if(Z)await V5(Z,$)}B.info(`[Batch] Batch ${$} finished with failure(s)`)}async function $9($){return(await l($))?.cancelled_at!==null}var a5,x5="stacks:batch:",e5="stacks:batches";var y=f(()=>{a5=new Map});var w5={};w(w5,{runJob:()=>f5,jobBatch:()=>X9,job:()=>Y9,Jobs:()=>G9});import{appPath as x$}from"@stacksjs/path";import{env as Q$}from"@stacksjs/env";import{enqueueAfterCommit as A$,isInTransaction as D$}from"@stacksjs/database";function P$(){return Q$.QUEUE_DRIVER||"sync"}function M$(){if(V9)return;V9=!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 v{name;payload;options={};txMode="auto";constructor($,V){this.name=$;this.payload=V}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:V}=await Promise.resolve().then(() => b5);if($()){V()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(D$()){let G=this.runDispatchPipeline.bind(this);if(A$(async()=>{await G()}))return}else if(this.txMode==="after")M$()}await this.runDispatchPipeline()}async runDispatchPipeline(){let $=!1;if(this.options.idempotencyKey){let V=await i5(this.options.idempotencyKey,this.name,this.options.queue);if(V==="duplicate")return;$=V==="claimed"}try{if(await S5(this.name,this.payload)){let Y=m(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await N5({queue:this.options.queue||"default",payload:JSON.stringify(Y),exception:`quarantined: ${this.name}`},"poison-detected"))return}let V=P$();if(V==="database")await this.dispatchToDatabase();else if(V==="redis")await this.dispatchToRedis();else if(V==="sync")await f5(this.name,{payload:this.payload,context:this.options.context});else if(V==="sqs"||V==="memory"||V==="beanstalkd")throw Error(`[queue] Driver "${V}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);else throw Error(`[queue] Unknown QUEUE_DRIVER "${V}". Allowed values: redis, database, sync.`)}catch(V){if($&&this.options.idempotencyKey)await d5(this.options.idempotencyKey);throw V}}async dispatchIf($){if($)await this.dispatch()}async dispatchUnless($){if(!$)await this.dispatch()}async dispatchToDatabase(){let $=Math.floor(Date.now()/1000),V=this.options.delay?$+this.options.delay:$,Y=m(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff}),{db:G}=await import("@stacksjs/database");await G.insertInto("jobs").values({queue:this.options.queue||"default",payload:JSON.stringify(Y),attempts:0,reserved_at:null,available_at:V,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){let{RedisQueue:$}=await Promise.resolve().then(() => (W5(),U5)),{queue:V}=await import("@stacksjs/config"),Y=V?.connections?.redis;if(!Y)throw Error("Redis queue connection is not configured. Check config/queue.ts");let G=new $(this.options.queue||"default",Y),X=m(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});await G.add(X,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await f5(this.name,{payload:this.payload,context:this.options.context})}}function Y9($,V){return new v($,V)}function X9($){let{PendingBatch:V}=(y(),j9(g));return new V($)}async function f5($,V={}){let{withTraceId:Y}=await import("@stacksjs/router"),G=V.traceId??`job:${$}:${Math.random().toString(36).slice(2,10)}`;await Y(G,async()=>{let z=(await import(x$(`Jobs/${$}.ts`))).default;if(!z)throw Error(`Job ${$} does not export a default`);if(typeof z.handle==="function")await z.handle(V.payload);else if(typeof z.action==="string"){let{runAction:W}=await import("@stacksjs/actions");await W(z.action)}else if(typeof z.action==="function")await z.action();else if(typeof z==="function")await z(V.payload,V.context);else throw Error(`Job ${$} does not have a valid handler`)})}var V9=!1,G9;var Q5=f(()=>{J5();K5();C5();G9={make($,V){return new v($,V)},async dispatch($,V){await new v($,V).dispatch()},async dispatchIf($,V,Y){if($)await new v(V,Y).dispatch()},async dispatchUnless($,V,Y){if(!$)await new v(V,Y).dispatch()},async dispatchNow($,V){await new v($,V).dispatchNow()},dispatchAfter($,V,Y){return new v(V,Y).delay($)},async dispatchOnce($,V,Y){await new v(V,Y).withIdempotencyKey($).dispatch()},async dispatchAfterCommit($,V){await new v($,V).afterCommit().dispatch()}}});var m5={};w(m5,{resumeQueue:()=>Z9,recordCircuitSuccess:()=>L9,recordCircuitFailure:()=>U9,pauseQueue:()=>W9,listCircuitState:()=>_9,isCircuitOpen:()=>z9});import{db as N}from"@stacksjs/database";function c(){if(K9)return;K9=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing \u2014 circuit breaker disabled. "+"Run migrations to enable.")}function i($){let V=$?.message??"";return V.includes("no such table")||V.includes("doesn't exist")}async function G8($,V){try{let Y=await N.selectFrom("queue_circuit_state").where("queue_name","=",$).selectAll().executeTakeFirst();if(Y)return Y;return await N.insertInto("queue_circuit_state").values({queue_name:$,success_count:0,failure_count:0,window_start:V,paused_at:null,resume_at:null}).execute(),{queue_name:$,success_count:0,failure_count:0,window_start:V,paused_at:null,resume_at:null}}catch(Y){if(i(Y))return c(),null;let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return await N.selectFrom("queue_circuit_state").where("queue_name","=",$).selectAll().executeTakeFirst();throw Y}}async function z9($){try{let V=await N.selectFrom("queue_circuit_state").where("queue_name","=",$).select(["paused_at","resume_at"]).executeTakeFirst();if(!V||!V.paused_at)return!1;if(V.resume_at){let Y=Date.parse(V.resume_at.replace(" ","T")+"Z");if(Number.isFinite(Y)&&Date.now()>=Y)return await N.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(V){if(i(V))return c(),!1;throw V}}async function L9($,V={}){let Y=V.windowSeconds??300,G=new Date().toISOString().slice(0,19).replace("T"," "),X=await G8($,G);if(!X)return;try{if(Y8(X.window_start,Y)){await N.updateTable("queue_circuit_state").set({success_count:1,failure_count:0,window_start:G}).where("queue_name","=",$).execute();return}await N.updateTable("queue_circuit_state").set({success_count:X.success_count+1}).where("queue_name","=",$).execute()}catch(K){if(i(K)){c();return}throw K}}async function U9($,V={}){let Y=V.failureRateThreshold??0.5,G=V.windowSeconds??300,X=V.pauseSeconds??300,K=V.minObservations??10,z=new Date,W=z.toISOString().slice(0,19).replace("T"," "),L=await G8($,W);if(!L)return!1;if(L.paused_at)return!1;try{let{success_count:U,failure_count:_}=L;if(Y8(L.window_start,G))U=0,_=0;_+=1;let Q=U+_,P=Q===0?0:_/Q;if(Q>=K&&P>=Y){let Z=new Date(z.getTime()+X*1000).toISOString().slice(0,19).replace("T"," ");return await N.updateTable("queue_circuit_state").set({success_count:U,failure_count:_,window_start:W,paused_at:W,resume_at:Z}).where("queue_name","=",$).execute(),!0}return await N.updateTable("queue_circuit_state").set({success_count:U,failure_count:_,window_start:Y8(L.window_start,G)?W:L.window_start}).where("queue_name","=",$).execute(),!1}catch(U){if(i(U))return c(),!1;throw U}}function Y8($,V){if(!$)return!0;let Y=Date.parse($.replace(" ","T")+"Z");if(!Number.isFinite(Y))return!0;return Date.now()-Y>V*1000}async function W9($,V=300){let Y=new Date,G=Y.toISOString().slice(0,19).replace("T"," "),X=new Date(Y.getTime()+V*1000).toISOString().slice(0,19).replace("T"," ");await G8($,G);try{await N.updateTable("queue_circuit_state").set({paused_at:G,resume_at:X}).where("queue_name","=",$).execute()}catch(K){if(i(K)){c();return}throw K}}async function Z9($){try{await N.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",$).execute()}catch(V){if(i(V)){c();return}throw V}}async function _9(){try{return await N.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch($){if(i($))return c(),[];throw $}}var K9=!1;var A5=()=>{};K5();import{env as o9}from"@stacksjs/env";function S8(){return o9.QUEUE_DRIVER||"sync"}class C8{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:V,getFakeQueue:Y}=await Promise.resolve().then(() => b5);if(V()){Y()?.dispatch(this.name||"UnknownJob",$,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let G=S8();if(G==="sync")return this.dispatchNow($);if(G==="redis")return this.dispatchToRedis($);if(G==="database")return this.dispatchToDatabase($);if(G==="sqs"||G==="memory"||G==="beanstalkd")throw Error(`[queue] Driver "${G}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);throw Error(`[queue] Unknown QUEUE_DRIVER "${G}". Allowed values: redis, database, sync.`)}async dispatchIf($,V){if($)return this.dispatch(V)}async dispatchUnless($,V){if(!$)return this.dispatch(V)}async dispatchAfter($,V){let Y=S8();if(Y==="redis")return this.dispatchToRedis(V,{delay:$});if(Y==="database")return this.dispatchToDatabase(V,{delay:$});return await new Promise((G)=>setTimeout(G,$*1000)),await this.dispatchNow(V)}async dispatchNow($){if(typeof this.handle==="function")await this.handle($);else if(typeof this.action==="string"){let{runAction:V}=await import("@stacksjs/actions");await V(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($,V){let Y=Math.floor(Date.now()/1000),G=V?.delay?Y+V.delay:Y,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}),{db:K}=await import("@stacksjs/database");await K.insertInto("jobs").values({queue:this.queue||"default",payload:JSON.stringify(X),attempts:0,reserved_at:null,available_at:G,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis($,V){let{RedisQueue:Y}=await Promise.resolve().then(() => (W5(),U5)),{queue:G}=await import("@stacksjs/config"),X=G?.connections?.redis;if(!X)throw Error("Redis queue connection is not configured. Check config/queue.ts");let K=new Y(this.queue||"default",X),z=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 K.add(z,{delay:V?.delay,maxTries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0})}}Q5();J5();C5();A5();K5();var X8=($)=>`__job_progress__:${$}`,K8=($)=>`__job_cancel__:${$}`;function B$($){return Number.isFinite($)?Math.max(0,Math.min(100,$)):0}async function T$($,V,Y){let{cache:G}=await import("@stacksjs/cache");await G.set(X8($),{percent:B$(V),message:Y,updatedAt:Date.now()},3600)}async function E$($){let{cache:V}=await import("@stacksjs/cache");return await V.get(X8($))??null}async function R$($){let{cache:V}=await import("@stacksjs/cache");await V.set(K8($),1,3600)}async function N$($){let{cache:V}=await import("@stacksjs/cache");return Boolean(await V.get(K8($)))}async function J$($){let{cache:V}=await import("@stacksjs/cache");await Promise.all([V.del(X8($)),V.del(K8($))])}import{log as Y5}from"@stacksjs/logging";import*as O9 from"@stacksjs/path";class F9{jobs=new Map;initialized=!1;register($){this.jobs.set($.name,$),Y5.debug(`Registered job: ${$.name} (${$.type})`)}get($){return this.jobs.get($)}all(){return Array.from(this.jobs.values())}byQueue($){return this.all().filter((V)=>V.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 d=new F9;async function z8($){let V=$||O9.userJobsPath(),Y=[];try{let G=new Bun.Glob("**/*.{ts,js}"),X={cwd:V,onlyFiles:!0,absolute:!0};for await(let K of G.scan(X)){if(K.includes(".test.")||K.includes(".spec.")||K.endsWith("index.ts")||K.endsWith("index.js"))continue;try{let z=await S$(K);if(z)Y.push(z),d.register(z)}catch(z){Y5.warn(`Failed to load job from ${K}: ${z.message}`)}}return d.setInitialized(!0),Y5.info(`Discovered ${Y.length} jobs from ${V}`),Y}catch(G){return Y5.error(`Failed to discover jobs: ${G.message}`),[]}}async function S$($){try{let V=await import($),Y=$.split("/").pop()?.replace(/\.(ts|js)$/,"")||"UnknownJob";if(V.default&&typeof V.default==="function"){let G=V.default;if(typeof G.handle==="function"||typeof G.prototype?.handle==="function"){let X=G.config||{};return{name:X.name||Y,path:$,config:{name:X.name||Y,description:X.description,queue:X.queue||"default",tries:X.retries||3,timeout:X.timeout,withoutOverlapping:X.withoutOverlapping,schedule:X.schedule,retryAfter:X.retryAfter},type:"class",module:G}}}if(V.default&&typeof V.default==="object"){let G=V.default;if(typeof G.handle==="function"||typeof G.action==="string")return{name:G.name||Y,path:$,config:{name:G.name||Y,description:G.description,queue:G.queue||"default",tries:G.tries||3,backoff:G.backoff,rate:G.rate,timeout:G.timeout||G.timeOut,backoffConfig:G.backoffConfig},type:"function",module:G}}return null}catch(V){return Y5.debug(`Could not load job from ${$}: ${V.message}`),null}}function C$($){return d.get($)}function q$(){return d.all()}function L8(){return d.scheduled()}async function v$($,V){let Y=d.get($);if(!Y)throw Error(`Job "${$}" not found. Did you run discoverJobs()?`);try{if(Y.type==="class"){if(typeof Y.module.handle==="function")return await Y.module.handle(V);return await new Y.module().handle(V)}else{if(typeof Y.module.handle==="function")return await Y.module.handle(V);throw Error(`Job "${$}" does not have a handle method`)}}catch(G){throw Y5.error(`Failed to execute job "${$}": ${G.message}`),G}}function k$($){let V=$.config;return{name:V.name,queue:V.queue,tries:V.tries,backoff:V.backoff,timeout:V.timeout,backoffConfig:V.backoffConfig,rate:V.rate}}import{log as T}from"@stacksjs/logging";q();import{log as I$}from"@stacksjs/logging";var H9=!1;async function x9(){if(H9)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(),H9=!0,!0}catch($){return I$.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${$ instanceof Error?$.message:String($)}`),!1}}async function Q9($){if(!await x9())return null;try{let{db:V}=await import("@stacksjs/database"),Y=await V.selectFrom("scheduled_job_runs").where("job_name","=",$).select(["last_run_at"]).executeTakeFirst();if(!Y?.last_run_at)return null;let G=new Date(Y.last_run_at);return Number.isNaN(G.getTime())?null:G}catch{return null}}async function A9($,V){if(!await x9())return;try{let{db:Y}=await import("@stacksjs/database"),G=V.toISOString();await Y.deleteFrom("scheduled_job_runs").where("job_name","=",$).execute(),await Y.insertInto("scheduled_job_runs").values({job_name:$,last_run_at:G}).execute()}catch{}}var T9={checkInterval:60000,preventOverlapping:!0},D={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...T9}},D9=new Set;function y$($,V){if(D9.has($))return;D9.add($),T.warn(`[scheduler] Cron expression "${$}" specifies seconds="${V}" 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 P9=!1,M9=new Map;function w$($){let V=M9.get($);if(!V)V=new Intl.DateTimeFormat("en-US",{timeZone:$,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),M9.set($,V);return V}function U8($,V){if(V&&V!=="local"&&V!=="system")try{let Y=w$(V).formatToParts($),G=(z)=>Y.find((W)=>W.type===z)?.value??"",X={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},K=Number(G("hour"));if(K===24)K=0;return{minute:Number(G("minute")),hour:K,day:Number(G("day")),month:Number(G("month")),dayOfWeek:X[G("weekday")]??$.getDay()}}catch{if(!P9)P9=!0,T.warn(`[scheduler] Invalid timezone "${V}"; falling back to system local time.`)}return{minute:$.getMinutes(),hour:$.getHours(),day:$.getDate(),month:$.getMonth()+1,dayOfWeek:$.getDay()}}function f$($,V,Y){let G=U8(new Date,Y),X=G.minute,K=G.hour,z=G.day,W=G.month,L=G.dayOfWeek;if(V){let H=U8(V,Y);if(H.minute===X&&H.hour===K&&H.day===z)return!1}let U=$.trim().split(/\s+/);if(U.length===6){let H=U[0];if(H&&H!=="0"&&H!=="*")y$($,H);U=U.slice(1)}if(U.length<5)return T.warn(`Invalid cron expression: ${$}`),!1;let[_,Q,P,O,Z]=U;return k(_,X,0,59)&&k(Q,K,0,23)&&k(P,z,1,31)&&k(O,W,1,12)&&k(Z,L,0,6)}function k($,V,Y,G){if($==="*")return!0;if($.includes(","))return $.split(",").map((z)=>Number.parseInt(z.trim(),10)).includes(V);if($.includes("-")){let[K,z]=$.split("-").map((W)=>Number.parseInt(W.trim(),10));return V>=K&&V<=z}if($.includes("/")){let[K,z]=$.split("/"),W=Number.parseInt(z,10);if(K==="*")return V%W===0;if(K.includes("-")){let[L,U]=K.split("-").map((_)=>Number.parseInt(_.trim(),10));return V>=L&&V<=U&&(V-L)%W===0}}let X=Number.parseInt($,10);return!Number.isNaN(X)&&V===X}function E9($){let Y={"@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(Y)return Y;let G=$.match(/^Every\.(\w+)$/i);if(G&&G[1]!==void 0){let K=G[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 * *"}[K]||null}let X=$.split(/\s+/).length;if(X>=5&&X<=6)return $;return null}function R9($,V){let Y=$.trim().split(/\s+/),G=Y.length===6?Y.slice(1):Y;if(G.length<5)return null;let[X,K,z,W,L]=G,U=new Date;U.setSeconds(0,0);let _=527040;for(let Q=1;Q<=_;Q++){let P=new Date(U.getTime()+Q*60000),O=U8(P,V);if(k(X,O.minute,0,59)&&k(K,O.hour,0,23)&&k(z,O.day,1,31)&&k(W,O.month,1,12)&&k(L,O.dayOfWeek,0,6))return P}return null}async function m$($={}){if(D.isRunning){T.warn("Scheduler is already running");return}D.config={...T9,...$},D.isRunning=!0,D.isShuttingDown=!1,await z8();let V=L8();for(let X of V){let K=X.config.rate||X.config.schedule;if(K){let z=E9(K);if(z){let W=await Q9(X.name);D.jobs.set(X.name,{job:X,lastRun:W,nextRun:R9(z,D.config.timezone),isRunning:!1}),T.info(`Registered scheduled job: ${X.name} (${z})`)}else T.warn(`Invalid schedule for job ${X.name}: ${K}`)}}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",()=>W8()),process.on("SIGTERM",()=>W8());let Y=!1,G=()=>{if(D.isShuttingDown)return;let X=D.config.checkInterval,K=X-Date.now()%X;D.checkInterval=setTimeout(()=>{if(!D.isShuttingDown&&!Y)Y=!0,B9().catch((z)=>T.error("Scheduler check failed:",z)).finally(()=>{Y=!1});G()},K),D.checkInterval?.unref?.()};G(),await B9()}async function B9(){for(let[$,V]of D.jobs){let Y=V.job.config.rate||V.job.config.schedule;if(!Y)continue;let G=E9(Y);if(!G)continue;if(f$(G,V.lastRun,D.config.timezone)){if(D.config.preventOverlapping&&V.isRunning){T.debug(`Skipping ${$}: previous execution still running`);continue}if(V.job.config.withoutOverlapping&&V.isRunning){T.debug(`Skipping ${$}: withoutOverlapping is enabled`);continue}try{V.isRunning=!0,V.lastRun=new Date,V.nextRun=R9(G,D.config.timezone),await A9($,V.lastRun),T.info(`Dispatching scheduled job: ${$}`),await u("job:added",{jobId:`scheduled-${$}-${Date.now()}`,queueName:V.job.config.queue||"default",jobName:$}),await n5($,{queue:V.job.config.queue||"default",payload:{},maxTries:V.job.config.tries||3,timeout:V.job.config.timeout||60}),V.isRunning=!1,T.info(`Scheduled job ${$} dispatched to queue`)}catch(X){V.isRunning=!1,T.error(`Failed to dispatch scheduled job ${$}:`,X)}}}}async function W8(){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 j$(){return{isRunning:D.isRunning,jobCount:D.jobs.size,jobs:Array.from(D.jobs.entries()).map(([$,V])=>({name:$,schedule:V.job.config.rate||V.job.config.schedule,lastRun:V.lastRun,nextRun:V.nextRun,isRunning:V.isRunning}))}}function h$(){return D.isRunning}function p$(){return new Map(D.jobs)}async function g$($){let V=D.jobs.get($);if(!V)throw Error(`Scheduled job "${$}" not found`);T.info(`Manually triggering scheduled job: ${$}`),await n5($,{queue:V.job.config.queue||"default",payload:{},maxTries:V.job.config.tries||3,timeout:V.job.config.timeout||60})}q();q();import{log as b$}from"@stacksjs/logging";var u$={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function Z8($={}){let V={...u$,...$},Y=[],G=new Date,X=Math.floor(G.getTime()/1000);try{let{db:K}=await import("@stacksjs/database"),z=await K.selectFrom("jobs").selectAll().execute(),W=await K.selectFrom("failed_jobs").selectAll().execute(),L=new Map;for(let x of z){let M=x.queue||"default";if(!L.has(M))L.set(M,{pending:0,processing:0,delayed:0});let R=L.get(M);if(x.reserved_at)R.processing++;else if(x.available_at&&x.available_at>X)R.delayed++;else if(R.pending++,x.created_at){let C=typeof x.created_at==="number"?x.created_at:Math.floor(new Date(x.created_at).getTime()/1000),Q8=X-C;if(!R.oldestAge||Q8>R.oldestAge)R.oldestAge=Q8}}let U=new Map;for(let x of W){let M=x.queue||"default";U.set(M,(U.get(M)||0)+1)}let _=[],Q=new Set([...L.keys(),...U.keys()]),P=V.queues?[...Q].filter((x)=>V.queues.includes(x)):[...Q],O=0,Z=0,H=0,s=0;for(let x of P){let M=L.get(x)||{pending:0,processing:0,delayed:0},R=U.get(x)||0;O+=M.pending,Z+=M.processing,H+=M.delayed,s+=R;let C="healthy";if(M.pending>=V.maxPendingCritical)C="unhealthy",Y.push({level:"critical",message:`Queue "${x}" has ${M.pending} pending jobs (threshold: ${V.maxPendingCritical})`,queue:x,timestamp:G.toISOString()});else if(M.pending>=V.maxPendingWarning)C="degraded",Y.push({level:"warning",message:`Queue "${x}" has ${M.pending} pending jobs (threshold: ${V.maxPendingWarning})`,queue:x,timestamp:G.toISOString()});if(R>=V.maxFailedCritical)C="unhealthy",Y.push({level:"critical",message:`Queue "${x}" has ${R} failed jobs (threshold: ${V.maxFailedCritical})`,queue:x,timestamp:G.toISOString()});else if(R>=V.maxFailedWarning){if(C==="healthy")C="degraded";Y.push({level:"warning",message:`Queue "${x}" has ${R} failed jobs (threshold: ${V.maxFailedWarning})`,queue:x,timestamp:G.toISOString()})}if(M.oldestAge){if(M.oldestAge>=V.maxJobAgeCritical)C="unhealthy",Y.push({level:"critical",message:`Queue "${x}" has a job waiting for ${Math.floor(M.oldestAge/3600)} hours`,queue:x,timestamp:G.toISOString()});else if(M.oldestAge>=V.maxJobAgeWarning){if(C==="healthy")C="degraded";Y.push({level:"warning",message:`Queue "${x}" has a job waiting for ${Math.floor(M.oldestAge/60)} minutes`,queue:x,timestamp:G.toISOString()})}}_.push({name:x,status:C,pending:M.pending,processing:M.processing,delayed:M.delayed,failed:R,oldestJobAge:M.oldestAge})}let H8=k5().getMetrics(),v9=H8.throughputPerMinute,k9=H8.averageDuration,x8=O+Z+H+s,X5=x8>0?s/x8:0,t="healthy";if(_.some((x)=>x.status==="unhealthy"))t="unhealthy";else if(_.some((x)=>x.status==="degraded"))t="degraded";if(X5>=V.maxErrorRateCritical)t="unhealthy",Y.push({level:"critical",message:`Overall error rate is ${(X5*100).toFixed(1)}% (threshold: ${V.maxErrorRateCritical*100}%)`,timestamp:G.toISOString()});else if(X5>=V.maxErrorRateWarning){if(t==="healthy")t="degraded";Y.push({level:"warning",message:`Overall error rate is ${(X5*100).toFixed(1)}% (threshold: ${V.maxErrorRateWarning*100}%)`,timestamp:G.toISOString()})}let I9=F5().getAll().map((x)=>({id:x.id,status:x.status,queue:x.queue,processedCount:x.processedCount,failedCount:x.failedCount,lastActivityAt:x.lastActivityAt}));return{status:t,timestamp:G.toISOString(),queues:_,workers:I9,metrics:{totalPending:O,totalProcessing:Z,totalDelayed:H,totalFailed:s,throughputPerMinute:v9,averageProcessingTime:k9,errorRate:X5},alerts:Y}}catch(K){b$.error("Failed to perform queue health check:",K);let W=F5().getAll().map((L)=>({id:L.id,status:L.status,queue:L.queue,processedCount:L.processedCount,failedCount:L.failedCount,lastActivityAt:L.lastActivityAt}));return{status:"unhealthy",timestamp:G.toISOString(),queues:[],workers:W,metrics:{totalPending:0,totalProcessing:0,totalDelayed:0,totalFailed:0,throughputPerMinute:0,averageProcessingTime:0,errorRate:0},alerts:[{level:"critical",message:`Health check failed: ${K.message}`,timestamp:G.toISOString()}]}}}function l$($={}){return async(V)=>{let Y=await Z8($),G=Y.status==="healthy"?200:Y.status==="degraded"?207:503;return new Response(JSON.stringify(Y,null,2),{status:G,headers:{"Content-Type":"application/json","Cache-Control":"no-store"}})}}async function c$($={}){return(await Z8($)).status==="healthy"}import{log as S}from"@stacksjs/logging";var i$={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function D5($){return String($).replace(/[&<>"']/g,(V)=>i$[V]??V)}class _8{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 V=Date.now();if(V-this.lastResetTime>3600000)this.notificationCount=0,this.lastResetTime=V;if(this.notificationCount>=this.config.rateLimit){S.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((V)=>S.error("Failed to flush notification batch:",V)).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(($)=>S.error("Failed to flush notification batch on shutdown:",$))}async sendNotifications($){let V=[];for(let Y of this.config.channels)switch(Y){case"email":if(this.config.email)V.push(this.sendEmail($));break;case"slack":if(this.config.slack)V.push(this.sendSlack($));break;case"discord":if(this.config.discord)V.push(this.sendDiscord($));break;case"webhook":if(this.config.webhook)V.push(this.sendWebhook($));break;case"log":this.logFailures($);break}try{await Promise.all(V)}catch(Y){S.error("Failed to send job failure notifications:",Y)}finally{this.notificationCount+=$.length}}async sendEmail($){let V=this.config.email,Y=V.subject||`[Queue] ${$.length} Job(s) Failed`,G=this.formatEmailBody($);try{let{mail:X}=await import("@stacksjs/email");await X.send({to:Array.isArray(V.to)?V.to:[V.to],from:V.from,subject:Y,html:G})}catch(X){S.error("Failed to send email notification:",X)}}formatEmailBody($){let V=$.map((Y)=>`
2
+ var{defineProperty:h5,getOwnPropertyNames:f9,getOwnPropertyDescriptor:m9}=Object,j9=Object.prototype.hasOwnProperty;function h9($){return this[$]}var p9=($)=>{var V=(D8??=new WeakMap).get($),Y;if(V)return V;if(V=h5({},"__esModule",{value:!0}),$&&typeof $==="object"||typeof $==="function"){for(var G of f9($))if(!j9.call(V,G))h5(V,G,{get:h9.bind($,G),enumerable:!(Y=m9($,G))||Y.enumerable})}return D8.set($,V),V},D8;var g9=($)=>$;function u9($,V){this[$]=g9.bind(null,V)}var w=($,V)=>{for(var Y in V)h5($,Y,{get:V[Y],enumerable:!0,configurable:!0,set:u9.bind(V,Y)})};var f=($,V)=>()=>($&&(V=$($=0)),V);var F=import.meta.require;function m($,V,Y){return{jobName:$,payload:V,options:Y,envelopeVersion:1,dispatchedAt:new Date().toISOString()}}function P8($,V){if(p5.has($))return;p5.add($),console.warn(V)}function l9(){p5.clear()}function T5($){let V;if(typeof $==="string")try{V=JSON.parse($)}catch{return{ok:!1,reason:"malformed",detail:"not valid JSON"}}else if($&&typeof $==="object")V=$;else return{ok:!1,reason:"malformed",detail:`expected string or object, got ${typeof $}`};if(V.envelopeVersion===1){if(typeof V.jobName!=="string")return{ok:!1,reason:"missing-job-name"};return{ok:!0,envelope:{jobName:V.jobName,payload:V.payload,options:V.options??void 0,envelopeVersion:1,dispatchedAt:typeof V.dispatchedAt==="string"?V.dispatchedAt:new Date(0).toISOString()},source:"v1"}}if(typeof V.envelopeVersion==="number"&&V.envelopeVersion>1)return{ok:!1,reason:"unknown-version",detail:`envelopeVersion=${V.envelopeVersion}, this worker speaks v1`};if(typeof V.jobName==="string")return P8("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:V.jobName,payload:V.payload,options:V.options??void 0,envelopeVersion:1,dispatchedAt:typeof V.dispatchedAt==="string"?V.dispatchedAt:new Date(0).toISOString()},source:"v0-implicit"};if(typeof V.job==="string")return P8("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:V.job.replace(/^App\\+Jobs\\+/,"").replace(/^.*\\/,""),payload:V.data,options:void 0,envelopeVersion:1,dispatchedAt:new Date(0).toISOString()},source:"laravel-legacy"};return{ok:!1,reason:"missing-job-name"}}var b9=1,p5;var K5=f(()=>{p5=new Set});var b5={};w(b5,{runJob:()=>N8,restore:()=>u5,isFaked:()=>T8,getFakeQueue:()=>B8,fake:()=>g5,expectJobToFail:()=>J8,createQueueTester:()=>E8,QueueTester:()=>E5});class M8{dispatchedJobs=[];pushedJobs=[];processedJobs=[];failedJobs=[];dispatch($,V,Y={}){this.dispatchedJobs.push({name:$,data:V,options:Y,dispatchedAt:new Date,queue:Y.queue||"default"})}push($,V,Y={}){this.pushedJobs.push({name:$,data:V,options:Y,dispatchedAt:new Date,queue:Y.queue||"default"})}dispatched($){if($)return this.dispatchedJobs.filter((V)=>V.name===$);return[...this.dispatchedJobs]}pushed($){if($)return this.pushedJobs.filter((V)=>V.name===$);return[...this.pushedJobs]}assertDispatched($,V){let Y=this.dispatched($);if(Y.length===0)throw Error(`Expected job "${$}" to be dispatched, but it was not.`);if(V){if(Y.filter(V).length===0)throw Error(`Expected job "${$}" to be dispatched matching the callback, but no matching jobs were found.`)}}assertNotDispatched($){let V=this.dispatched($);if(V.length>0)throw Error(`Expected job "${$}" to not be dispatched, but it was dispatched ${V.length} time(s).`)}assertDispatchedTimes($,V){let Y=this.dispatched($);if(Y.length!==V)throw Error(`Expected job "${$}" to be dispatched ${V} time(s), but it was dispatched ${Y.length} time(s).`)}assertNothingDispatched(){if(this.dispatchedJobs.length>0){let $=[...new Set(this.dispatchedJobs.map((V)=>V.name))].join(", ");throw Error(`Expected no jobs to be dispatched, but found: ${$}`)}}assertPushed($,V){let Y=this.pushed($);if(Y.length===0)throw Error(`Expected job "${$}" to be pushed, but it was not.`);if(V){if(Y.filter(V).length===0)throw Error(`Expected job "${$}" to be pushed matching the callback, but no matching jobs were found.`)}}assertPushedWithDelay($,V){if(this.pushed($).filter((X)=>X.options.delay===V).length===0)throw Error(`Expected job "${$}" to be pushed with delay ${V}ms, but no matching jobs were found.`)}assertPushedOn($,V){if(this.pushed(V).filter((G)=>G.queue===$).length===0)throw Error(`Expected job "${V}" to be pushed on queue "${$}", but it was not.`)}async processJob($,V){let Y=this.dispatchedJobs.find((G)=>G.name===$);if(!Y)throw Error(`No dispatched job found with name "${$}"`);try{await V(Y.data),this.processedJobs.push(Y)}catch(G){throw this.failedJobs.push({job:Y,error:G}),G}}processed($){if($)return this.processedJobs.filter((V)=>V.name===$);return[...this.processedJobs]}failed($){if($)return this.failedJobs.filter((V)=>V.job.name===$);return[...this.failedJobs]}reset(){this.dispatchedJobs=[],this.pushedJobs=[],this.processedJobs=[],this.failedJobs=[]}}function g5(){return z5=new M8,z5}function B8(){return z5}function T8(){return z5!==null}function u5(){z5=null}class E5{queue;constructor(){this.queue=g5()}dispatch($,V,Y={}){return this.queue.dispatch($,V,Y),this}push($,V,Y={}){return this.queue.push($,V,Y),this}assertDispatched($,V){return this.queue.assertDispatched($,V),this}assertNotDispatched($){return this.queue.assertNotDispatched($),this}assertDispatchedTimes($,V){return this.queue.assertDispatchedTimes($,V),this}assertNothingDispatched(){return this.queue.assertNothingDispatched(),this}dispatched($){return this.queue.dispatched($)}reset(){return this.queue.reset(),this}cleanup(){u5()}}function E8(){return new E5}async function N8($,V){return await $.handle(V)}async function J8($,V,Y){try{throw await $.handle(V),Error("Expected job to fail, but it succeeded")}catch(G){if(G.message==="Expected job to fail, but it succeeded")throw G;if(Y){let X=G.message;if(typeof Y==="string"){if(!X.includes(Y))throw Error(`Expected error to contain "${Y}", got "${X}"`)}else if(!Y.test(X))throw Error(`Expected error to match ${Y}, got "${X}"`)}return G}}var z5=null;var U5={};w(U5,{setQueueManager:()=>o9,getQueueManager:()=>r9,dispatchSync:()=>t9,dispatchAfter:()=>s9,dispatch:()=>n9,createRedisDispatcher:()=>e9,chain:()=>d9,batch:()=>i9,StacksQueueManager:()=>S8,RedisQueue:()=>r,RedisJob:()=>R8,QueueManager:()=>a9});import{Queue as c9,batch as i9,chain as d9,dispatch as n9,dispatchAfter as s9,dispatchSync as t9,getQueueManager as r9,QueueManager as a9,setQueueManager as o9}from"@stacksjs/bun-queue";import{log as l5}from"@stacksjs/logging";class r{queue;config;isProcessing=!1;constructor($,V){this.config=V,this.queue=new c9($,{driver:"redis",prefix:V.prefix,redis:V.redis?{url:V.redis.url||this.buildRedisUrl(V.redis)}:void 0,defaultJobOptions:V.defaultJobOptions?{delay:V.defaultJobOptions.delay,attempts:V.defaultJobOptions.attempts,backoff:V.defaultJobOptions.backoff,removeOnComplete:V.defaultJobOptions.removeOnComplete,removeOnFail:V.defaultJobOptions.removeOnFail,priority:V.defaultJobOptions.priority,lifo:V.defaultJobOptions.lifo,timeout:V.defaultJobOptions.timeout,jobId:V.defaultJobOptions.jobId,dependsOn:V.defaultJobOptions.dependsOn,keepJobs:V.defaultJobOptions.keepJobs,deadLetter:V.defaultJobOptions.deadLetter}:void 0,limiter:V.limiter,metrics:V.metrics,stalledJobCheckInterval:V.stalledJobCheckInterval,maxStalledJobRetries:V.maxStalledJobRetries,distributedLock:V.distributedLock,defaultDeadLetterOptions:V.defaultDeadLetterOptions,horizontalScaling:V.horizontalScaling,logLevel:V.logLevel}),l5.debug(`Redis queue "${$}" initialized`)}buildRedisUrl($){let V=$.host||"localhost",Y=$.port||6379,G=$.password?`:${encodeURIComponent($.password)}@`:"",X=Number.isFinite($.db)&&$.db>=0?$.db:0;return`redis://${G}${V}:${Y}/${X}`}async add($,V){let Y={delay:V?.delay?V.delay*1000:void 0,attempts:V?.maxTries,priority:V?.priority,timeout:V?.timeout?V.timeout*1000:void 0,backoff:Array.isArray(V?.backoff)?V.backoff.map((G)=>(Number(G)||1)*1000):V?.backoff};return this.queue.add($,Y)}process($,V){if(this.isProcessing){l5.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process($,V),l5.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($,V){this.queue.events.on($,V)}}class S8{config;queues=new Map;defaultConnection="default";constructor($){this.config=$;for(let[V,Y]of Object.entries($))if(Y.driver==="redis")this.queues.set(V,new r(V,Y))}queue($){let V=$||this.defaultConnection,Y=this.queues.get(V);if(!Y){let G=this.config[V];if(!G)throw Error(`Queue "${V}" not configured`);Y=new r(V,G),this.queues.set(V,Y)}return Y}setDefaultConnection($){this.defaultConnection=$}async closeAll(){let $=Array.from(this.queues.values()).map((V)=>V.close());await Promise.all($),this.queues.clear()}}function e9($,V){let Y=new r($,V);return async(G,X)=>{return Y.add(G,X)}}class R8{data;options={};queue;constructor($,V,Y){this.data=Y;this.queue=new r($,V)}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 W5=()=>{};var w8={};w(w8,{retryDeadLetterJob:()=>I8,purgeDeadLetterJobs:()=>y8,moveToDeadLetter:()=>S5,listDeadLetterJobs:()=>k8});import{db as a}from"@stacksjs/database";function N5(){if(v8)return;v8=!0,console.warn("[queue/dlq] dead_letter_jobs table missing \u2014 DLQ disabled. "+"Run migrations to enable poison-message isolation.")}function J5($){let V=$?.message??"";return V.includes("no such table")||V.includes("doesn't exist")}async function S5($,V,Y=1){let G=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:V,total_failures:Y,first_failed_at:$.failed_at??G,last_failed_at:G,dead_lettered_at:G}).execute(),!0}catch(X){if(J5(X))return N5(),!1;throw X}}async function k8($={}){try{let V=a.selectFrom("dead_letter_jobs").selectAll();if($.queue)V=V.where("queue","=",$.queue);if($.reason)V=V.where("reason","=",$.reason);if($.sinceCutoffMs){let G=new Date($.sinceCutoffMs).toISOString().slice(0,19).replace("T"," ");V=V.where("dead_lettered_at",">=",G)}if($.limit&&$.limit>0)V=V.limit($.limit);return await V.execute()??[]}catch(V){if(J5(V))return N5(),[];throw V}}async function I8($){try{let V=await a.selectFrom("dead_letter_jobs").where("id","=",$).selectAll().executeTakeFirst();if(!V)return!1;let Y=Math.floor(Date.now()/1000);return await a.insertInto("jobs").values({queue:V.queue,payload:V.payload,attempts:0,reserved_at:null,available_at:Y,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),await a.deleteFrom("dead_letter_jobs").where("id","=",$).execute(),!0}catch(V){if(J5(V))return N5(),!1;throw V}}async function y8($=30){try{let V=new Date(Date.now()-$*24*60*60*1000).toISOString().slice(0,19).replace("T"," "),Y=await a.deleteFrom("dead_letter_jobs").where("dead_lettered_at","<",V).execute();return Number(Y?.numDeletedRows??Y?.[0]?.numDeletedRows??Y?.affectedRows??0)}catch(V){if(J5(V))return N5(),0;throw V}}var v8=!1;var R5=()=>{};function c5(){if(f8)return;f8=!0,console.warn("[queue/idempotency] job_idempotency table missing \u2014 idempotency keys are accepted but NOT enforced. "+"Run migrations to enable dedup.")}function i5($){let V=$?.message??"";return V.includes("no such table")||V.includes("doesn't exist")}async function V$($){try{let{db:V}=await import("@stacksjs/database"),Y=await V.selectFrom("job_idempotency").where("idempotency_key","=",$).select(["idempotency_key"]).executeTakeFirst();return Boolean(Y)}catch(V){if(i5(V))return c5(),!1;throw V}}async function Y$($,V,Y){try{let{db:G}=await import("@stacksjs/database");await G.insertInto("job_idempotency").values({idempotency_key:$,job_name:V,queue:Y??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}catch(G){if(i5(G)){c5();return}let X=G?.message??"";if(X.includes("UNIQUE constraint")||X.includes("Duplicate entry"))return;throw G}}async function d5($,V,Y){try{let{db:G}=await import("@stacksjs/database");return await G.insertInto("job_idempotency").values({idempotency_key:$,job_name:V,queue:Y??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),"claimed"}catch(G){if(i5(G))return c5(),"unenforced";let X=G?.message??"";if(X.includes("UNIQUE constraint")||X.includes("Duplicate entry"))return"duplicate";throw G}}async function n5($){try{let{db:V}=await import("@stacksjs/database");await V.deleteFrom("job_idempotency").where("idempotency_key","=",$).execute()}catch{}}var f8=!1;var u8={};w(u8,{unquarantineJob:()=>p8,recordFailureForPoison:()=>j8,quarantineJob:()=>h8,listQuarantined:()=>g8,isQuarantined:()=>C5,hashPayload:()=>O5});import{createHash as G$}from"crypto";import{db as S}from"@stacksjs/database";function Z5(){if(m8)return;m8=!0,console.warn("[queue/poison] job_quarantine table missing \u2014 poison detection disabled. "+"Run migrations to enable.")}function _5($){let V=$?.message??"";return V.includes("no such table")||V.includes("doesn't exist")}function O5($){let V;try{V=typeof $==="string"?$:JSON.stringify($??null)}catch{V=String($)}return G$("sha256").update(V).digest("hex").slice(0,32)}async function j8($,V,Y={}){let G=Y.maxFailures??5,X=Y.windowMinutes??60,K=O5(V),z=new Date,W=z.toISOString().slice(0,19).replace("T"," ");try{let L=await S.selectFrom("job_quarantine").where("job_name","=",$).where("payload_hash","=",K).selectAll().executeTakeFirst();if(!L)return await S.insertInto("job_quarantine").values({job_name:$,payload_hash:K,failure_count:1,window_start:W,quarantined_at:null}).execute(),!1;if(L.quarantined_at)return!0;let U=Date.parse(L.window_start.replace(" ","T")+"Z"),O=z.getTime()-U,Q=X*60*1000;if(Number.isFinite(U)&&O>Q)return await S.updateTable("job_quarantine").set({failure_count:1,window_start:W}).where("id","=",L.id).execute(),!1;let P=L.failure_count+1;if(P>=G)return await S.updateTable("job_quarantine").set({failure_count:P,quarantined_at:W}).where("id","=",L.id).execute(),!0;return await S.updateTable("job_quarantine").set({failure_count:P}).where("id","=",L.id).execute(),!1}catch(L){if(_5(L))return Z5(),!1;throw L}}async function C5($,V){let Y=O5(V);try{let G=await S.selectFrom("job_quarantine").where("job_name","=",$).where("payload_hash","in",[Y,"*"]).where("quarantined_at","is not",null).select(["id"]).executeTakeFirst();return Boolean(G)}catch(G){if(_5(G))return Z5(),!1;throw G}}async function h8($,V){let Y=V===void 0?"*":O5(V),G=new Date().toISOString().slice(0,19).replace("T"," ");try{let X=await S.selectFrom("job_quarantine").where("job_name","=",$).where("payload_hash","=",Y).select(["id"]).executeTakeFirst();if(X)await S.updateTable("job_quarantine").set({quarantined_at:G}).where("id","=",X.id).execute();else await S.insertInto("job_quarantine").values({job_name:$,payload_hash:Y,failure_count:0,window_start:G,quarantined_at:G}).execute()}catch(X){if(_5(X)){Z5();return}throw X}}async function p8($){try{await S.deleteFrom("job_quarantine").where("job_name","=",$).execute()}catch(V){if(_5(V)){Z5();return}throw V}}async function g8(){try{return await S.selectFrom("job_quarantine").where("quarantined_at","is not",null).selectAll().execute()??[]}catch($){if(_5($))return Z5(),[];throw $}}var m8=!1;var q5=()=>{};function o($){let V=$?.numUpdatedRows;if(V===null||V===void 0)return 0;if(typeof V==="object")return Number(V.changes??0);return Number(V)}async function s5($,V){let Y=JSON.stringify({jobName:$,payload:V.payload||{},options:{queue:V.queue,tries:V.maxTries,timeout:V.timeout,backoff:V.backoff}}),{db:G}=await import("@stacksjs/database");await G.insertInto("jobs").values({queue:V.queue||"default",payload:Y,attempts:0,available_at:X$(V.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}function X$($){let V=Date.now();return Math.floor(V/1000+$)}var j={};w(j,{withEvents:()=>l8,onQueueEvent:()=>b8,getWorkerTracker:()=>F5,getQueueEvents:()=>e,getGlobalMetrics:()=>I5,emitQueueEvent:()=>b,QueueMetrics:()=>k5,QueueEvents:()=>v5,OnQueueEvent:()=>c8});import{log as I}from"@stacksjs/logging";class v5{handlers=new Map;wildcardHandlers=new Set;on($,V){if(!this.handlers.has($))this.handlers.set($,new Set);return this.handlers.get($).add(V),()=>{this.handlers.get($)?.delete(V)}}onAny($){return this.wildcardHandlers.add($),()=>{this.wildcardHandlers.delete($)}}once($,V){let Y=async(G)=>{this.handlers.get($)?.delete(Y),await V(G)};return this.on($,Y)}async emit($,V){let Y={...V,timestamp:Date.now()};this.logEvent($,Y);let G=this.handlers.get($);if(G)for(let X of G)try{await X(Y)}catch(K){I.error(`Error in queue event handler for ${$}:`,K)}for(let X of this.wildcardHandlers)try{await X($,Y)}catch(K){I.error("Error in wildcard queue event handler:",K)}}logEvent($,V){let Y=V.jobId?`[${V.jobId}]`:"",G=V.queueName?`on ${V.queueName}`:"";switch($){case"job:added":I.debug(`Job added ${Y} ${G}`);break;case"job:processing":I.debug(`Job processing ${Y} ${G}`);break;case"job:completed":I.info(`Job completed ${Y} ${G} in ${V.duration}ms`);break;case"job:failed":I.error(`Job failed ${Y} ${G}:`,V.error);break;case"job:retrying":I.warn(`Job retrying ${Y} ${G} (attempt ${V.attemptsMade})`);break;case"job:stalled":I.warn(`Job stalled ${Y} ${G}`);break;case"queue:error":I.error(`Queue error ${G}:`,V.error);break}}off($){this.handlers.delete($)}removeAllListeners(){this.handlers.clear(),this.wildcardHandlers.clear()}}function e(){if(!t5)t5=new v5;return t5}function b8($,V){let Y=e();if($==="*")return Y.onAny(V);return Y.on($,V)}function b($,V){return e().emit($,V)}function l8($,V){return async(...Y)=>{let G=Y[0]?.id||"unknown",X=Date.now();await b("job:processing",{jobId:G,queueName:$,data:Y[0]?.data});try{let K=await V(...Y);return await b("job:completed",{jobId:G,queueName:$,result:K,duration:Date.now()-X}),K}catch(K){throw await b("job:failed",{jobId:G,queueName:$,error:K,duration:Date.now()-X}),K}}}function c8($){return function(V,Y,G){let X=G.value;return e().on($,X),G}}class k5{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",(V)=>{this.jobCounts.completed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1);let Y=V.duration||0;if(this.completions.push({timestamp:Date.now(),duration:Y}),this.completions.length>1000)this.completions.shift()}),$.on("job:failed",(V)=>{if(this.jobCounts.failed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1),V.error){if(this.errors.push({error:V.error,timestamp:Date.now()}),this.errors.length>100)this.errors.shift()}}))}getThroughputPerMinute(){let $=Date.now()-60000;return this.completions.filter((Y)=>Y.timestamp>=$).length}getAverageProcessingTime(){let $=Date.now()-60000,V=this.completions.filter((Y)=>Y.timestamp>=$);if(V.length===0)return 0;return V.reduce((Y,G)=>Y+G.duration,0)/V.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 I5(){if(!r5)r5=new k5;return r5}class i8{workers=new Map;register($,V){this.workers.set($,{id:$,status:"idle",queue:V,processedCount:0,failedCount:0,lastActivityAt:new Date().toISOString(),startedAt:new Date().toISOString()})}markActive($){let V=this.workers.get($);if(V)V.status="active",V.lastActivityAt=new Date().toISOString()}markIdle($){let V=this.workers.get($);if(V)V.status="idle",V.lastActivityAt=new Date().toISOString()}recordCompletion($){let V=this.workers.get($);if(V)V.processedCount++,V.lastActivityAt=new Date().toISOString()}recordFailure($){let V=this.workers.get($);if(V)V.failedCount++,V.lastActivityAt=new Date().toISOString()}unregister($){let V=this.workers.get($);if(V)V.status="stopped"}getAll(){return Array.from(this.workers.values())}clear(){this.workers.clear()}}function F5(){return K$}var t5=null,r5=null,K$;var q=f(()=>{K$=new i8});var g={};w(g,{recordBatchJobFailure:()=>Y8,recordBatchJobCompletion:()=>V8,isBatchCancelled:()=>Y9,getBatchCallbacks:()=>H5,PendingBatch:()=>w5,DispatchedBatch:()=>h,Batch:()=>a5});var{RedisClient:z$}=globalThis.Bun;import{log as B}from"@stacksjs/logging";import{env as L$}from"@stacksjs/env";function p(){return L$.QUEUE_DRIVER||"sync"}class w5{jobs;options={thenCallbacks:[],catchCallbacks:[],finallyCallbacks:[],progressCallbacks:[]};constructor($){this.jobs=$.map((V)=>("job"in V)?V:{job:V})}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(),V=this.jobs.length;if(V===0)throw Error("Cannot dispatch an empty batch");let Y=p();await W$({id:$,name:this.options.name||"",total_jobs:V,pending_jobs:V,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}),U$($,this.options);try{let{emitQueueEvent:G}=await Promise.resolve().then(() => (q(),j));await G("batch:added",{jobId:$,data:{name:this.options.name,totalJobs:V}})}catch{}for(let G=0;G<this.jobs.length;G++){let X=this.jobs[G];if(!X)continue;let{job:K,payload:z}=X,W={...z,_batchId:$,_batchIndex:G};if(this.options.queue&&!K.queue)K.queue=this.options.queue;if(Y==="sync")try{await K.dispatchNow(W),await V8($)}catch(L){await Y8($,`${$}:${G}`,L)}else await K.dispatch(W)}return B.info(`[Batch] Dispatched batch "${this.options.name||$}" with ${V} jobs`),new h($)}getJobs(){return[...this.jobs]}getOptions(){return this.options}}class h{id;constructor($){this.id=$}async fresh(){return l(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 V=$.total_jobs-$.pending_jobs;return Math.round(V/$.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(p()==="redis")await A$(this.id);else await F$(this.id);B.info(`[Batch] Cancelled batch ${this.id}`);let V=H5(this.id);if(V)for(let Y of V.finallyCallbacks)try{await Y(this)}catch(G){B.error(`[Batch] Error in finally callback for batch ${this.id}:`,G)}}async add($){let V=await this.fresh();if(!V)throw Error(`Batch ${this.id} not found`);if(V.cancelled_at)throw Error(`Batch ${this.id} has been cancelled`);if(V.finished_at)throw Error(`Batch ${this.id} has already finished`);let Y=$.map((z)=>("job"in z)?z:{job:z}),G=V.total_jobs+Y.length,X=V.pending_jobs+Y.length;await d8(this.id,{total_jobs:G,pending_jobs:X});let K=JSON.parse(V.options||"{}");for(let z=0;z<Y.length;z++){let W=Y[z];if(!W)continue;let{job:L,payload:U}=W,O={...U,_batchId:this.id,_batchIndex:V.total_jobs+z};if(K.queue&&!L.queue)L.queue=K.queue;await L.dispatch(O)}B.info(`[Batch] Added ${Y.length} jobs to batch ${this.id}`)}async delete(){await _$(this.id),y5(this.id)}}class a5{static create($){return new w5($)}static async find($){if(!await l($))return null;return new h($)}static async all(){return(await Z$()).map((V)=>new h(V.id))}static async prune($=24){return O$($)}}function U$($,V){o5.set($,V)}function H5($){return o5.get($)}function y5($){o5.delete($)}async function W$($){if(p()==="redis")await x$($);else await n8($)}async function l($){if(p()==="redis")return Q$($);return s8($)}async function Z$(){if(p()==="redis")return e8();return t8()}async function d8($,V){if(p()==="redis")await $9($,V);else await e5($,V)}async function _$($){if(p()==="redis")await V9($);else await r8($)}async function O$($){if(p()==="redis")return D$($);return a8($)}async function n8($){let{db:V}=await import("@stacksjs/database");await V.insertInto("job_batches").values({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}).execute()}async function s8($){let{db:V}=await import("@stacksjs/database");return await V.selectFrom("job_batches").where("id","=",$).selectAll().executeTakeFirst()||null}async function t8(){let{db:$}=await import("@stacksjs/database");return await $.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function e5($,V){let{db:Y}=await import("@stacksjs/database");await Y.updateTable("job_batches").set(V).where("id","=",$).execute()}async function r8($){let{db:V}=await import("@stacksjs/database");await V.deleteFrom("job_batches").where("id","=",$).execute()}async function F$($){await e5($,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function a8($){let{db:V}=await import("@stacksjs/database"),Y=new Date(Date.now()-$*60*60*1000).toISOString().slice(0,19).replace("T"," "),G=await V.deleteFrom("job_batches").where("finished_at","is not",null).where("finished_at","<",Y).executeTakeFirst();return Number(G?.numDeletedRows??0)}async function H$(){let{queue:$}=await import("@stacksjs/config"),V=$?.connections?.redis?.redis;if(V?.url)return V.url;let Y=V?.password?`:${encodeURIComponent(V.password)}@`:"",G=V?.db?`/${V.db}`:"";return`redis://${Y}${V?.host||"localhost"}:${V?.port||6379}${G}`}async function Q5(){let $=new z$(await H$());return await $.connect(),$}async function x$($){try{let V=await Q5(),Y=`${x5}${$.id}`;await V.hset(Y,{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||""}),await V.sadd($8,$.id),V.close()}catch{await n8($)}}function o8($){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}}async function Q$($){try{let V=await Q5(),Y=`${x5}${$}`,G=await V.hgetall(Y);return V.close(),o8(G)}catch{return s8($)}}async function e8(){try{let $=await Q5(),V=await $.smembers($8),Y=[];for(let G of V){let X=o8(await $.hgetall(`${x5}${G}`));if(X)Y.push(X)}return $.close(),Y}catch{return t8()}}async function $9($,V){try{let Y=await Q5(),G=`${x5}${$}`,X={};for(let[K,z]of Object.entries(V))X[K]=z===null?"":String(z);await Y.hset(G,X),Y.close()}catch{await e5($,V)}}async function V9($){try{let V=await Q5();await V.del(`${x5}${$}`),await V.srem($8,$),V.close()}catch{await r8($)}}async function A$($){let V=new Date().toISOString().slice(0,19).replace("T"," ");await $9($,{cancelled_at:V,finished_at:V})}async function D$($){try{let V=await e8(),Y=Date.now()-$*60*60*1000,G=0;for(let X of V)if(X.finished_at){if(new Date(X.finished_at).getTime()<Y)await V9(X.id),G++}return G}catch{return a8($)}}function $5($){if(!$)return null;try{let V=JSON.parse($);if(V&&(V.kind==="job"||V.kind==="module"))return V;return B.warn(`[Batch] handler JSON has unknown kind '${V?.kind}' \u2014 skipping`),null}catch(V){return B.warn(`[Batch] failed to parse persistent handler: ${V.message}`),null}}async function V5($,V){try{if($.kind==="job"){let{Jobs:X}=await Promise.resolve().then(() => (A5(),f5));await X.dispatch($.name,{...$.payload??{},_batchId:V});return}let Y=await import($.module).catch((X)=>{return B.warn(`[Batch] persistent handler module not found: ${$.module} (${X.message})`),null});if(!Y)return;let G=Y[$.export];if(typeof G!=="function"){B.warn(`[Batch] persistent handler export '${$.export}' is not a function on ${$.module}`);return}await G($.payload,V)}catch(Y){B.error(`[Batch] persistent handler threw for batch ${V}:`,Y)}}async function V8($){let{db:V,sql:Y}=await import("@stacksjs/database");await V.updateTable("job_batches").set({pending_jobs:Y`GREATEST(pending_jobs - 1, 0)`}).where("id","=",$).where("pending_jobs",">",0).execute();let G=new Date().toISOString().slice(0,19).replace("T"," "),X=await V.updateTable("job_batches").set({finished_at:G}).where("id","=",$).where("pending_jobs","=",0).where("finished_at","is",null).executeTakeFirst(),K=o(X)>0,z=H5($),W=new h($);if(z)for(let L of z.progressCallbacks)try{await L(W)}catch(U){B.error(`[Batch] Error in progress callback for batch ${$}:`,U)}if(K){try{let{emitQueueEvent:_}=await Promise.resolve().then(() => (q(),j));await _("batch:completed",{jobId:$})}catch{}let L=await l($);if(!L){y5($),B.info(`[Batch] Batch ${$} finished (record vanished)`);return}let U=JSON.parse(L.options||"{}"),Q=!((L.failed_jobs||0)>0)||U.allowFailures;if(z){if(Q)for(let _ of z.thenCallbacks)try{await _(W)}catch(Z){B.error(`[Batch] Error in then callback for batch ${$}:`,Z)}for(let _ of z.finallyCallbacks)try{await _(W)}catch(Z){B.error(`[Batch] Error in finally callback for batch ${$}:`,Z)}y5($)}if(Q){let _=$5(L.then_handler);if(_)await V5(_,$)}else{let _=$5(L.catch_handler);if(_)await V5(_,$)}let P=$5(L.finally_handler);if(P)await V5(P,$);B.info(`[Batch] Batch ${$} finished`)}}async function Y8($,V,Y){let{db:G,sql:X}=await import("@stacksjs/database"),K=await l($);if(!K)return;let z=JSON.parse(K.options||"{}");await G.updateTable("job_batches").set({pending_jobs:X`GREATEST(pending_jobs - 1, 0)`,failed_jobs:X`failed_jobs + 1`}).where("id","=",$).where("pending_jobs",">",0).execute();try{let Z=await l($);if(Z){let H=[];try{H=JSON.parse(Z.failed_job_ids||"[]")}catch{H=[]}H.push(V),await d8($,{failed_job_ids:JSON.stringify(H)})}}catch{}let W=new h($),L=H5($);if(L)for(let Z of L.catchCallbacks)try{await Z(W,Y)}catch(H){B.error(`[Batch] Error in catch callback for batch ${$}:`,H)}try{let{emitQueueEvent:Z}=await Promise.resolve().then(() => (q(),j));await Z("batch:failed",{jobId:$,error:Y})}catch{}let U=new Date().toISOString().slice(0,19).replace("T"," "),O=G.updateTable("job_batches").set(z.allowFailures?{finished_at:U}:{finished_at:U,cancelled_at:U}).where("id","=",$).where("finished_at","is",null);if(z.allowFailures)O=O.where("pending_jobs","=",0);let Q=await O.executeTakeFirst();if(o(Q)===0)return;let P=!!z.allowFailures;if(L){if(P)for(let Z of L.thenCallbacks)try{await Z(W)}catch(H){B.error(`[Batch] Error in then callback for batch ${$}:`,H)}for(let Z of L.finallyCallbacks)try{await Z(W)}catch(H){B.error(`[Batch] Error in finally callback for batch ${$}:`,H)}y5($)}let _=await l($);if(_){if(P){let H=$5(_.then_handler);if(H)await V5(H,$)}else{let H=$5(_.catch_handler);if(H)await V5(H,$)}let Z=$5(_.finally_handler);if(Z)await V5(Z,$)}B.info(`[Batch] Batch ${$} finished with failure(s)`)}async function Y9($){return(await l($))?.cancelled_at!==null}var o5,x5="stacks:batch:",$8="stacks:batches";var y=f(()=>{o5=new Map});var f5={};w(f5,{runJob:()=>m5,jobBatch:()=>z9,job:()=>X9,Jobs:()=>K9});import{appPath as P$}from"@stacksjs/path";import{env as M$}from"@stacksjs/env";import{enqueueAfterCommit as B$,isInTransaction as T$}from"@stacksjs/database";function E$(){return M$.QUEUE_DRIVER||"sync"}function N$(){if(G9)return;G9=!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 v{name;payload;options={};txMode="auto";constructor($,V){this.name=$;this.payload=V}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:V}=await Promise.resolve().then(() => b5);if($()){V()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(T$()){let G=this.runDispatchPipeline.bind(this);if(B$(async()=>{await G()}))return}else if(this.txMode==="after")N$()}await this.runDispatchPipeline()}async runDispatchPipeline(){let $=!1;if(this.options.idempotencyKey){let V=await d5(this.options.idempotencyKey,this.name,this.options.queue);if(V==="duplicate")return;$=V==="claimed"}try{if(await C5(this.name,this.payload)){let Y=m(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await S5({queue:this.options.queue||"default",payload:JSON.stringify(Y),exception:`quarantined: ${this.name}`},"poison-detected"))return}let V=E$();if(V==="database")await this.dispatchToDatabase();else if(V==="redis")await this.dispatchToRedis();else if(V==="sync")await m5(this.name,{payload:this.payload,context:this.options.context});else if(V==="sqs"||V==="memory"||V==="beanstalkd")throw Error(`[queue] Driver "${V}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);else throw Error(`[queue] Unknown QUEUE_DRIVER "${V}". Allowed values: redis, database, sync.`)}catch(V){if($&&this.options.idempotencyKey)await n5(this.options.idempotencyKey);throw V}}async dispatchIf($){if($)await this.dispatch()}async dispatchUnless($){if(!$)await this.dispatch()}async dispatchToDatabase(){let $=Math.floor(Date.now()/1000),V=this.options.delay?$+this.options.delay:$,Y=m(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff}),{db:G}=await import("@stacksjs/database");await G.insertInto("jobs").values({queue:this.options.queue||"default",payload:JSON.stringify(Y),attempts:0,reserved_at:null,available_at:V,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){let{RedisQueue:$}=await Promise.resolve().then(() => (W5(),U5)),{queue:V}=await import("@stacksjs/config"),Y=V?.connections?.redis;if(!Y)throw Error("Redis queue connection is not configured. Check config/queue.ts");let G=new $(this.options.queue||"default",Y),X=m(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});await G.add(X,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await m5(this.name,{payload:this.payload,context:this.options.context})}}function X9($,V){return new v($,V)}function z9($){let{PendingBatch:V}=(y(),p9(g));return new V($)}async function m5($,V={}){let{withTraceId:Y}=await import("@stacksjs/router"),G=V.traceId??`job:${$}:${Math.random().toString(36).slice(2,10)}`;await Y(G,async()=>{let z=(await import(P$(`Jobs/${$}.ts`))).default;if(!z)throw Error(`Job ${$} does not export a default`);if(typeof z.handle==="function")await z.handle(V.payload);else if(typeof z.action==="string"){let{runAction:W}=await import("@stacksjs/actions");await W(z.action)}else if(typeof z.action==="function")await z.action();else if(typeof z==="function")await z(V.payload,V.context);else throw Error(`Job ${$} does not have a valid handler`)})}var G9=!1,K9;var A5=f(()=>{R5();K5();q5();K9={make($,V){return new v($,V)},async dispatch($,V){await new v($,V).dispatch()},async dispatchIf($,V,Y){if($)await new v(V,Y).dispatch()},async dispatchUnless($,V,Y){if(!$)await new v(V,Y).dispatch()},async dispatchNow($,V){await new v($,V).dispatchNow()},dispatchAfter($,V,Y){return new v(V,Y).delay($)},async dispatchOnce($,V,Y){await new v(V,Y).withIdempotencyKey($).dispatch()},async dispatchAfterCommit($,V){await new v($,V).afterCommit().dispatch()}}});var j5={};w(j5,{resumeQueue:()=>O9,recordCircuitSuccess:()=>W9,recordCircuitFailure:()=>Z9,pauseQueue:()=>_9,listCircuitState:()=>F9,isCircuitOpen:()=>U9});import{db as J}from"@stacksjs/database";function c(){if(L9)return;L9=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing \u2014 circuit breaker disabled. "+"Run migrations to enable.")}function i($){let V=$?.message??"";return V.includes("no such table")||V.includes("doesn't exist")}async function X8($,V){try{let Y=await J.selectFrom("queue_circuit_state").where("queue_name","=",$).selectAll().executeTakeFirst();if(Y)return Y;return await J.insertInto("queue_circuit_state").values({queue_name:$,success_count:0,failure_count:0,window_start:V,paused_at:null,resume_at:null}).execute(),{queue_name:$,success_count:0,failure_count:0,window_start:V,paused_at:null,resume_at:null}}catch(Y){if(i(Y))return c(),null;let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return await J.selectFrom("queue_circuit_state").where("queue_name","=",$).selectAll().executeTakeFirst();throw Y}}async function U9($){try{let V=await J.selectFrom("queue_circuit_state").where("queue_name","=",$).select(["paused_at","resume_at"]).executeTakeFirst();if(!V||!V.paused_at)return!1;if(V.resume_at){let Y=Date.parse(V.resume_at.replace(" ","T")+"Z");if(Number.isFinite(Y)&&Date.now()>=Y)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(V){if(i(V))return c(),!1;throw V}}async function W9($,V={}){let Y=V.windowSeconds??300,G=new Date().toISOString().slice(0,19).replace("T"," "),X=await X8($,G);if(!X)return;try{if(G8(X.window_start,Y)){await J.updateTable("queue_circuit_state").set({success_count:1,failure_count:0,window_start:G}).where("queue_name","=",$).execute();return}await J.updateTable("queue_circuit_state").set({success_count:X.success_count+1}).where("queue_name","=",$).execute()}catch(K){if(i(K)){c();return}throw K}}async function Z9($,V={}){let Y=V.failureRateThreshold??0.5,G=V.windowSeconds??300,X=V.pauseSeconds??300,K=V.minObservations??10,z=new Date,W=z.toISOString().slice(0,19).replace("T"," "),L=await X8($,W);if(!L)return!1;if(L.paused_at)return!1;try{let{success_count:U,failure_count:O}=L;if(G8(L.window_start,G))U=0,O=0;O+=1;let Q=U+O,P=Q===0?0:O/Q;if(Q>=K&&P>=Y){let Z=new Date(z.getTime()+X*1000).toISOString().slice(0,19).replace("T"," ");return await J.updateTable("queue_circuit_state").set({success_count:U,failure_count:O,window_start:W,paused_at:W,resume_at:Z}).where("queue_name","=",$).execute(),!0}return await J.updateTable("queue_circuit_state").set({success_count:U,failure_count:O,window_start:G8(L.window_start,G)?W:L.window_start}).where("queue_name","=",$).execute(),!1}catch(U){if(i(U))return c(),!1;throw U}}function G8($,V){if(!$)return!0;let Y=Date.parse($.replace(" ","T")+"Z");if(!Number.isFinite(Y))return!0;return Date.now()-Y>V*1000}async function _9($,V=300){let Y=new Date,G=Y.toISOString().slice(0,19).replace("T"," "),X=new Date(Y.getTime()+V*1000).toISOString().slice(0,19).replace("T"," ");await X8($,G);try{await J.updateTable("queue_circuit_state").set({paused_at:G,resume_at:X}).where("queue_name","=",$).execute()}catch(K){if(i(K)){c();return}throw K}}async function O9($){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(V){if(i(V)){c();return}throw V}}async function F9(){try{return await J.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch($){if(i($))return c(),[];throw $}}var L9=!1;var D5=()=>{};K5();import{env as $$}from"@stacksjs/env";function C8(){return $$.QUEUE_DRIVER||"sync"}class q8{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:V,getFakeQueue:Y}=await Promise.resolve().then(() => b5);if(V()){Y()?.dispatch(this.name||"UnknownJob",$,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let G=C8();if(G==="sync")return this.dispatchNow($);if(G==="redis")return this.dispatchToRedis($);if(G==="database")return this.dispatchToDatabase($);if(G==="sqs"||G==="memory"||G==="beanstalkd")throw Error(`[queue] Driver "${G}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);throw Error(`[queue] Unknown QUEUE_DRIVER "${G}". Allowed values: redis, database, sync.`)}async dispatchIf($,V){if($)return this.dispatch(V)}async dispatchUnless($,V){if(!$)return this.dispatch(V)}async dispatchAfter($,V){let Y=C8();if(Y==="redis")return this.dispatchToRedis(V,{delay:$});if(Y==="database")return this.dispatchToDatabase(V,{delay:$});return await new Promise((G)=>setTimeout(G,$*1000)),await this.dispatchNow(V)}async dispatchNow($){if(typeof this.handle==="function")await this.handle($);else if(typeof this.action==="string"){let{runAction:V}=await import("@stacksjs/actions");await V(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($,V){let Y=Math.floor(Date.now()/1000),G=V?.delay?Y+V.delay:Y,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}),{db:K}=await import("@stacksjs/database");await K.insertInto("jobs").values({queue:this.queue||"default",payload:JSON.stringify(X),attempts:0,reserved_at:null,available_at:G,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis($,V){let{RedisQueue:Y}=await Promise.resolve().then(() => (W5(),U5)),{queue:G}=await import("@stacksjs/config"),X=G?.connections?.redis;if(!X)throw Error("Redis queue connection is not configured. Check config/queue.ts");let K=new Y(this.queue||"default",X),z=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 K.add(z,{delay:V?.delay,maxTries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0})}}A5();R5();q5();D5();K5();var K8=($)=>`__job_progress__:${$}`,z8=($)=>`__job_cancel__:${$}`;function J$($){return Number.isFinite($)?Math.max(0,Math.min(100,$)):0}async function S$($,V,Y){let{cache:G}=await import("@stacksjs/cache");await G.set(K8($),{percent:J$(V),message:Y,updatedAt:Date.now()},3600)}async function R$($){let{cache:V}=await import("@stacksjs/cache");return await V.get(K8($))??null}async function C$($){let{cache:V}=await import("@stacksjs/cache");await V.set(z8($),1,3600)}async function q$($){let{cache:V}=await import("@stacksjs/cache");return Boolean(await V.get(z8($)))}async function v$($){let{cache:V}=await import("@stacksjs/cache");await Promise.all([V.del(K8($)),V.del(z8($))])}import{log as Y5}from"@stacksjs/logging";import*as H9 from"@stacksjs/path";class x9{jobs=new Map;initialized=!1;register($){this.jobs.set($.name,$),Y5.debug(`Registered job: ${$.name} (${$.type})`)}get($){return this.jobs.get($)}all(){return Array.from(this.jobs.values())}byQueue($){return this.all().filter((V)=>V.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 d=new x9;async function L8($){let V=$||H9.userJobsPath(),Y=[];try{let G=new Bun.Glob("**/*.{ts,js}"),X={cwd:V,onlyFiles:!0,absolute:!0};for await(let K of G.scan(X)){if(K.includes(".test.")||K.includes(".spec.")||K.endsWith("index.ts")||K.endsWith("index.js"))continue;try{let z=await k$(K);if(z)Y.push(z),d.register(z)}catch(z){Y5.warn(`Failed to load job from ${K}: ${z.message}`)}}return d.setInitialized(!0),Y5.info(`Discovered ${Y.length} jobs from ${V}`),Y}catch(G){return Y5.error(`Failed to discover jobs: ${G.message}`),[]}}async function k$($){try{let V=await import($),Y=$.split("/").pop()?.replace(/\.(ts|js)$/,"")||"UnknownJob";if(V.default&&typeof V.default==="function"){let G=V.default;if(typeof G.handle==="function"||typeof G.prototype?.handle==="function"){let X=G.config||{};return{name:X.name||Y,path:$,config:{name:X.name||Y,description:X.description,queue:X.queue||"default",tries:X.retries||3,timeout:X.timeout,withoutOverlapping:X.withoutOverlapping,schedule:X.schedule,retryAfter:X.retryAfter},type:"class",module:G}}}if(V.default&&typeof V.default==="object"){let G=V.default;if(typeof G.handle==="function"||typeof G.action==="string")return{name:G.name||Y,path:$,config:{name:G.name||Y,description:G.description,queue:G.queue||"default",tries:G.tries||3,backoff:G.backoff,rate:G.rate,timeout:G.timeout||G.timeOut,backoffConfig:G.backoffConfig},type:"function",module:G}}return null}catch(V){return Y5.debug(`Could not load job from ${$}: ${V.message}`),null}}function I$($){return d.get($)}function y$(){return d.all()}function U8(){return d.scheduled()}async function w$($,V){let Y=d.get($);if(!Y)throw Error(`Job "${$}" not found. Did you run discoverJobs()?`);try{if(Y.type==="class"){if(typeof Y.module.handle==="function")return await Y.module.handle(V);return await new Y.module().handle(V)}else{if(typeof Y.module.handle==="function")return await Y.module.handle(V);throw Error(`Job "${$}" does not have a handle method`)}}catch(G){throw Y5.error(`Failed to execute job "${$}": ${G.message}`),G}}function f$($){let V=$.config;return{name:V.name,queue:V.queue,tries:V.tries,backoff:V.backoff,timeout:V.timeout,backoffConfig:V.backoffConfig,rate:V.rate}}import{log as T}from"@stacksjs/logging";q();import{log as m$}from"@stacksjs/logging";var Q9=!1;async function A9(){if(Q9)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(),Q9=!0,!0}catch($){return m$.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${$ instanceof Error?$.message:String($)}`),!1}}async function D9($){if(!await A9())return null;try{let{db:V}=await import("@stacksjs/database"),Y=await V.selectFrom("scheduled_job_runs").where("job_name","=",$).select(["last_run_at"]).executeTakeFirst();if(!Y?.last_run_at)return null;let G=new Date(Y.last_run_at);return Number.isNaN(G.getTime())?null:G}catch{return null}}async function P9($,V){if(!await A9())return;try{let{db:Y}=await import("@stacksjs/database"),G=V.toISOString();await Y.deleteFrom("scheduled_job_runs").where("job_name","=",$).execute(),await Y.insertInto("scheduled_job_runs").values({job_name:$,last_run_at:G}).execute()}catch{}}var N9={checkInterval:60000,preventOverlapping:!0},D={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...N9}},M9=new Set;function j$($,V){if(M9.has($))return;M9.add($),T.warn(`[scheduler] Cron expression "${$}" specifies seconds="${V}" 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 B9=!1,T9=new Map;function h$($){let V=T9.get($);if(!V)V=new Intl.DateTimeFormat("en-US",{timeZone:$,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),T9.set($,V);return V}function W8($,V){if(V&&V!=="local"&&V!=="system")try{let Y=h$(V).formatToParts($),G=(z)=>Y.find((W)=>W.type===z)?.value??"",X={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},K=Number(G("hour"));if(K===24)K=0;return{minute:Number(G("minute")),hour:K,day:Number(G("day")),month:Number(G("month")),dayOfWeek:X[G("weekday")]??$.getDay()}}catch{if(!B9)B9=!0,T.warn(`[scheduler] Invalid timezone "${V}"; falling back to system local time.`)}return{minute:$.getMinutes(),hour:$.getHours(),day:$.getDate(),month:$.getMonth()+1,dayOfWeek:$.getDay()}}function p$($,V,Y){let G=W8(new Date,Y),X=G.minute,K=G.hour,z=G.day,W=G.month,L=G.dayOfWeek;if(V){let H=W8(V,Y);if(H.minute===X&&H.hour===K&&H.day===z)return!1}let U=$.trim().split(/\s+/);if(U.length===6){let H=U[0];if(H&&H!=="0"&&H!=="*")j$($,H);U=U.slice(1)}if(U.length<5)return T.warn(`Invalid cron expression: ${$}`),!1;let[O,Q,P,_,Z]=U;return k(O,X,0,59)&&k(Q,K,0,23)&&k(P,z,1,31)&&k(_,W,1,12)&&k(Z,L,0,6)}function k($,V,Y,G){if($==="*")return!0;if($.includes(","))return $.split(",").map((z)=>Number.parseInt(z.trim(),10)).includes(V);if($.includes("-")){let[K,z]=$.split("-").map((W)=>Number.parseInt(W.trim(),10));return V>=K&&V<=z}if($.includes("/")){let[K,z]=$.split("/"),W=Number.parseInt(z,10);if(K==="*")return V%W===0;if(K.includes("-")){let[L,U]=K.split("-").map((O)=>Number.parseInt(O.trim(),10));return V>=L&&V<=U&&(V-L)%W===0}}let X=Number.parseInt($,10);return!Number.isNaN(X)&&V===X}function J9($){let Y={"@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(Y)return Y;let G=$.match(/^Every\.(\w+)$/i);if(G&&G[1]!==void 0){let K=G[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 * *"}[K]||null}let X=$.split(/\s+/).length;if(X>=5&&X<=6)return $;return null}function S9($,V){let Y=$.trim().split(/\s+/),G=Y.length===6?Y.slice(1):Y;if(G.length<5)return null;let[X,K,z,W,L]=G,U=new Date;U.setSeconds(0,0);let O=527040;for(let Q=1;Q<=O;Q++){let P=new Date(U.getTime()+Q*60000),_=W8(P,V);if(k(X,_.minute,0,59)&&k(K,_.hour,0,23)&&k(z,_.day,1,31)&&k(W,_.month,1,12)&&k(L,_.dayOfWeek,0,6))return P}return null}async function g$($={}){if(D.isRunning){T.warn("Scheduler is already running");return}D.config={...N9,...$},D.isRunning=!0,D.isShuttingDown=!1,await L8();let V=U8();for(let X of V){let K=X.config.rate||X.config.schedule;if(K){let z=J9(K);if(z){let W=await D9(X.name);D.jobs.set(X.name,{job:X,lastRun:W,nextRun:S9(z,D.config.timezone),isRunning:!1}),T.info(`Registered scheduled job: ${X.name} (${z})`)}else T.warn(`Invalid schedule for job ${X.name}: ${K}`)}}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",()=>Z8()),process.on("SIGTERM",()=>Z8());let Y=!1,G=()=>{if(D.isShuttingDown)return;let X=D.config.checkInterval,K=X-Date.now()%X;D.checkInterval=setTimeout(()=>{if(!D.isShuttingDown&&!Y)Y=!0,E9().catch((z)=>T.error("Scheduler check failed:",z)).finally(()=>{Y=!1});G()},K),D.checkInterval?.unref?.()};G(),await E9()}async function E9(){for(let[$,V]of D.jobs){let Y=V.job.config.rate||V.job.config.schedule;if(!Y)continue;let G=J9(Y);if(!G)continue;if(p$(G,V.lastRun,D.config.timezone)){if(D.config.preventOverlapping&&V.isRunning){T.debug(`Skipping ${$}: previous execution still running`);continue}if(V.job.config.withoutOverlapping&&V.isRunning){T.debug(`Skipping ${$}: withoutOverlapping is enabled`);continue}try{V.isRunning=!0,V.lastRun=new Date,V.nextRun=S9(G,D.config.timezone),await P9($,V.lastRun),T.info(`Dispatching scheduled job: ${$}`),await b("job:added",{jobId:`scheduled-${$}-${Date.now()}`,queueName:V.job.config.queue||"default",jobName:$}),await s5($,{queue:V.job.config.queue||"default",payload:{},maxTries:V.job.config.tries||3,timeout:V.job.config.timeout||60}),V.isRunning=!1,T.info(`Scheduled job ${$} dispatched to queue`)}catch(X){V.isRunning=!1,T.error(`Failed to dispatch scheduled job ${$}:`,X)}}}}async function Z8(){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 u$(){return{isRunning:D.isRunning,jobCount:D.jobs.size,jobs:Array.from(D.jobs.entries()).map(([$,V])=>({name:$,schedule:V.job.config.rate||V.job.config.schedule,lastRun:V.lastRun,nextRun:V.nextRun,isRunning:V.isRunning}))}}function b$(){return D.isRunning}function l$(){return new Map(D.jobs)}async function c$($){let V=D.jobs.get($);if(!V)throw Error(`Scheduled job "${$}" not found`);T.info(`Manually triggering scheduled job: ${$}`),await s5($,{queue:V.job.config.queue||"default",payload:{},maxTries:V.job.config.tries||3,timeout:V.job.config.timeout||60})}q();q();import{log as i$}from"@stacksjs/logging";var d$={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function _8($={}){let V={...d$,...$},Y=[],G=new Date,X=Math.floor(G.getTime()/1000);try{let{db:K}=await import("@stacksjs/database"),z=await K.selectFrom("jobs").selectAll().execute(),W=await K.selectFrom("failed_jobs").selectAll().execute(),L=new Map;for(let x of z){let M=x.queue||"default";if(!L.has(M))L.set(M,{pending:0,processing:0,delayed:0});let N=L.get(M);if(x.reserved_at)N.processing++;else if(x.available_at&&x.available_at>X)N.delayed++;else if(N.pending++,x.created_at){let C=typeof x.created_at==="number"?x.created_at:Math.floor(new Date(x.created_at).getTime()/1000),A8=X-C;if(!N.oldestAge||A8>N.oldestAge)N.oldestAge=A8}}let U=new Map;for(let x of W){let M=x.queue||"default";U.set(M,(U.get(M)||0)+1)}let O=[],Q=new Set([...L.keys(),...U.keys()]),P=V.queues?[...Q].filter((x)=>V.queues.includes(x)):[...Q],_=0,Z=0,H=0,s=0;for(let x of P){let M=L.get(x)||{pending:0,processing:0,delayed:0},N=U.get(x)||0;_+=M.pending,Z+=M.processing,H+=M.delayed,s+=N;let C="healthy";if(M.pending>=V.maxPendingCritical)C="unhealthy",Y.push({level:"critical",message:`Queue "${x}" has ${M.pending} pending jobs (threshold: ${V.maxPendingCritical})`,queue:x,timestamp:G.toISOString()});else if(M.pending>=V.maxPendingWarning)C="degraded",Y.push({level:"warning",message:`Queue "${x}" has ${M.pending} pending jobs (threshold: ${V.maxPendingWarning})`,queue:x,timestamp:G.toISOString()});if(N>=V.maxFailedCritical)C="unhealthy",Y.push({level:"critical",message:`Queue "${x}" has ${N} failed jobs (threshold: ${V.maxFailedCritical})`,queue:x,timestamp:G.toISOString()});else if(N>=V.maxFailedWarning){if(C==="healthy")C="degraded";Y.push({level:"warning",message:`Queue "${x}" has ${N} failed jobs (threshold: ${V.maxFailedWarning})`,queue:x,timestamp:G.toISOString()})}if(M.oldestAge){if(M.oldestAge>=V.maxJobAgeCritical)C="unhealthy",Y.push({level:"critical",message:`Queue "${x}" has a job waiting for ${Math.floor(M.oldestAge/3600)} hours`,queue:x,timestamp:G.toISOString()});else if(M.oldestAge>=V.maxJobAgeWarning){if(C==="healthy")C="degraded";Y.push({level:"warning",message:`Queue "${x}" has a job waiting for ${Math.floor(M.oldestAge/60)} minutes`,queue:x,timestamp:G.toISOString()})}}O.push({name:x,status:C,pending:M.pending,processing:M.processing,delayed:M.delayed,failed:N,oldestJobAge:M.oldestAge})}let x8=I5().getMetrics(),I9=x8.throughputPerMinute,y9=x8.averageDuration,Q8=_+Z+H+s,X5=Q8>0?s/Q8:0,t="healthy";if(O.some((x)=>x.status==="unhealthy"))t="unhealthy";else if(O.some((x)=>x.status==="degraded"))t="degraded";if(X5>=V.maxErrorRateCritical)t="unhealthy",Y.push({level:"critical",message:`Overall error rate is ${(X5*100).toFixed(1)}% (threshold: ${V.maxErrorRateCritical*100}%)`,timestamp:G.toISOString()});else if(X5>=V.maxErrorRateWarning){if(t==="healthy")t="degraded";Y.push({level:"warning",message:`Overall error rate is ${(X5*100).toFixed(1)}% (threshold: ${V.maxErrorRateWarning*100}%)`,timestamp:G.toISOString()})}let w9=F5().getAll().map((x)=>({id:x.id,status:x.status,queue:x.queue,processedCount:x.processedCount,failedCount:x.failedCount,lastActivityAt:x.lastActivityAt}));return{status:t,timestamp:G.toISOString(),queues:O,workers:w9,metrics:{totalPending:_,totalProcessing:Z,totalDelayed:H,totalFailed:s,throughputPerMinute:I9,averageProcessingTime:y9,errorRate:X5},alerts:Y}}catch(K){i$.error("Failed to perform queue health check:",K);let W=F5().getAll().map((L)=>({id:L.id,status:L.status,queue:L.queue,processedCount:L.processedCount,failedCount:L.failedCount,lastActivityAt:L.lastActivityAt}));return{status:"unhealthy",timestamp:G.toISOString(),queues:[],workers:W,metrics:{totalPending:0,totalProcessing:0,totalDelayed:0,totalFailed:0,throughputPerMinute:0,averageProcessingTime:0,errorRate:0},alerts:[{level:"critical",message:`Health check failed: ${K.message}`,timestamp:G.toISOString()}]}}}function n$($={}){return async(V)=>{let Y=await _8($),G=Y.status==="healthy"?200:Y.status==="degraded"?207:503;return new Response(JSON.stringify(Y,null,2),{status:G,headers:{"Content-Type":"application/json","Cache-Control":"no-store"}})}}async function s$($={}){return(await _8($)).status==="healthy"}import{log as R}from"@stacksjs/logging";var t$={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function P5($){return String($).replace(/[&<>"']/g,(V)=>t$[V]??V)}class O8{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 V=Date.now();if(V-this.lastResetTime>3600000)this.notificationCount=0,this.lastResetTime=V;if(this.notificationCount>=this.config.rateLimit){R.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((V)=>R.error("Failed to flush notification batch:",V)).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(($)=>R.error("Failed to flush notification batch on shutdown:",$))}async sendNotifications($){let V=[];for(let Y of this.config.channels)switch(Y){case"email":if(this.config.email)V.push(this.sendEmail($));break;case"slack":if(this.config.slack)V.push(this.sendSlack($));break;case"discord":if(this.config.discord)V.push(this.sendDiscord($));break;case"webhook":if(this.config.webhook)V.push(this.sendWebhook($));break;case"log":this.logFailures($);break}try{await Promise.all(V)}catch(Y){R.error("Failed to send job failure notifications:",Y)}finally{this.notificationCount+=$.length}}async sendEmail($){let V=this.config.email,Y=V.subject||`[Queue] ${$.length} Job(s) Failed`,G=this.formatEmailBody($);try{let{mail:X}=await import("@stacksjs/email");await X.send({to:Array.isArray(V.to)?V.to:[V.to],from:V.from,subject:Y,html:G})}catch(X){R.error("Failed to send email notification:",X)}}formatEmailBody($){let V=$.map((Y)=>`
3
3
  <tr>
4
- <td style="padding: 8px; border: 1px solid #ddd;">${D5(String(Y.id))}</td>
5
- <td style="padding: 8px; border: 1px solid #ddd;">${D5(Y.name)}</td>
6
- <td style="padding: 8px; border: 1px solid #ddd;">${D5(Y.queue)}</td>
4
+ <td style="padding: 8px; border: 1px solid #ddd;">${P5(String(Y.id))}</td>
5
+ <td style="padding: 8px; border: 1px solid #ddd;">${P5(Y.name)}</td>
6
+ <td style="padding: 8px; border: 1px solid #ddd;">${P5(Y.queue)}</td>
7
7
  <td style="padding: 8px; border: 1px solid #ddd;">${Y.attempts}/${Y.maxAttempts}</td>
8
- <td style="padding: 8px; border: 1px solid #ddd;">${D5(Y.failedAt.toISOString())}</td>
9
- <td style="padding: 8px; border: 1px solid #ddd;"><pre style="max-width: 300px; overflow: auto;">${D5(Y.exception)}</pre></td>
8
+ <td style="padding: 8px; border: 1px solid #ddd;">${P5(Y.failedAt.toISOString())}</td>
9
+ <td style="padding: 8px; border: 1px solid #ddd;"><pre style="max-width: 300px; overflow: auto;">${P5(Y.exception)}</pre></td>
10
10
  </tr>
11
11
  `).join("");return`
12
12
  <h2>Failed Jobs Report</h2>
@@ -28,4 +28,4 @@ var{defineProperty:j5,getOwnPropertyNames:y9,getOwnPropertyDescriptor:w9}=Object
28
28
  </table>
29
29
  `}async sendSlack($){let V=this.config.slack,Y=[{type:"header",text:{type:"plain_text",text:`\uD83D\uDEA8 ${$.length} Job(s) Failed`,emoji:!0}},...$.slice(0,10).map((G)=>({type:"section",text:{type:"mrkdwn",text:`*${G.name}*
30
30
  Queue: ${G.queue} | Attempts: ${G.attempts}/${G.maxAttempts}
31
- \`\`\`${G.exception.slice(0,200)}\`\`\``}}))];if($.length>10)Y.push({type:"section",text:{type:"mrkdwn",text:`_...and ${$.length-10} more failed jobs_`}});try{let G=await fetch(V.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:V.channel,username:V.username||"Queue Monitor",icon_emoji:V.iconEmoji||":warning:",blocks:Y})});if(!G.ok)S.error(`Slack notification failed with status ${G.status}`)}catch(G){S.error("Failed to send Slack notification:",G)}}async sendDiscord($){let V=this.config.discord,Y=$.slice(0,10).map((G)=>({title:`\u274C ${G.name}`,color:15158332,fields:[{name:"Queue",value:G.queue,inline:!0},{name:"Attempts",value:`${G.attempts}/${G.maxAttempts}`,inline:!0},{name:"Failed At",value:G.failedAt.toISOString(),inline:!0},{name:"Exception",value:`\`\`\`${G.exception.slice(0,500)}\`\`\``}],timestamp:G.failedAt.toISOString()}));try{let G=await fetch(V.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:V.username||"Queue Monitor",avatar_url:V.avatarUrl,content:`\uD83D\uDEA8 **${$.length} Job(s) Failed**`,embeds:Y})});if(!G.ok)S.error(`Discord notification failed with status ${G.status}`)}catch(G){S.error("Failed to send Discord notification:",G)}}async sendWebhook($){let V=this.config.webhook,Y={event:"jobs.failed",timestamp:new Date().toISOString(),count:$.length,jobs:$.map((X)=>({id:X.id,name:X.name,queue:X.queue,attempts:X.attempts,maxAttempts:X.maxAttempts,exception:X.exception,failedAt:X.failedAt.toISOString()}))},G={"Content-Type":"application/json",...V.headers};if(V.secret){let X=JSON.stringify(Y),K=await this.generateSignature(X,V.secret);G["X-Signature"]=K}try{let X=await fetch(V.url,{method:"POST",headers:G,body:JSON.stringify(Y)});if(!X.ok)S.error(`Webhook notification failed with status ${X.status}`)}catch(X){S.error("Failed to send webhook notification:",X)}}async generateSignature($,V){let Y=new TextEncoder,G=await crypto.subtle.importKey("raw",Y.encode(V),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),X=await crypto.subtle.sign("HMAC",G,Y.encode($));return Array.from(new Uint8Array(X)).map((K)=>K.toString(16).padStart(2,"0")).join("")}logFailures($){for(let V of $)S.error(`[Queue] Job "${V.name}" failed on queue "${V.queue}" after ${V.attempts} attempts: ${V.exception}`)}async cleanup(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.pendingBatch.length>0)await this.flushBatch()}}var P5=null;function d$($){return P5=new _8($),P5}function n$(){return P5}async function s$($){if(P5)await P5.notify($)}y();K5();import{err as t$,ok as N9}from"@stacksjs/error-handling";import{log as A}from"@stacksjs/logging";import M5 from"process";import{env as r$}from"@stacksjs/env";M5.on("unhandledRejection",($,V)=>{A.error(`Unhandled Rejection: ${$}`)});M5.on("uncaughtException",($)=>{A.error(`Uncaught Exception: ${$.message}`)});var b=0,G5=!1,E="",n=new Set;function a$($){return n.add($),$.finally(()=>n.delete($)),$}function o$(){let $=M5.env.STACKS_QUEUE_RESERVATION_TTL_SEC,V=$===void 0?Number.NaN:Number.parseInt($,10);return Number.isFinite(V)&&V>0?V:3600}function e$(){let $=M5.env.STACKS_QUEUE_SWEEP_INTERVAL_SEC,V=$===void 0?Number.NaN:Number.parseInt($,10);return(Number.isFinite(V)&&V>0?V:60)*1000}async function J9(){let $=o$(),V=Math.floor(Date.now()/1000)-$,Y=Math.floor(Date.now()/1000);try{let{db:G}=await import("@stacksjs/database"),X=await G.updateTable("jobs").set({reserved_at:null,available_at:Y}).where("reserved_at","<=",V).executeTakeFirst(),K=o(X);if(K>0)A.warn(`[queue] Requeued ${K} job(s) whose reservation exceeded the ${$}s TTL \u2014 likely victims of a worker crash. Set `+"STACKS_QUEUE_RESERVATION_TTL_SEC to tune.");return K}catch(G){return A.error("[queue] Reservation sweep failed",{reason:G instanceof Error?G.message:String(G)}),0}}function $1($){if(!$||typeof $!=="object")return!1;let V=$.status??$.statusCode;if(typeof V==="number"&&V>=400&&V<500)return!0;let Y=$.name;if(typeof Y==="string"&&(Y==="ValidationError"||Y==="ModelNotFoundError"))return!0;return!1}function V1(){return r$.QUEUE_DRIVER||"sync"}async function Y1($,V={}){try{A.info("Starting queue processor..."),G5=!0,E=`worker-${M5.pid}-${Date.now()}`;let Y=V.concurrency||1,G=V1(),{getWorkerTracker:X,getGlobalMetrics:K}=await Promise.resolve().then(() => (q(),j));if(K(),X().register(E,$||"default"),G==="redis")return A.info("Using Redis queue driver (bun-queue)"),await Z1($||"default",Y),N9(void 0);let z;if($)z=[$];else if(z=await C9(),z.length===0)z=["default"];return A.info(`Processing queues: ${z.join(", ")}`),await G1(z,Y),N9(void 0)}catch(Y){return G5=!1,t$(Y)}}async function C9(){try{let{db:$}=await import("@stacksjs/database"),Y=(await $.selectFrom("jobs").select("queue").distinct().execute()).map((G)=>G.queue).filter((G)=>Boolean(G));return Y.length>0?Y:["default"]}catch{return["default"]}}async function G1($,V){A.info("Listening for jobs...");let Y=$,G=Date.now(),X=1e4,K=Date.now(),z=e$();await J9();while(G5)try{let W=Date.now();if(W-G>X){try{let L=await C9();if(L.length>0)Y=L}catch{}G=W}if(W-K>z)await J9(),K=W;for(let L of Y){try{let{isCircuitOpen:_}=await Promise.resolve().then(() => (A5(),m5));if(await _(L))continue}catch{}let U=[];try{U=await X1(L,V)}catch{continue}await Promise.all(U.map(async(_)=>{try{A.info(`Processing job ${_.id} from queue "${L}"`),await a$(K1(_))}catch{A.error(`Unexpected error processing job ${_.id}`)}}))}await F8(1000)}catch{await F8(3000)}}async function X1($,V){let Y=Math.floor(Date.now()/1000),{db:G}=await import("@stacksjs/database"),X=[];for(let K=0;K<V;K++){let z=await G.selectFrom("jobs").where("queue","=",$).whereNull("reserved_at").where("available_at","<=",Y).orderBy("id","asc").limit(1).selectAll().executeTakeFirst();if(!z)break;let W=await G.updateTable("jobs").set({reserved_at:Y,attempts:(z.attempts||0)+1}).where("id","=",z.id).where("reserved_at","is",null).executeTakeFirst();if(o(W)>0)X.push(z)}return X}async function K1($){let V=$.id,Y=$.queue||"default";b++;let G=Date.now(),{emitQueueEvent:X,getWorkerTracker:K}=await Promise.resolve().then(() => (q(),j)),z=K();z.markActive(E);let W;try{W=JSON.parse($.payload||"{}").jobName}catch{}await X("job:processing",{jobId:String(V),queueName:Y,jobName:W});try{let _=JSON.parse($.payload||"{}").payload?._batchId;if(_){let{isBatchCancelled:Q}=await Promise.resolve().then(() => (y(),g));if(await Q(_)){A.info(`[Queue] Skipping job ${V} - batch ${_} has been cancelled`),await O8(V),b--,z.markIdle(E);return}}}catch{}let L=null;try{let U=JSON.parse($.payload||"{}"),_=U1(U);if(_===void 0)await S9(U);else await W1(S9(U),_*1000,`Job ${V} exceeded ${_}s timeout`)}catch(U){L=U instanceof Error?U:Error(String(U))}if(!L)try{await O8(V),A.info(`[Queue] Job ${V} completed`),z.recordCompletion(E);try{let{recordCircuitSuccess:U}=await Promise.resolve().then(() => (A5(),m5));await U($.queue??"default")}catch{}await X("job:completed",{jobId:String(V),queueName:Y,duration:Date.now()-G});try{let _=JSON.parse($.payload||"{}").payload?._batchId;if(_){let{recordBatchJobCompletion:Q}=await Promise.resolve().then(() => (y(),g));await Q(_)}}catch{}}catch{A.info(`[Queue] Failed to delete completed job ${V}`)}else{let U=L.message;A.info(`[Queue] Job ${V} failed: ${U}`),z.recordFailure(E),await X("job:failed",{jobId:String(V),queueName:Y,error:L,duration:Date.now()-G,attemptsMade:($.attempts||0)+1});let _=1,Q={};try{Q=JSON.parse($.payload||"{}"),_=Q.options?.tries||1}catch{}let P=($.attempts||0)+1;if(P>=_){A.info(`[Queue] Job ${V} exceeded max attempts (${P}/${_})`);let O=!1;if(Q?._retriedFromFailed===!0)try{let{moveToDeadLetter:Z}=await Promise.resolve().then(() => (J5(),y8));if(O=await Z({queue:$.queue,payload:$.payload,exception:L.stack||L.message},"repeat-failure",2),O)A.info(`[Queue] Job ${V} re-failed after retry \u2014 moved to dead_letter_jobs`)}catch{O=!1}if(!O){A.info(`[Queue] Moving job ${V} to failed_jobs`);try{O=await L1($,L)}catch{O=!1}}if(O)try{await O8(V)}catch{A.info(`[Queue] Failed to delete failed job ${V}`)}else A.error(`[Queue] Job ${V} 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:Z}=await Promise.resolve().then(() => (C5(),g8)),{recordCircuitFailure:H}=await Promise.resolve().then(() => (A5(),m5)),s=Q?.jobName??"unknown";await Z(s,Q?.payload),await H($.queue??"default")}catch{}try{let Z=Q.payload?._batchId;if(Z){let{recordBatchJobFailure:H}=await Promise.resolve().then(() => (y(),g));await H(Z,String(V),L)}}catch{}}else{let O=Q.options?.backoff,Z=30;if(Array.isArray(O)&&O.length>0){let H=Math.min(P-1,O.length-1);Z=O[H]}else if(typeof O==="number"&&O>0)Z=O;if(Z=Number(Z),!Number.isFinite(Z)||Z<0)Z=30;A.info(`[Queue] Job ${V} will be retried in ${Z}s (attempt ${P}/${_})`);try{await z1(V,Z),A.info(`[Queue] Job ${V} released for retry`)}catch{A.info(`[Queue] Failed to release job ${V} for retry`)}}}b--,z.markIdle(E)}async function O8($){let{db:V}=await import("@stacksjs/database");await V.deleteFrom("jobs").where("id","=",$).execute()}async function z1($,V=30){let Y=Math.floor(Date.now()/1000)+V;A.debug(`Releasing job ${$} for retry at ${Y}`);try{let{db:G}=await import("@stacksjs/database");await G.updateTable("jobs").set({reserved_at:null,available_at:Y}).where("id","=",$).execute(),A.debug(`Job ${$} released successfully`)}catch{A.error(`Failed to release job ${$}`)}}async function L1($,V){try{let Y=new Date().toISOString().slice(0,19).replace("T"," "),G=crypto.randomUUID(),X=V.stack||V.message,{db:K}=await import("@stacksjs/database");return await K.insertInto("failed_jobs").values({uuid:G,connection:"database",queue:$.queue,payload:$.payload,exception:X,failed_at:Y}).execute(),!0}catch(Y){return A.error("Failed to log failed job:",Y),!1}}function U1($){if(!$||typeof $!=="object")return;let Y=$.options?.timeout;if(typeof Y!=="number"||!Number.isFinite(Y)||Y<=0)return;return Y}async function W1($,V,Y){let G,X=new Promise((K,z)=>{G=setTimeout(()=>z(Error(Y)),V)});try{return await Promise.race([$,X])}finally{if(G!==void 0)clearTimeout(G)}}async function S9($){let V=B5($);if(!V.ok)throw Error(`[queue] Cannot deserialize job envelope: ${V.reason}`+(V.detail?` (${V.detail})`:""));let{runJob:Y}=await Promise.resolve().then(() => (Q5(),w5));await Y(V.envelope.jobName,{payload:V.envelope.payload})}async function Z1($,V){let{RedisQueue:Y}=await Promise.resolve().then(() => (W5(),U5)),{queue:G}=await import("@stacksjs/config"),X=G?.connections?.redis;if(!X)throw Error("Redis queue connection is not configured. Check config/queue.ts");let K=new Y($,X),{emitQueueEvent:z,getWorkerTracker:W}=await Promise.resolve().then(() => (q(),j)),L=W();K.process(V,async(U)=>{b++,L.markActive(E);let _=Date.now(),Q=B5(U.data);if(!Q.ok){b--,L.markIdle(E),A.error(`[Queue] Skipping Redis job ${U.id} - unparseable envelope: ${Q.reason}${Q.detail?` (${Q.detail})`:""}`);return}let P=Q.envelope,O=P.payload?._batchId;if(O)try{let{isBatchCancelled:Z}=await Promise.resolve().then(() => (y(),g));if(await Z(O)){A.info(`[Queue] Skipping Redis job ${U.id} - batch ${O} has been cancelled`),b--,L.markIdle(E);return}}catch{}await z("job:processing",{jobId:String(U.id),queueName:$});try{let{runJob:Z}=await Promise.resolve().then(() => (Q5(),w5));if(await Z(P.jobName,{payload:P.payload}),L.recordCompletion(E),await z("job:completed",{jobId:String(U.id),queueName:$,duration:Date.now()-_}),O)try{let{recordBatchJobCompletion:H}=await Promise.resolve().then(() => (y(),g));await H(O)}catch{}A.info(`[Queue] Redis job ${U.id} completed`)}catch(Z){if(L.recordFailure(E),await z("job:failed",{jobId:String(U.id),queueName:$,error:Z instanceof Error?Z:Error(String(Z)),duration:Date.now()-_}),O)try{let{recordBatchJobFailure:H}=await Promise.resolve().then(() => (y(),g));await H(O,String(U.id),Z instanceof Error?Z:Error(String(Z)))}catch{}if(A.error(`[Queue] Redis job ${U.id} failed: ${Z}`),$1(Z)){A.info(`[Queue] Redis job ${U.id} hit a non-retryable error \u2014 skipping retry`);return}throw Z}finally{b--,L.markIdle(E)}}),A.info(`Listening for Redis jobs on queue "${$}" with concurrency ${V}...`);while(G5)await F8(1000);await K.close()}async function _1($={}){let V=$.graceMs??1e4;if(G5=!1,n.size>0&&V>0){A.info(`[queue] Draining ${n.size} in-flight job(s) (grace ${V}ms)`);let Y=Promise.allSettled([...n]),G=new Promise((K)=>setTimeout(()=>K("timeout"),V));if(await Promise.race([Y.then(()=>"drained"),G])==="timeout"&&n.size>0)A.warn(`[queue] Drain timed out with ${n.size} job(s) still active. Their reservations will be reclaimed by the next worker's sweep (Q-2).`)}if(E){let{getWorkerTracker:Y}=await Promise.resolve().then(() => (q(),j));Y().unregister(E)}A.info("Queue processor stopped")}async function O1(){let{db:$}=await import("@stacksjs/database"),V=await $.selectFrom("failed_jobs").selectAll().execute();for(let Y of V)await q9(Number(Y.id))}async function q9($){let V=Math.floor(Date.now()/1000),Y=new Date().toISOString().slice(0,19).replace("T"," "),{db:G}=await import("@stacksjs/database"),K=(await G.selectFrom("failed_jobs").where("id","=",$).selectAll().execute())[0];if(!K)throw Error(`Failed job ${$} not found`);let z=K.payload;try{let W=JSON.parse(K.payload||"{}");W._retriedFromFailed=!0,z=JSON.stringify(W)}catch{}await G.insertInto("jobs").values({queue:K.queue,payload:z,attempts:0,reserved_at:null,available_at:V,created_at:Y}).execute(),await G.deleteFrom("failed_jobs").where("id","=",$).execute(),A.info(`Failed job ${$} has been re-queued`)}function F1(){return b}function H1(){return G5}function F8($){return new Promise((V)=>setTimeout(V,$))}async function Z3(){let{RedisQueue:$}=await Promise.resolve().then(() => (W5(),U5));return $}export{u8 as withEvents,h8 as unquarantineJob,g$ as triggerJob,k$ as toJobOptions,W8 as stopScheduler,_1 as stopProcessor,m$ as startScheduler,Y1 as startProcessor,T$ as setJobProgress,E8 as runTestJob,f5 as runJob,q9 as retryFailedJob,k8 as retryDeadLetterJob,Z9 as resumeQueue,g5 as restore,d5 as releaseDispatchKey,m8 as recordFailureForPoison,$$ as recordDispatchedKey,L9 as recordCircuitSuccess,U9 as recordCircuitFailure,V8 as recordBatchJobFailure,$8 as recordBatchJobCompletion,j8 as quarantineJob,I8 as purgeDeadLetterJobs,W9 as pauseQueue,B5 as parseEnvelope,b8 as onQueueEvent,s$ as notifyJobFailed,N5 as moveToDeadLetter,p8 as listQuarantined,v8 as listDeadLetterJobs,_9 as listCircuitState,d as jobRegistry,X9 as jobBatch,Y9 as job,H1 as isWorkerRunning,h$ as isSchedulerRunning,c$ as isQueueHealthy,S5 as isQuarantined,N$ as isJobCancelled,B8 as isFaked,z9 as isCircuitOpen,$9 as isBatchCancelled,O5 as hashPayload,e9 as hasDispatchedKey,F5 as getWorkerTracker,j$ as getSchedulerStatus,L8 as getScheduledJobs,p$ as getRegisteredJobs,Z3 as getRedisQueue,e as getQueueEvents,E$ as getJobProgress,C$ as getJob,k5 as getGlobalMetrics,M8 as getFakeQueue,n$ as getFailedJobNotifier,H5 as getBatchCallbacks,q$ as getAllJobs,F1 as getActiveJobCount,p5 as fake,R8 as expectJobToFail,v$ as executeJob,O1 as executeFailedJobs,u as emitQueueEvent,z8 as discoverJobs,T8 as createQueueTester,l$ as createHealthCheckHandler,m as createEnvelope,d$ as configureFailedJobNotifications,J$ as clearJobState,b9 as clearEnvelopeWarnings,i5 as claimDispatchKey,Z8 as checkQueueHealth,R$ as cancelJob,T5 as QueueTester,v5 as QueueMetrics,q5 as QueueEvents,y5 as PendingBatch,l8 as OnQueueEvent,G9 as Jobs,C8 as Job,g9 as JOB_ENVELOPE_VERSION,_8 as FailedJobNotifier,h as DispatchedBatch,r5 as Batch};
31
+ \`\`\`${G.exception.slice(0,200)}\`\`\``}}))];if($.length>10)Y.push({type:"section",text:{type:"mrkdwn",text:`_...and ${$.length-10} more failed jobs_`}});try{let G=await fetch(V.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:V.channel,username:V.username||"Queue Monitor",icon_emoji:V.iconEmoji||":warning:",blocks:Y})});if(!G.ok)R.error(`Slack notification failed with status ${G.status}`)}catch(G){R.error("Failed to send Slack notification:",G)}}async sendDiscord($){let V=this.config.discord,Y=$.slice(0,10).map((G)=>({title:`\u274C ${G.name}`,color:15158332,fields:[{name:"Queue",value:G.queue,inline:!0},{name:"Attempts",value:`${G.attempts}/${G.maxAttempts}`,inline:!0},{name:"Failed At",value:G.failedAt.toISOString(),inline:!0},{name:"Exception",value:`\`\`\`${G.exception.slice(0,500)}\`\`\``}],timestamp:G.failedAt.toISOString()}));try{let G=await fetch(V.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:V.username||"Queue Monitor",avatar_url:V.avatarUrl,content:`\uD83D\uDEA8 **${$.length} Job(s) Failed**`,embeds:Y})});if(!G.ok)R.error(`Discord notification failed with status ${G.status}`)}catch(G){R.error("Failed to send Discord notification:",G)}}async sendWebhook($){let V=this.config.webhook,Y={event:"jobs.failed",timestamp:new Date().toISOString(),count:$.length,jobs:$.map((X)=>({id:X.id,name:X.name,queue:X.queue,attempts:X.attempts,maxAttempts:X.maxAttempts,exception:X.exception,failedAt:X.failedAt.toISOString()}))},G={"Content-Type":"application/json",...V.headers};if(V.secret){let X=JSON.stringify(Y),K=await this.generateSignature(X,V.secret);G["X-Signature"]=K}try{let X=await fetch(V.url,{method:"POST",headers:G,body:JSON.stringify(Y)});if(!X.ok)R.error(`Webhook notification failed with status ${X.status}`)}catch(X){R.error("Failed to send webhook notification:",X)}}async generateSignature($,V){let Y=new TextEncoder,G=await crypto.subtle.importKey("raw",Y.encode(V),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),X=await crypto.subtle.sign("HMAC",G,Y.encode($));return Array.from(new Uint8Array(X)).map((K)=>K.toString(16).padStart(2,"0")).join("")}logFailures($){for(let V of $)R.error(`[Queue] Job "${V.name}" failed on queue "${V.queue}" after ${V.attempts} attempts: ${V.exception}`)}async cleanup(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.pendingBatch.length>0)await this.flushBatch()}}var M5=null;function r$($){return M5=new O8($),M5}function a$(){return M5}async function o$($){if(M5)await M5.notify($)}y();K5();import{err as e$,ok as R9}from"@stacksjs/error-handling";import{log as A}from"@stacksjs/logging";import B5 from"process";import{env as $1}from"@stacksjs/env";B5.on("unhandledRejection",($,V)=>{A.error(`Unhandled Rejection: ${$}`)});B5.on("uncaughtException",($)=>{A.error(`Uncaught Exception: ${$.message}`)});var u=0,G5=!1,E="",n=new Set;function V1($){return n.add($),$.finally(()=>n.delete($)),$}function Y1(){let $=B5.env.STACKS_QUEUE_RESERVATION_TTL_SEC,V=$===void 0?Number.NaN:Number.parseInt($,10);return Number.isFinite(V)&&V>0?V:3600}function G1(){let $=B5.env.STACKS_QUEUE_SWEEP_INTERVAL_SEC,V=$===void 0?Number.NaN:Number.parseInt($,10);return(Number.isFinite(V)&&V>0?V:60)*1000}async function C9(){let $=Y1(),V=Math.floor(Date.now()/1000)-$,Y=Math.floor(Date.now()/1000);try{let{db:G}=await import("@stacksjs/database"),X=await G.updateTable("jobs").set({reserved_at:null,available_at:Y}).where("reserved_at","<=",V).executeTakeFirst(),K=o(X);if(K>0)A.warn(`[queue] Requeued ${K} job(s) whose reservation exceeded the ${$}s TTL \u2014 likely victims of a worker crash. Set `+"STACKS_QUEUE_RESERVATION_TTL_SEC to tune.");return K}catch(G){return A.error("[queue] Reservation sweep failed",{reason:G instanceof Error?G.message:String(G)}),0}}function X1($){if(!$||typeof $!=="object")return!1;let V=$.status??$.statusCode;if(typeof V==="number"&&V>=400&&V<500)return!0;let Y=$.name;if(typeof Y==="string"&&(Y==="ValidationError"||Y==="ModelNotFoundError"))return!0;return!1}function K1(){return $1.QUEUE_DRIVER||"sync"}async function z1($,V={}){try{A.info("Starting queue processor..."),G5=!0,E=`worker-${B5.pid}-${Date.now()}`;let Y=V.concurrency||1,G=K1(),{getWorkerTracker:X,getGlobalMetrics:K}=await Promise.resolve().then(() => (q(),j));if(K(),X().register(E,$||"default"),G==="redis")return A.info("Using Redis queue driver (bun-queue)"),await H1($||"default",Y),R9(void 0);let z;if($)z=[$];else if(z=await v9(),z.length===0)z=["default"];return A.info(`Processing queues: ${z.join(", ")}`),await L1(z,Y),R9(void 0)}catch(Y){return G5=!1,e$(Y)}}async function v9(){try{let{db:$}=await import("@stacksjs/database"),Y=(await $.selectFrom("jobs").select("queue").distinct().execute()).map((G)=>G.queue).filter((G)=>Boolean(G));return Y.length>0?Y:["default"]}catch{return["default"]}}async function L1($,V){A.info("Listening for jobs...");let Y=$,G=Date.now(),X=1e4,K=Date.now(),z=G1();await C9();while(G5)try{let W=Date.now();if(W-G>X){try{let L=await v9();if(L.length>0)Y=L}catch{}G=W}if(W-K>z)await C9(),K=W;for(let L of Y){try{let{isCircuitOpen:O}=await Promise.resolve().then(() => (D5(),j5));if(await O(L))continue}catch{}let U=[];try{U=await U1(L,V)}catch{continue}await Promise.all(U.map(async(O)=>{try{A.info(`Processing job ${O.id} from queue "${L}"`),await V1(W1(O))}catch{A.error(`Unexpected error processing job ${O.id}`)}}))}await H8(1000)}catch{await H8(3000)}}async function U1($,V){let Y=Math.floor(Date.now()/1000),{db:G}=await import("@stacksjs/database"),X=[];for(let K=0;K<V;K++){let z=await G.selectFrom("jobs").where("queue","=",$).whereNull("reserved_at").where("available_at","<=",Y).orderBy("id","asc").limit(1).selectAll().executeTakeFirst();if(!z)break;let W=await G.updateTable("jobs").set({reserved_at:Y,attempts:(z.attempts||0)+1}).where("id","=",z.id).where("reserved_at","is",null).executeTakeFirst();if(o(W)>0)X.push(z)}return X}async function W1($){let V=$.id,Y=$.queue||"default";u++;let G=Date.now(),{emitQueueEvent:X,getWorkerTracker:K}=await Promise.resolve().then(() => (q(),j)),z=K();z.markActive(E);let W;try{W=JSON.parse($.payload||"{}").jobName}catch{}await X("job:processing",{jobId:String(V),queueName:Y,jobName:W});try{let O=JSON.parse($.payload||"{}").payload?._batchId;if(O){let{isBatchCancelled:Q}=await Promise.resolve().then(() => (y(),g));if(await Q(O)){A.info(`[Queue] Skipping job ${V} - batch ${O} has been cancelled`),await F8(V),u--,z.markIdle(E);return}}}catch{}let L=null;try{let U=JSON.parse($.payload||"{}"),O=O1(U);if(O===void 0)await q9(U);else await F1(q9(U),O*1000,`Job ${V} exceeded ${O}s timeout`)}catch(U){L=U instanceof Error?U:Error(String(U))}if(!L)try{await F8(V),A.info(`[Queue] Job ${V} completed`),z.recordCompletion(E);try{let{recordCircuitSuccess:U}=await Promise.resolve().then(() => (D5(),j5));await U($.queue??"default")}catch{}await X("job:completed",{jobId:String(V),queueName:Y,duration:Date.now()-G});try{let O=JSON.parse($.payload||"{}").payload?._batchId;if(O){let{recordBatchJobCompletion:Q}=await Promise.resolve().then(() => (y(),g));await Q(O)}}catch{}}catch{A.info(`[Queue] Failed to delete completed job ${V}`)}else{let U=L.message;A.info(`[Queue] Job ${V} failed: ${U}`),z.recordFailure(E),await X("job:failed",{jobId:String(V),queueName:Y,error:L,duration:Date.now()-G,attemptsMade:($.attempts||0)+1});let O=1,Q={};try{Q=JSON.parse($.payload||"{}"),O=Q.options?.tries||1}catch{}let P=($.attempts||0)+1;if(P>=O){A.info(`[Queue] Job ${V} exceeded max attempts (${P}/${O})`);let _=!1;if(Q?._retriedFromFailed===!0)try{let{moveToDeadLetter:Z}=await Promise.resolve().then(() => (R5(),w8));if(_=await Z({queue:$.queue,payload:$.payload,exception:L.stack||L.message},"repeat-failure",2),_)A.info(`[Queue] Job ${V} re-failed after retry \u2014 moved to dead_letter_jobs`)}catch{_=!1}if(!_){A.info(`[Queue] Moving job ${V} to failed_jobs`);try{_=await _1($,L)}catch{_=!1}}if(_)try{await F8(V)}catch{A.info(`[Queue] Failed to delete failed job ${V}`)}else A.error(`[Queue] Job ${V} 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:Z}=await Promise.resolve().then(() => (q5(),u8)),{recordCircuitFailure:H}=await Promise.resolve().then(() => (D5(),j5)),s=Q?.jobName??"unknown";await Z(s,Q?.payload),await H($.queue??"default")}catch{}try{let Z=Q.payload?._batchId;if(Z){let{recordBatchJobFailure:H}=await Promise.resolve().then(() => (y(),g));await H(Z,String(V),L)}}catch{}}else{let _=Q.options?.backoff,Z=30;if(Array.isArray(_)&&_.length>0){let H=Math.min(P-1,_.length-1);Z=_[H]}else if(typeof _==="number"&&_>0)Z=_;if(Z=Number(Z),!Number.isFinite(Z)||Z<0)Z=30;A.info(`[Queue] Job ${V} will be retried in ${Z}s (attempt ${P}/${O})`);try{await Z1(V,Z),A.info(`[Queue] Job ${V} released for retry`)}catch{A.info(`[Queue] Failed to release job ${V} for retry`)}}}u--,z.markIdle(E)}async function F8($){let{db:V}=await import("@stacksjs/database");await V.deleteFrom("jobs").where("id","=",$).execute()}async function Z1($,V=30){let Y=Math.floor(Date.now()/1000)+V;A.debug(`Releasing job ${$} for retry at ${Y}`);try{let{db:G}=await import("@stacksjs/database");await G.updateTable("jobs").set({reserved_at:null,available_at:Y}).where("id","=",$).execute(),A.debug(`Job ${$} released successfully`)}catch{A.error(`Failed to release job ${$}`)}}async function _1($,V){try{let Y=new Date().toISOString().slice(0,19).replace("T"," "),G=crypto.randomUUID(),X=V.stack||V.message,{db:K}=await import("@stacksjs/database");return await K.insertInto("failed_jobs").values({uuid:G,connection:"database",queue:$.queue,payload:$.payload,exception:X,failed_at:Y}).execute(),!0}catch(Y){return A.error("Failed to log failed job:",Y),!1}}function O1($){if(!$||typeof $!=="object")return;let Y=$.options?.timeout;if(typeof Y!=="number"||!Number.isFinite(Y)||Y<=0)return;return Y}async function F1($,V,Y){let G,X=new Promise((K,z)=>{G=setTimeout(()=>z(Error(Y)),V)});try{return await Promise.race([$,X])}finally{if(G!==void 0)clearTimeout(G)}}async function q9($){let V=T5($);if(!V.ok)throw Error(`[queue] Cannot deserialize job envelope: ${V.reason}`+(V.detail?` (${V.detail})`:""));let{runJob:Y}=await Promise.resolve().then(() => (A5(),f5));await Y(V.envelope.jobName,{payload:V.envelope.payload})}async function H1($,V){let{RedisQueue:Y}=await Promise.resolve().then(() => (W5(),U5)),{queue:G}=await import("@stacksjs/config"),X=G?.connections?.redis;if(!X)throw Error("Redis queue connection is not configured. Check config/queue.ts");let K=new Y($,X),{emitQueueEvent:z,getWorkerTracker:W}=await Promise.resolve().then(() => (q(),j)),L=W();K.process(V,async(U)=>{u++,L.markActive(E);let O=Date.now(),Q=T5(U.data);if(!Q.ok){u--,L.markIdle(E),A.error(`[Queue] Skipping Redis job ${U.id} - unparseable envelope: ${Q.reason}${Q.detail?` (${Q.detail})`:""}`);return}let P=Q.envelope,_=P.payload?._batchId;if(_)try{let{isBatchCancelled:Z}=await Promise.resolve().then(() => (y(),g));if(await Z(_)){A.info(`[Queue] Skipping Redis job ${U.id} - batch ${_} has been cancelled`),u--,L.markIdle(E);return}}catch{}await z("job:processing",{jobId:String(U.id),queueName:$});try{let{runJob:Z}=await Promise.resolve().then(() => (A5(),f5));if(await Z(P.jobName,{payload:P.payload}),L.recordCompletion(E),await z("job:completed",{jobId:String(U.id),queueName:$,duration:Date.now()-O}),_)try{let{recordBatchJobCompletion:H}=await Promise.resolve().then(() => (y(),g));await H(_)}catch{}A.info(`[Queue] Redis job ${U.id} completed`)}catch(Z){if(L.recordFailure(E),await z("job:failed",{jobId:String(U.id),queueName:$,error:Z instanceof Error?Z:Error(String(Z)),duration:Date.now()-O}),_)try{let{recordBatchJobFailure:H}=await Promise.resolve().then(() => (y(),g));await H(_,String(U.id),Z instanceof Error?Z:Error(String(Z)))}catch{}if(A.error(`[Queue] Redis job ${U.id} failed: ${Z}`),X1(Z)){A.info(`[Queue] Redis job ${U.id} hit a non-retryable error \u2014 skipping retry`);return}throw Z}finally{u--,L.markIdle(E)}}),A.info(`Listening for Redis jobs on queue "${$}" with concurrency ${V}...`);while(G5)await H8(1000);await K.close()}async function x1($={}){let V=$.graceMs??1e4;if(G5=!1,n.size>0&&V>0){A.info(`[queue] Draining ${n.size} in-flight job(s) (grace ${V}ms)`);let Y=Promise.allSettled([...n]),G=new Promise((K)=>setTimeout(()=>K("timeout"),V));if(await Promise.race([Y.then(()=>"drained"),G])==="timeout"&&n.size>0)A.warn(`[queue] Drain timed out with ${n.size} job(s) still active. Their reservations will be reclaimed by the next worker's sweep (Q-2).`)}if(E){let{getWorkerTracker:Y}=await Promise.resolve().then(() => (q(),j));Y().unregister(E)}A.info("Queue processor stopped")}async function Q1(){let{db:$}=await import("@stacksjs/database"),V=await $.selectFrom("failed_jobs").selectAll().execute();for(let Y of V)await k9(Number(Y.id))}async function k9($){let V=Math.floor(Date.now()/1000),Y=new Date().toISOString().slice(0,19).replace("T"," "),{db:G}=await import("@stacksjs/database"),K=(await G.selectFrom("failed_jobs").where("id","=",$).selectAll().execute())[0];if(!K)throw Error(`Failed job ${$} not found`);let z=K.payload;try{let W=JSON.parse(K.payload||"{}");W._retriedFromFailed=!0,z=JSON.stringify(W)}catch{}await G.insertInto("jobs").values({queue:K.queue,payload:z,attempts:0,reserved_at:null,available_at:V,created_at:Y}).execute(),await G.deleteFrom("failed_jobs").where("id","=",$).execute(),A.info(`Failed job ${$} has been re-queued`)}function A1(){return u}function D1(){return G5}function H8($){return new Promise((V)=>setTimeout(V,$))}async function x3(){let{RedisQueue:$}=await Promise.resolve().then(() => (W5(),U5));return $}export{l8 as withEvents,p8 as unquarantineJob,c$ as triggerJob,f$ as toJobOptions,Z8 as stopScheduler,x1 as stopProcessor,g$ as startScheduler,z1 as startProcessor,S$ as setJobProgress,N8 as runTestJob,m5 as runJob,k9 as retryFailedJob,I8 as retryDeadLetterJob,O9 as resumeQueue,u5 as restore,n5 as releaseDispatchKey,j8 as recordFailureForPoison,Y$ as recordDispatchedKey,W9 as recordCircuitSuccess,Z9 as recordCircuitFailure,Y8 as recordBatchJobFailure,V8 as recordBatchJobCompletion,h8 as quarantineJob,y8 as purgeDeadLetterJobs,_9 as pauseQueue,T5 as parseEnvelope,b8 as onQueueEvent,o$ as notifyJobFailed,S5 as moveToDeadLetter,g8 as listQuarantined,k8 as listDeadLetterJobs,F9 as listCircuitState,d as jobRegistry,z9 as jobBatch,X9 as job,D1 as isWorkerRunning,b$ as isSchedulerRunning,s$ as isQueueHealthy,C5 as isQuarantined,q$ as isJobCancelled,T8 as isFaked,U9 as isCircuitOpen,Y9 as isBatchCancelled,O5 as hashPayload,V$ as hasDispatchedKey,F5 as getWorkerTracker,u$ as getSchedulerStatus,U8 as getScheduledJobs,l$ as getRegisteredJobs,x3 as getRedisQueue,e as getQueueEvents,R$ as getJobProgress,I$ as getJob,I5 as getGlobalMetrics,B8 as getFakeQueue,a$ as getFailedJobNotifier,H5 as getBatchCallbacks,y$ as getAllJobs,A1 as getActiveJobCount,g5 as fake,J8 as expectJobToFail,w$ as executeJob,Q1 as executeFailedJobs,b as emitQueueEvent,L8 as discoverJobs,E8 as createQueueTester,n$ as createHealthCheckHandler,m as createEnvelope,r$ as configureFailedJobNotifications,v$ as clearJobState,l9 as clearEnvelopeWarnings,d5 as claimDispatchKey,_8 as checkQueueHealth,C$ as cancelJob,E5 as QueueTester,k5 as QueueMetrics,v5 as QueueEvents,w5 as PendingBatch,c8 as OnQueueEvent,K9 as Jobs,q8 as Job,b9 as JOB_ENVELOPE_VERSION,O8 as FailedJobNotifier,h as DispatchedBatch,a5 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.165",
5
+ "version": "0.70.167",
6
6
  "description": "The Stacks Queue system powered by bun-queue.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -57,18 +57,9 @@
57
57
  "prepublishOnly": "bun run build"
58
58
  },
59
59
  "dependencies": {
60
- "@stacksjs/bun-queue": "^0.1.7"
61
- },
62
- "peerDependencies": {
63
- "redis": ">=4.0.0"
64
- },
65
- "peerDependenciesMeta": {
66
- "redis": {
67
- "optional": true
68
- }
60
+ "@stacksjs/bun-queue": "^0.1.8"
69
61
  },
70
62
  "devDependencies": {
71
- "better-dx": "^0.2.17",
72
- "redis": "^5.12.1"
63
+ "better-dx": "^0.2.17"
73
64
  }
74
65
  }