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