@stacksjs/queue 0.72.5 → 0.72.7
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/health.d.ts +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/dist/health.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive a queued job's state from the columns that hold it.
|
|
3
|
+
*
|
|
4
|
+
* There is no `status` column: a worker claims a job by stamping
|
|
5
|
+
* `reserved_at`, and `available_at` in the future means it is not eligible
|
|
6
|
+
* yet. Dashboards and health checks both need this rule, and reading it off a
|
|
7
|
+
* `status` column - which several of them tried - silently reports every job
|
|
8
|
+
* as pending forever.
|
|
9
|
+
*/
|
|
10
|
+
export declare function queuedJobState(job: { reserved_at?: unknown, available_at?: unknown }, nowTimestamp?: number): QueuedJobState;
|
|
1
11
|
/**
|
|
2
12
|
* Perform queue health check
|
|
3
13
|
*/
|
|
@@ -83,3 +93,5 @@ export declare interface HealthCheckConfig {
|
|
|
83
93
|
* Health status
|
|
84
94
|
*/
|
|
85
95
|
export type HealthStatus = 'healthy' | 'degraded' | 'unhealthy';
|
|
96
|
+
/** What a row in the `jobs` table is currently doing. */
|
|
97
|
+
export type QueuedJobState = 'pending' | 'processing' | 'delayed';
|
package/dist/index.d.ts
CHANGED
|
@@ -129,11 +129,13 @@ export {
|
|
|
129
129
|
checkQueueHealth,
|
|
130
130
|
createHealthCheckHandler,
|
|
131
131
|
isQueueHealthy,
|
|
132
|
+
queuedJobState,
|
|
132
133
|
type HealthAlert,
|
|
133
134
|
type HealthCheckConfig,
|
|
134
135
|
type HealthStatus,
|
|
135
136
|
type QueueHealthResult,
|
|
136
137
|
type QueueMetrics as HealthQueueMetrics,
|
|
138
|
+
type QueuedJobState,
|
|
137
139
|
type QueueStatus,
|
|
138
140
|
type WorkerStatus,
|
|
139
141
|
} from './health';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var{defineProperty:h1,getOwnPropertyNames:cL,getOwnPropertyDescriptor:dL}=Object,nL=Object.prototype.hasOwnProperty;function sL(L){return this[L]}var rL=(L)=>{var $=(B4??=new WeakMap).get(L),V;if($)return $;if($=h1({},"__esModule",{value:!0}),L&&typeof L==="object"||typeof L==="function"){for(var Y of cL(L))if(!nL.call($,Y))h1($,Y,{get:sL.bind(L,Y),enumerable:!(V=dL(L,Y))||V.enumerable})}return B4.set(L,$),$},B4;var tL=(L)=>L;function oL(L,$){this[L]=tL.bind(null,$)}var p=(L,$)=>{for(var V in $)h1(L,V,{get:$[V],enumerable:!0,configurable:!0,set:oL.bind($,V)})};var v=(L,$)=>()=>(L&&($=L(L=0)),$);var Q=import.meta.require;function y(L,$,V,Y){return{jobName:L,payload:$,options:V,envelopeVersion:1,dispatchedAt:new Date().toISOString(),...Y?{traceId:Y}:{}}}function j(L){try{return JSON.stringify(L)}catch($){throw eL(L,$)}}function _1(L){j(L)}function eL(L,$){if(L0($)){let G=null;try{G=g1(L,"",new Set)}catch{}let U=G?`\`${G.path}\` is a BigInt (${G.value}n)`:"it holds a BigInt";return Error(`[queue] Job "${L.jobName}" was not dispatched: ${U}, `+"and job envelopes travel as JSON, which cannot represent one. Convert it where you dispatch \u2014 "+"`String(value)` keeps every digit, `Number(value)` is exact below 2^53 \u2014 and convert it back inside "+"the job handler. See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts.",{cause:$})}if($0($))return Error(`[queue] Job "${L.jobName}" was not dispatched: its payload contains a circular reference, which JSON cannot represent. Send the ids of the things the payload points at rather than the `+`objects themselves \u2014 that also keeps the job replayable once those objects have changed. ${"See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts."}`,{cause:$});let Y=$ instanceof Error?$.message:String($);return Error(`[queue] Job "${L.jobName}" was not dispatched: serializing its payload threw (${Y}). `+"JSON rejects only two things on its own and this was neither, so the throw came from the payload \u2014 "+"a `toJSON()` or a property getter. See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts.",{cause:$})}function L0(L){return L instanceof TypeError&&/bigint/i.test(L.message)}function $0(L){return L instanceof TypeError&&/circular|cyclic/i.test(L.message)}function g1(L,$,V){if(typeof L==="bigint")return{path:$||"envelope",value:L};if(!L||typeof L!=="object")return null;if(V.has(L))return null;if(V.add(L),Array.isArray(L)){for(let Y=0;Y<L.length;Y++){let G=g1(L[Y],`${$}[${Y}]`,V);if(G)return G}return null}for(let[Y,G]of Object.entries(L)){let U=g1(G,$?`${$}.${Y}`:Y,V);if(U)return U}return null}function T4(L,$){if(p1.has(L))return;p1.add(L),console.warn($)}function V0(){p1.clear()}function T1(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(),...typeof $.traceId==="string"&&$.traceId?{traceId:$.traceId}:{}},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 T4("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(),...typeof $.traceId==="string"&&$.traceId?{traceId:$.traceId}:{}},source:"v0-implicit"};if(typeof $.job==="string")return T4("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 aL=1,p1;var a=v(()=>{p1=new Set});var i1={};p(i1,{runJob:()=>R4,restore:()=>l1,isFaked:()=>J4,getFakeQueue:()=>N4,fake:()=>u1,expectJobToFail:()=>C4,createQueueTester:()=>S4,QueueTester:()=>E1});class E4{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 u1(){return Z1=new E4,Z1}function N4(){return Z1}function J4(){return Z1!==null}function l1(){Z1=null}class E1{queue;constructor(){this.queue=u1()}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(){l1()}}function S4(){return new E1}async function R4(L,$){return await L.handle($)}async function C4(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 Z1=null;var O1={};p(O1,{setQueueManager:()=>Z0,getQueueManager:()=>W0,dispatchSync:()=>z0,dispatchAfter:()=>X0,dispatch:()=>K0,createRedisDispatcher:()=>O0,chain:()=>U0,batch:()=>G0,StacksQueueManager:()=>q4,RedisQueue:()=>e,RedisJob:()=>k4,QueueManager:()=>_0});import{Queue as Y0,batch as G0,chain as U0,dispatch as K0,dispatchAfter as X0,dispatchSync as z0,getQueueManager as W0,QueueManager as _0,setQueueManager as Z0}from"@stacksjs/bun-queue";import{log as c1}from"@stacksjs/logging";class e{queue;config;isProcessing=!1;constructor(L,$){this.config=$,this.queue=new Y0(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}),c1.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){c1.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process(L,$),c1.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 q4{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 e($,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 e($,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 O0(L,$){let V=new e(L,$);return async(Y,G)=>{return V.add(Y,G)}}class k4{data;options={};queue;constructor(L,$,V){this.data=V;this.queue=new e(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 F1=()=>{};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 m4={};p(m4,{retryDeadLetterJob:()=>f4,purgeDeadLetterJobs:()=>j4,moveToDeadLetter:()=>J1,listDeadLetterJobs:()=>w4});import{db as L1}from"@stacksjs/database";function N1(){if(y4)return;y4=!0,console.warn("[queue/dlq] dead_letter_jobs table missing \u2014 DLQ disabled. "+"Run migrations to enable poison-message isolation.")}async function J1(L,$,V=1){let Y=new Date().toISOString().slice(0,19).replace("T"," ");try{return await L1.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 N1(),!1;throw G}}async function w4(L={}){try{let $=L1.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 N1(),[];throw $}}async function f4(L){try{let $=await L1.selectFrom("dead_letter_jobs").where("id","=",L).selectAll().executeTakeFirst();if(!$)return!1;let V=Math.floor(Date.now()/1000);return await L1.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 L1.deleteFrom("dead_letter_jobs").where("id","=",L).execute(),!0}catch($){if(B($))return N1(),!1;throw $}}async function j4(L=30){try{let $=new Date(Date.now()-L*24*60*60*1000).toISOString().slice(0,19).replace("T"," "),V=await L1.deleteFrom("dead_letter_jobs").where("dead_lettered_at","<",$).execute();return Number(V?.numDeletedRows??V?.[0]?.numDeletedRows??V?.affectedRows??0)}catch($){if(B($))return N1(),0;throw $}}var y4=!1;var S1=()=>{};function d1(){if(h4)return;h4=!0,console.warn("[queue/idempotency] job_idempotency table missing \u2014 idempotency keys are accepted but NOT enforced. "+"Run migrations to enable dedup.")}async function Q0(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 d1(),!1;throw $}}async function H0(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)){d1();return}let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return;throw Y}}async function n1(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 d1(),"unenforced";let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return"duplicate";throw Y}}async function s1(L){try{let{db:$}=await import("@stacksjs/database");await $.deleteFrom("job_idempotency").where("idempotency_key","=",L).execute()}catch{}}var h4=!1;var r1=()=>{};var i4={};p(i4,{unquarantineJob:()=>u4,recordFailureForPoison:()=>p4,quarantineJob:()=>b4,listQuarantined:()=>l4,isQuarantined:()=>R1,hashPayload:()=>H1});import{createHash as A0}from"crypto";import{db as R}from"@stacksjs/database";function Q1(){if(g4)return;g4=!0,console.warn("[queue/poison] job_quarantine table missing \u2014 poison detection disabled. "+"Run migrations to enable.")}function H1(L){let $;try{$=typeof L==="string"?L:JSON.stringify(L??null)}catch{$=String(L)}return A0("sha256").update($).digest("hex").slice(0,32)}async function p4(L,$,V={}){let Y=V.maxFailures??5,G=V.windowMinutes??60,U=H1($),K=new Date,W=K.toISOString().slice(0,19).replace("T"," ");try{let X=await R.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","=",U).selectAll().executeTakeFirst();if(!X)return await R.insertInto("job_quarantine").values({job_name:L,payload_hash:U,failure_count:1,window_start:W,quarantined_at:null}).execute(),!1;if(X.quarantined_at)return!0;let z=Date.parse(X.window_start.replace(" ","T")+"Z"),_=K.getTime()-z,A=G*60*1000;if(Number.isFinite(z)&&_>A)return await R.updateTable("job_quarantine").set({failure_count:1,window_start:W}).where("id","=",X.id).execute(),!1;let x=X.failure_count+1;if(x>=Y)return await R.updateTable("job_quarantine").set({failure_count:x,quarantined_at:W}).where("id","=",X.id).execute(),!0;return await R.updateTable("job_quarantine").set({failure_count:x}).where("id","=",X.id).execute(),!1}catch(X){if(B(X))return Q1(),!1;throw X}}async function R1(L,$){let V=H1($);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 Q1(),!1;throw Y}}async function b4(L,$){let V=$===void 0?"*":H1($),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)){Q1();return}throw G}}async function u4(L){try{await R.deleteFrom("job_quarantine").where("job_name","=",L).execute()}catch($){if(B($)){Q1();return}throw $}}async function l4(){try{return await R.selectFrom("job_quarantine").whereNotNull("quarantined_at").selectAll().execute()??[]}catch(L){if(B(L))return Q1(),[];throw L}}var g4=!1;var C1=()=>{};function c(L){let $=L?.numUpdatedRows;if($===null||$===void 0)return 0;if(typeof $==="object")return Number($.changes??0);return Number($)}function D0(L,$){let V=y(L,$.payload||{},{queue:$.queue,tries:$.maxTries,timeout:$.timeout,backoff:Array.isArray($.backoff)?$.backoff:void 0});return{queue:$.queue||"default",payload:j(V),attempts:0,available_at:x0($.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}}async function t1(L,$){let V=D0(L,$),{db:Y}=await import("@stacksjs/database");await Y.insertInto("jobs").values(V).execute()}function x0(L){let $=Date.now();return Math.floor($/1000+L)}var q1=v(()=>{a()});var b={};p(b,{withEvents:()=>d4,onQueueEvent:()=>c4,getWorkerTracker:()=>A1,getQueueEvents:()=>$1,getGlobalMetrics:()=>v1,emitQueueEvent:()=>d,QueueMetrics:()=>I1,QueueEvents:()=>k1,OnQueueEvent:()=>n4});import{log as m}from"@stacksjs/logging";class k1{handlers=new Map;wildcardHandlers=new Set;listenerSubscriptions=new WeakMap;reclaim=new FinalizationRegistry((L)=>{L()});on(L,$){if(!this.handlers.has(L))this.handlers.set(L,new Set);return this.handlers.get(L).add($),()=>{this.handlers.get(L)?.delete($)}}subscribeListener(L,$,V){let Y=new WeakRef(L),G=(X)=>{let z=Y.deref();if(z===void 0){U();return}return V.call(z,X)},U=this.on($,G),K=()=>{U();let X=Y.deref();if(X===void 0)return;let z=this.listenerSubscriptions.get(X);if(!z)return;if(z.delete(K),z.size===0)this.listenerSubscriptions.delete(X),this.reclaim.unregister(X)},W=this.listenerSubscriptions.get(L);if(!W)W=new Set,this.listenerSubscriptions.set(L,W);return W.add(K),this.reclaim.register(L,U,L),K}unsubscribeListener(L){let $=this.listenerSubscriptions.get(L);if(!$)return 0;let V=Array.from($);for(let Y of V)Y();return this.listenerSubscriptions.delete(L),this.reclaim.unregister(L),V.length}listenerCount(L){if(L==="*")return this.wildcardHandlers.size;return this.handlers.get(L)?.size??0}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){m.error(`Error in queue event handler for ${L}:`,U)}for(let G of this.wildcardHandlers)try{await G(L,V)}catch(U){m.error("Error in wildcard queue event handler:",U)}}logEvent(L,$){let V=$.jobId?`[${$.jobId}]`:"",Y=$.queueName?`on ${$.queueName}`:"";switch(L){case"job:added":m.debug(`Job added ${V} ${Y}`);break;case"job:processing":m.debug(`Job processing ${V} ${Y}`);break;case"job:completed":m.info(`Job completed ${V} ${Y} in ${$.duration}ms`);break;case"job:failed":m.error(`Job failed ${V} ${Y}:`,$.error);break;case"job:retrying":m.warn(`Job retrying ${V} ${Y} (attempt ${$.attemptsMade})`);break;case"job:stalled":m.warn(`Job stalled ${V} ${Y}`);break;case"queue:error":m.error(`Queue error ${Y}:`,$.error);break}}off(L){this.handlers.delete(L)}removeAllListeners(){this.handlers.clear(),this.wildcardHandlers.clear(),this.listenerSubscriptions=new WeakMap}}function $1(){if(!o1)o1=new k1;return o1}function c4(L,$){let V=$1();if(L==="*")return V.onAny($);return V.on(L,$)}function d(L,$){return $1().emit(L,$)}function d4(L,$){return async(...V)=>{let Y=V[0]?.id||"unknown",G=Date.now();await d("job:processing",{jobId:Y,queueName:L,data:V[0]?.data});try{let U=await $(...V);return await d("job:completed",{jobId:Y,queueName:L,result:U,duration:Date.now()-G}),U}catch(U){throw await d("job:failed",{jobId:Y,queueName:L,error:U,duration:Date.now()-G}),U}}}function n4(L){return function($,V){if(typeof V!=="object"||V===null)throw TypeError(`@OnQueueEvent('${L}') requires standard (TC39) decorators. Legacy decorators ('experimentalDecorators: true') give a method decorator no construction-time hook, so the handler cannot be bound to an instance.`);if(V.kind!=="method")throw TypeError(`@OnQueueEvent('${L}') can only decorate a class method, but it was applied to a ${V.kind}${V.name===void 0?"":` ('${String(V.name)}')`}. Move the handler into a method, or call onQueueEvent('${L}', handler) directly.`);if(V.static)throw TypeError(`@OnQueueEvent('${L}') cannot decorate the static method '${String(V.name)}': a static method has no instance, so it would subscribe at class-definition time and stay subscribed for the life of the process, even if the class is never used. Use an instance method, or subscribe explicitly with onQueueEvent('${L}', MyClass.${String(V.name)}).`);return V.addInitializer(function(){$1().subscribeListener(this,L,$)}),$}}class I1{jobCounts={added:0,completed:0,failed:0,processing:0};completions=[];errors=[];unsubscribe=[];constructor(){this.setupListeners()}setupListeners(){let L=$1();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(!a1)a1=new I1;return a1}class s4{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 A1(){return P0}var o1=null,a1=null,P0;var k=v(()=>{P0=new s4});var l={};p(l,{recordBatchJobFailure:()=>U4,recordBatchJobCompletion:()=>G4,isBatchCancelled:()=>UL,getBatchCallbacks:()=>D1,batchRecordToHash:()=>$L,batchRecordFromHash:()=>Y4,batchCounterIncrements:()=>r4,PendingBatch:()=>w1,DispatchedBatch:()=>u,Batch:()=>e1});var{RedisClient:M0}=globalThis.Bun;import{log as T}from"@stacksjs/logging";import{env as B0}from"@stacksjs/env";function h(){return B0.QUEUE_DRIVER||"sync"}class w1{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=h();await E0({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}),T0(L,this.options);try{let{emitQueueEvent:Y}=await Promise.resolve().then(() => (k(),b));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:K}=G,W={...K,_batchId:L,_batchIndex:Y};if(this.options.queue&&!U.queue)U.queue=this.options.queue;if(V==="sync")try{await U.dispatchNow(W),await G4(L)}catch(X){await U4(L,`${L}:${Y}`,X)}else await U.dispatch(W)}return T.info(`[Batch] Dispatched batch "${this.options.name||L}" with ${$} jobs`),new u(L)}getJobs(){return[...this.jobs]}getOptions(){return this.options}}class u{id;constructor(L){this.id=L}async fresh(){return n(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(h()==="redis")await w0(this.id);else await k0(this.id);T.info(`[Batch] Cancelled batch ${this.id}`);let $=D1(this.id);if($)for(let V of $.finallyCallbacks)try{await V(this)}catch(Y){T.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((U)=>("job"in U)?U:{job:U});if(!await S0(this.id,V.length))throw Error(`Batch ${this.id} was cancelled, finished or deleted before the jobs could be added`);let G=JSON.parse($.options||"{}");for(let U=0;U<V.length;U++){let K=V[U];if(!K)continue;let{job:W,payload:X}=K,z={...X,_batchId:this.id,_batchIndex:$.total_jobs+U};if(G.queue&&!W.queue)W.queue=G.queue;await W.dispatch(z)}T.info(`[Batch] Added ${V.length} jobs to batch ${this.id}`)}async delete(){await R0(this.id),y1(this.id)}}class e1{static create(L){return new w1(L)}static async find(L){if(!await n(L))return null;return new u(L)}static async all(){return(await N0()).map(($)=>new u($.id))}static async prune(L=24){return C0(L)}}function T0(L,$){L4.set(L,$)}function D1(L){return L4.get(L)}function y1(L){L4.delete(L)}async function E0(L){if(h()==="redis")await v0(L);else await t4(L)}async function n(L){if(h()==="redis")return y0(L);return o4(L)}async function N0(){if(h()==="redis")return VL();return a4()}async function J0(L,$){if(h()==="redis")await YL(L,$);else await $4(L,$)}async function S0(L,$){if($===0)return!0;if(h()==="redis")try{let U=await U1(),K=`${G1}${L}`;return await U.hincrby(K,"total_jobs",$),await U.hincrby(K,"pending_jobs",$),U.close(),!0}catch{}let{db:V,sql:Y}=await import("@stacksjs/database"),G=await V.updateTable("job_batches").set(r4(Y,$)).where("id","=",L).whereNull("cancelled_at").whereNull("finished_at").executeTakeFirst();return c(G)>0}function r4(L,$){return{total_jobs:L`total_jobs + ${$}`,pending_jobs:L`pending_jobs + ${$}`}}async function R0(L){if(h()==="redis")await GL(L);else await e4(L)}async function C0(L){if(h()==="redis")return f0(L);return LL(L)}function q0(L){return!!(L.then_handler||L.catch_handler||L.finally_handler)}async function t4(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(!q0(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){T.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 o4(L){let{db:$}=await import("@stacksjs/database");return await $.selectFrom("job_batches").where("id","=",L).selectAll().executeTakeFirst()||null}async function a4(){let{db:L}=await import("@stacksjs/database");return await L.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function $4(L,$){let{db:V}=await import("@stacksjs/database");await V.updateTable("job_batches").set($).where("id","=",L).execute()}async function e4(L){let{db:$}=await import("@stacksjs/database");await $.deleteFrom("job_batches").where("id","=",L).execute()}async function k0(L){await $4(L,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function LL(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 I0(){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 U1(){let L=new M0(await I0());return await L.connect(),L}function $L(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 v0(L){try{let $=await U1();await $.hset(`${G1}${L.id}`,$L(L)),await $.sadd(V4,L.id),$.close()}catch{await t4(L)}}function Y4(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 y0(L){try{let $=await U1(),V=`${G1}${L}`,Y=await $.hgetall(V);return $.close(),Y4(Y)}catch{return o4(L)}}async function VL(){try{let L=await U1(),$=await L.smembers(V4),V=[];for(let Y of $){let G=Y4(await L.hgetall(`${G1}${Y}`));if(G)V.push(G)}return L.close(),V}catch{return a4()}}async function YL(L,$){try{let V=await U1(),Y=`${G1}${L}`,G={};for(let[U,K]of Object.entries($))G[U]=K===null?"":String(K);await V.hset(Y,G),V.close()}catch{await $4(L,$)}}async function GL(L){try{let $=await U1();await $.del(`${G1}${L}`),await $.srem(V4,L),$.close()}catch{await e4(L)}}async function w0(L){let $=new Date().toISOString().slice(0,19).replace("T"," ");await YL(L,{cancelled_at:$,finished_at:$})}async function f0(L){try{let $=await VL(),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 GL(G.id),Y++}return Y}catch{return LL(L)}}function V1(L){if(!L)return null;try{let $=JSON.parse(L);if($&&($.kind==="job"||$.kind==="module"))return $;return T.warn(`[Batch] handler JSON has unknown kind '${$?.kind}' \u2014 skipping`),null}catch($){return T.warn(`[Batch] failed to parse persistent handler: ${$.message}`),null}}async function Y1(L,$){try{if(L.kind==="job"){let{Jobs:G}=await Promise.resolve().then(() => (x1(),f1));await G.dispatch(L.name,{...L.payload??{},_batchId:$});return}let V=await import(L.module).catch((G)=>{return T.warn(`[Batch] persistent handler module not found: ${L.module} (${G.message})`),null});if(!V)return;let Y=V[L.export];if(typeof Y!=="function"){T.warn(`[Batch] persistent handler export '${L.export}' is not a function on ${L.module}`);return}await Y(L.payload,$)}catch(V){T.error(`[Batch] persistent handler threw for batch ${$}:`,V)}}async function G4(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=c(G)>0,K=D1(L),W=new u(L);if(K)for(let X of K.progressCallbacks)try{await X(W)}catch(z){T.error(`[Batch] Error in progress callback for batch ${L}:`,z)}if(U){try{let{emitQueueEvent:O}=await Promise.resolve().then(() => (k(),b));await O("batch:completed",{jobId:L})}catch{}let X=await n(L);if(!X){y1(L),T.info(`[Batch] Batch ${L} finished (record vanished)`);return}let z=JSON.parse(X.options||"{}"),A=!((X.failed_jobs||0)>0)||z.allowFailures;if(K){if(A)for(let O of K.thenCallbacks)try{await O(W)}catch(Z){T.error(`[Batch] Error in then callback for batch ${L}:`,Z)}for(let O of K.finallyCallbacks)try{await O(W)}catch(Z){T.error(`[Batch] Error in finally callback for batch ${L}:`,Z)}y1(L)}if(A){let O=V1(X.then_handler);if(O)await Y1(O,L)}else{let O=V1(X.catch_handler);if(O)await Y1(O,L)}let x=V1(X.finally_handler);if(x)await Y1(x,L);T.info(`[Batch] Batch ${L} finished`)}}async function U4(L,$,V){let{db:Y,sql:G}=await import("@stacksjs/database"),U=await n(L);if(!U)return;let K=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 Z=await n(L);if(Z){let F=[];try{F=JSON.parse(Z.failed_job_ids||"[]")}catch{F=[]}F.push($),await J0(L,{failed_job_ids:JSON.stringify(F)})}}catch{}let W=new u(L),X=D1(L);if(X)for(let Z of X.catchCallbacks)try{await Z(W,V)}catch(F){T.error(`[Batch] Error in catch callback for batch ${L}:`,F)}try{let{emitQueueEvent:Z}=await Promise.resolve().then(() => (k(),b));await Z("batch:failed",{jobId:L,error:V})}catch{}let z=new Date().toISOString().slice(0,19).replace("T"," "),_=Y.updateTable("job_batches").set(K.allowFailures?{finished_at:z}:{finished_at:z,cancelled_at:z}).where("id","=",L).whereNull("finished_at");if(K.allowFailures)_=_.where("pending_jobs","=",0);let A=await _.executeTakeFirst();if(c(A)===0)return;let x=!!K.allowFailures;if(X){if(x)for(let Z of X.thenCallbacks)try{await Z(W)}catch(F){T.error(`[Batch] Error in then callback for batch ${L}:`,F)}for(let Z of X.finallyCallbacks)try{await Z(W)}catch(F){T.error(`[Batch] Error in finally callback for batch ${L}:`,F)}y1(L)}let O=await n(L);if(O){if(x){let F=V1(O.then_handler);if(F)await Y1(F,L)}else{let F=V1(O.catch_handler);if(F)await Y1(F,L)}let Z=V1(O.finally_handler);if(Z)await Y1(Z,L)}T.info(`[Batch] Batch ${L} finished with failure(s)`)}async function UL(L){return(await n(L))?.cancelled_at!==null}var L4,G1="stacks:batch:",V4="stacks:batches";var g=v(()=>{q1();L4=new Map});var f1={};p(f1,{runJob:()=>j1,resolveJobFile:()=>ZL,jobBatch:()=>_L,job:()=>zL,Jobs:()=>WL});import{appPath as j0,frameworkPath as m0}from"@stacksjs/path";import{env as h0}from"@stacksjs/env";import{enqueueAfterCommit as g0,isInTransaction as p0}from"@stacksjs/database";function b0(){return h0.QUEUE_DRIVER||"sync"}function u0(){if(KL)return;KL=!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 w{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(() => i1);if(L()){$()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(p0()){let Y=this.runDispatchPipeline.bind(this);if(g0(async()=>{await Y()}))return}else if(this.txMode==="after")u0()}await this.runDispatchPipeline()}async runDispatchPipeline(){let L=!1;if(this.options.idempotencyKey){let $=await n1(this.options.idempotencyKey,this.name,this.options.queue);if($==="duplicate")return;L=$==="claimed"}try{if(await R1(this.name,this.payload)){let V=y(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await J1({queue:this.options.queue||"default",payload:j(V),exception:`quarantined: ${this.name}`},"poison-detected"))return}let $=b0();if($==="database")await this.dispatchToDatabase();else if($==="redis")await this.dispatchToRedis();else if($==="sync")await j1(this.name,{payload:this.payload,context:this.options.context,traceId:await XL()});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 s1(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=y(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff},await XL()),Y=j(V),{db:G}=await import("@stacksjs/database");await G.insertInto("jobs").values({queue:this.options.queue||"default",payload:Y,attempts:0,reserved_at:null,available_at:$,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){let L=y(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});_1(L);let{RedisQueue:$}=await Promise.resolve().then(() => (F1(),O1)),{queue:V}=await import("@stacksjs/config"),Y=V?.connections?.redis;if(!Y)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new $(this.options.queue||"default",Y).add(L,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await j1(this.name,{payload:this.payload,context:this.options.context})}}function zL(L,$){return new w(L,$)}function _L(L){let{PendingBatch:$}=(g(),rL(l));return new $(L)}async function ZL(L){let $=[j0(`Jobs/${L}.ts`),m0(`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 j1(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 ZL(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 K=(await import(G)).default;if(!K)throw Error(`Job ${L} does not export a default`);if(typeof K.handle==="function")await K.handle($.payload);else if(typeof K.action==="string"){let{runAction:W}=await import("@stacksjs/actions");await W(K.action)}else if(typeof K.action==="function")await K.action();else if(typeof K==="function")await K($.payload,$.context);else throw Error(`Job ${L} does not have a valid handler`)})}async function XL(){try{let{getTraceId:L}=await import("@stacksjs/router");return L()}catch{return}}var KL=!1,WL;var x1=v(()=>{S1();a();r1();C1();WL={make(L,$){return new w(L,$)},async dispatch(L,$){await new w(L,$).dispatch()},async dispatchIf(L,$,V){if(L)await new w($,V).dispatch()},async dispatchUnless(L,$,V){if(!L)await new w($,V).dispatch()},async dispatchNow(L,$){await new w(L,$).dispatchNow()},dispatchAfter(L,$,V){return new w($,V).delay(L)},async dispatchOnce(L,$,V){await new w($,V).withIdempotencyKey(L).dispatch()},async dispatchAfterCommit(L,$){await new w(L,$).afterCommit().dispatch()}}});var m1={};p(m1,{resumeQueue:()=>DL,recordCircuitSuccess:()=>QL,recordCircuitFailure:()=>HL,pauseQueue:()=>AL,listCircuitState:()=>xL,isCircuitOpen:()=>FL});import{db as S}from"@stacksjs/database";function s(){if(OL)return;OL=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing \u2014 circuit breaker disabled. "+"Run migrations to enable.")}async function X4(L,$){try{let V=await S.selectFrom("queue_circuit_state").where("queue_name","=",L).selectAll().executeTakeFirst();if(V)return V;return await S.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 s(),null;let Y=V?.message??"";if(Y.includes("UNIQUE constraint")||Y.includes("Duplicate entry"))return await S.selectFrom("queue_circuit_state").where("queue_name","=",L).selectAll().executeTakeFirst();throw V}}async function FL(L){try{let $=await S.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 S.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 s(),!1;throw $}}async function QL(L,$={}){let V=$.windowSeconds??300,Y=new Date().toISOString().slice(0,19).replace("T"," "),G=await X4(L,Y);if(!G)return;try{if(K4(G.window_start,V)){await S.updateTable("queue_circuit_state").set({success_count:1,failure_count:0,window_start:Y}).where("queue_name","=",L).execute();return}await S.updateTable("queue_circuit_state").set({success_count:G.success_count+1}).where("queue_name","=",L).execute()}catch(U){if(B(U)){s();return}throw U}}async function HL(L,$={}){let V=$.failureRateThreshold??0.5,Y=$.windowSeconds??300,G=$.pauseSeconds??300,U=$.minObservations??10,K=new Date,W=K.toISOString().slice(0,19).replace("T"," "),X=await X4(L,W);if(!X)return!1;if(X.paused_at)return!1;try{let{success_count:z,failure_count:_}=X;if(K4(X.window_start,Y))z=0,_=0;_+=1;let A=z+_,x=A===0?0:_/A;if(A>=U&&x>=V){let Z=new Date(K.getTime()+G*1000).toISOString().slice(0,19).replace("T"," ");return await S.updateTable("queue_circuit_state").set({success_count:z,failure_count:_,window_start:W,paused_at:W,resume_at:Z}).where("queue_name","=",L).execute(),!0}return await S.updateTable("queue_circuit_state").set({success_count:z,failure_count:_,window_start:K4(X.window_start,Y)?W:X.window_start}).where("queue_name","=",L).execute(),!1}catch(z){if(B(z))return s(),!1;throw z}}function K4(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 AL(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 X4(L,Y);try{await S.updateTable("queue_circuit_state").set({paused_at:Y,resume_at:G}).where("queue_name","=",L).execute()}catch(U){if(B(U)){s();return}throw U}}async function DL(L){try{await S.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($)){s();return}throw $}}async function xL(){try{return await S.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch(L){if(B(L))return s(),[];throw L}}var OL=!1;var P1=()=>{};a();import{env as F0}from"@stacksjs/env";function I4(){return F0.QUEUE_DRIVER||"sync"}class v4{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(() => i1);if($()){V()?.dispatch(this.name||"UnknownJob",L,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let Y=I4();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=I4();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=y(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}),U=j(G),{db:K}=await import("@stacksjs/database");await K.insertInto("jobs").values({queue:this.queue||"default",payload:U,attempts:0,reserved_at:null,available_at:Y,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(L,$){let V=y(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});_1(V);let{RedisQueue:Y}=await Promise.resolve().then(() => (F1(),O1)),{queue:G}=await import("@stacksjs/config"),U=G?.connections?.redis;if(!U)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new Y(this.queue||"default",U).add(V,{delay:$?.delay,maxTries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0})}}x1();r1();S1();C1();P1();a();var z4=(L)=>`__job_progress__:${L}`,W4=(L)=>`__job_cancel__:${L}`;function l0(L){return Number.isFinite(L)?Math.max(0,Math.min(100,L)):0}async function i0(L,$,V){let{cache:Y}=await import("@stacksjs/cache");await Y.set(z4(L),{percent:l0($),message:V,updatedAt:Date.now()},3600)}async function c0(L){let{cache:$}=await import("@stacksjs/cache");return await $.get(z4(L))??null}async function d0(L){let{cache:$}=await import("@stacksjs/cache");await $.set(W4(L),1,3600)}async function n0(L){let{cache:$}=await import("@stacksjs/cache");return Boolean(await $.get(W4(L)))}async function s0(L){let{cache:$}=await import("@stacksjs/cache");await Promise.all([$.del(z4(L)),$.del(W4(L))])}import{log as K1}from"@stacksjs/logging";import*as PL from"@stacksjs/path";class ML{jobs=new Map;initialized=!1;register(L){this.jobs.set(L.name,L),K1.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 r=new ML;async function _4(L){let $=L||PL.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 K=await r0(U);if(K)V.push(K),r.register(K)}catch(K){K1.warn(`Failed to load job from ${U}: ${K.message}`)}}return r.setInitialized(!0),K1.info(`Discovered ${V.length} jobs from ${$}`),V}catch(Y){return K1.error(`Failed to discover jobs: ${Y.message}`),[]}}async function r0(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 K1.debug(`Could not load job from ${L}: ${$.message}`),null}}function t0(L){return r.get(L)}function o0(){return r.all()}function Z4(){return r.scheduled()}async function a0(L,$){let V=r.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 K1.error(`Failed to execute job "${L}": ${Y.message}`),Y}}function e0(L){let $=L.config;return{name:$.name,queue:$.queue,tries:$.tries,backoff:$.backoff,timeout:$.timeout,backoffConfig:$.backoffConfig,rate:$.rate}}import{log as E}from"@stacksjs/logging";k();import{log as TL}from"@stacksjs/logging";var BL=!1;async function EL(){if(BL)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(),BL=!0,!0}catch(L){return TL.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${L instanceof Error?L.message:String(L)}`),!1}}async function NL(L){if(!await EL())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 JL(L,$){if(!await EL())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 L9(L){return`%"jobName":"${L.replace(/[\\%_]/g,(V)=>`\\${V}`)}"%`}async function SL(L){try{let{db:$}=await import("@stacksjs/database"),V=await $.unsafe("SELECT 1 AS present FROM jobs WHERE payload LIKE ? ESCAPE '\\' LIMIT 1",[L9(L)]).execute(),Y=Array.isArray(V)?V:V?.rows??[];return Array.isArray(Y)&&Y.length>0}catch($){return TL.debug(`[scheduler] overlap check unavailable, dispatching anyway: ${$ instanceof Error?$.message:String($)}`),!1}}q1();var IL={checkInterval:60000,preventOverlapping:!0},P={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...IL}},RL=new Set;function $9(L,$){if(RL.has(L))return;RL.add(L),E.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 CL=!1,qL=new Map;function V9(L){let $=qL.get(L);if(!$)$=new Intl.DateTimeFormat("en-US",{timeZone:L,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),qL.set(L,$);return $}function O4(L,$){if($&&$!=="local"&&$!=="system")try{let V=V9($).formatToParts(L),Y=(K)=>V.find((W)=>W.type===K)?.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(!CL)CL=!0,E.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 Y9(L,$,V){let Y=O4(new Date,V),G=Y.minute,U=Y.hour,K=Y.day,W=Y.month,X=Y.dayOfWeek;if($){let F=O4($,V);if(F.minute===G&&F.hour===U&&F.day===K)return!1}let z=L.trim().split(/\s+/);if(z.length===6){let F=z[0];if(F&&F!=="0"&&F!=="*")$9(L,F);z=z.slice(1)}if(z.length<5)return E.warn(`Invalid cron expression: ${L}`),!1;let[_,A,x,O,Z]=z;return f(_,G,0,59)&&f(A,U,0,23)&&f(x,K,1,31)&&f(O,W,1,12)&&f(Z,X,0,6)}function f(L,$,V,Y){if(L==="*")return!0;if(L.includes(","))return L.split(",").map((K)=>Number.parseInt(K.trim(),10)).includes($);if(L.includes("-")){let[U,K]=L.split("-").map((W)=>Number.parseInt(W.trim(),10));return $>=U&&$<=K}if(L.includes("/")){let[U,K]=L.split("/"),W=Number.parseInt(K,10);if(U==="*")return $%W===0;if(U.includes("-")){let[X,z]=U.split("-").map((_)=>Number.parseInt(_.trim(),10));return $>=X&&$<=z&&($-X)%W===0}}let G=Number.parseInt(L,10);return!Number.isNaN(G)&&$===G}function vL(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 yL(L,$){let V=L.trim().split(/\s+/),Y=V.length===6?V.slice(1):V;if(Y.length<5)return null;let[G,U,K,W,X]=Y,z=new Date;z.setSeconds(0,0);let _=527040;for(let A=1;A<=_;A++){let x=new Date(z.getTime()+A*60000),O=O4(x,$);if(f(G,O.minute,0,59)&&f(U,O.hour,0,23)&&f(K,O.day,1,31)&&f(W,O.month,1,12)&&f(X,O.dayOfWeek,0,6))return x}return null}async function G9(L={}){if(P.isRunning){E.warn("Scheduler is already running");return}P.config={...IL,...L},P.isRunning=!0,P.isShuttingDown=!1,await _4();let $=Z4();for(let G of $){let U=G.config.rate||G.config.schedule;if(U){let K=vL(U);if(K){let W=await NL(G.name);P.jobs.set(G.name,{job:G,lastRun:W,nextRun:yL(K,P.config.timezone),isRunning:!1}),E.info(`Registered scheduled job: ${G.name} (${K})`)}else E.warn(`Invalid schedule for job ${G.name}: ${U}`)}}if(P.jobs.size===0){E.info("No scheduled jobs found");return}E.info(`Scheduler started with ${P.jobs.size} job(s)`),process.on("SIGINT",()=>F4()),process.on("SIGTERM",()=>F4());let V=!1,Y=()=>{if(P.isShuttingDown)return;let G=P.config.checkInterval,U=G-Date.now()%G;P.checkInterval=setTimeout(()=>{if(!P.isShuttingDown&&!V)V=!0,kL().catch((K)=>E.error("Scheduler check failed:",K)).finally(()=>{V=!1});Y()},U),P.checkInterval?.unref?.()};Y(),await kL()}async function kL(){for(let[L,$]of P.jobs){let V=$.job.config.rate||$.job.config.schedule;if(!V)continue;let Y=vL(V);if(!Y)continue;if(Y9(Y,$.lastRun,P.config.timezone)){if((P.config.preventOverlapping||$.job.config.withoutOverlapping)&&await SL(L)){E.debug(`Skipping ${L}: previous execution still running`);continue}if($.isRunning){E.debug(`Skipping ${L}: a dispatch for it is already in flight`);continue}try{$.isRunning=!0,$.lastRun=new Date,$.nextRun=yL(Y,P.config.timezone),await JL(L,$.lastRun),E.info(`Dispatching scheduled job: ${L}`),await d("job:added",{jobId:`scheduled-${L}-${Date.now()}`,queueName:$.job.config.queue||"default",jobName:L}),await t1(L,{queue:$.job.config.queue||"default",payload:{},maxTries:$.job.config.tries||3,timeout:$.job.config.timeout||60}),$.isRunning=!1,E.info(`Scheduled job ${L} dispatched to queue`)}catch(U){$.isRunning=!1,E.error(`Failed to dispatch scheduled job ${L}:`,U)}}}}async function F4(){if(!P.isRunning)return;if(E.info("Stopping scheduler..."),P.isShuttingDown=!0,P.checkInterval)clearTimeout(P.checkInterval),P.checkInterval=null;P.isRunning=!1,P.jobs.clear(),E.info("Scheduler stopped")}function U9(){return{isRunning:P.isRunning,jobCount:P.jobs.size,jobs:Array.from(P.jobs.entries()).map(([L,$])=>({name:L,schedule:$.job.config.rate||$.job.config.schedule,lastRun:$.lastRun,nextRun:$.nextRun,isRunning:$.isRunning}))}}function K9(){return P.isRunning}function X9(){return new Map(P.jobs)}async function z9(L){let $=P.jobs.get(L);if(!$)throw Error(`Scheduled job "${L}" not found`);E.info(`Manually triggering scheduled job: ${L}`),await t1(L,{queue:$.job.config.queue||"default",payload:{},maxTries:$.job.config.tries||3,timeout:$.job.config.timeout||60})}k();k();import{log as W9}from"@stacksjs/logging";var _9={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function Q4(L={}){let $={..._9,...L},V=[],Y=new Date,G=Math.floor(Y.getTime()/1000);try{let{db:U}=await import("@stacksjs/database"),K=await U.selectFrom("jobs").selectAll().execute(),W=await U.selectFrom("failed_jobs").selectAll().execute(),X=new Map;for(let H of K){let M=H.queue||"default";if(!X.has(M))X.set(M,{pending:0,processing:0,delayed:0});let J=X.get(M);if(H.reserved_at)J.processing++;else if(H.available_at&&H.available_at>G)J.delayed++;else if(J.pending++,H.created_at){let q=typeof H.created_at==="number"?H.created_at:Math.floor(new Date(H.created_at).getTime()/1000),M4=G-q;if(!J.oldestAge||M4>J.oldestAge)J.oldestAge=M4}}let z=new Map;for(let H of W){let M=H.queue||"default";z.set(M,(z.get(M)||0)+1)}let _=[],A=new Set([...X.keys(),...z.keys()]),x=$.queues?[...A].filter((H)=>$.queues.includes(H)):[...A],O=0,Z=0,F=0,I=0;for(let H of x){let M=X.get(H)||{pending:0,processing:0,delayed:0},J=z.get(H)||0;O+=M.pending,Z+=M.processing,F+=M.delayed,I+=J;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(J>=$.maxFailedCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${H}" has ${J} failed jobs (threshold: ${$.maxFailedCritical})`,queue:H,timestamp:Y.toISOString()});else if(J>=$.maxFailedWarning){if(q==="healthy")q="degraded";V.push({level:"warning",message:`Queue "${H}" has ${J} 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()})}}_.push({name:H,status:q,pending:M.pending,processing:M.processing,delayed:M.delayed,failed:J,oldestJobAge:M.oldestAge})}let x4=v1().getMetrics(),uL=x4.throughputPerMinute,lL=x4.averageDuration,P4=O+Z+F+I,W1=P4>0?I/P4:0,o="healthy";if(_.some((H)=>H.status==="unhealthy"))o="unhealthy";else if(_.some((H)=>H.status==="degraded"))o="degraded";if(W1>=$.maxErrorRateCritical)o="unhealthy",V.push({level:"critical",message:`Overall error rate is ${(W1*100).toFixed(1)}% (threshold: ${$.maxErrorRateCritical*100}%)`,timestamp:Y.toISOString()});else if(W1>=$.maxErrorRateWarning){if(o==="healthy")o="degraded";V.push({level:"warning",message:`Overall error rate is ${(W1*100).toFixed(1)}% (threshold: ${$.maxErrorRateWarning*100}%)`,timestamp:Y.toISOString()})}let iL=A1().getAll().map((H)=>({id:H.id,status:H.status,queue:H.queue,processedCount:H.processedCount,failedCount:H.failedCount,lastActivityAt:H.lastActivityAt}));return{status:o,timestamp:Y.toISOString(),queues:_,workers:iL,metrics:{totalPending:O,totalProcessing:Z,totalDelayed:F,totalFailed:I,throughputPerMinute:uL,averageProcessingTime:lL,errorRate:W1},alerts:V}}catch(U){W9.error("Failed to perform queue health check:",U);let W=A1().getAll().map((X)=>({id:X.id,status:X.status,queue:X.queue,processedCount:X.processedCount,failedCount:X.failedCount,lastActivityAt:X.lastActivityAt}));return{status:"unhealthy",timestamp:Y.toISOString(),queues:[],workers:W,metrics:{totalPending:0,totalProcessing:0,totalDelayed:0,totalFailed:0,throughputPerMinute:0,averageProcessingTime:0,errorRate:0},alerts:[{level:"critical",message:`Health check failed: ${U.message}`,timestamp:Y.toISOString()}]}}}function Z9(L={}){return async($)=>{let V=await Q4(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 O9(L={}){return(await Q4(L)).status==="healthy"}import{log as C}from"@stacksjs/logging";var F9={"&":"&","<":"<",">":">",'"':""","'":"'"};function M1(L){return String(L).replace(/[&<>"']/g,($)=>F9[$]??$)}class H4{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:h1,getOwnPropertyNames:nL,getOwnPropertyDescriptor:sL}=Object,rL=Object.prototype.hasOwnProperty;function tL(L){return this[L]}var oL=(L)=>{var $=(B4??=new WeakMap).get(L),V;if($)return $;if($=h1({},"__esModule",{value:!0}),L&&typeof L==="object"||typeof L==="function"){for(var Y of nL(L))if(!rL.call($,Y))h1($,Y,{get:tL.bind(L,Y),enumerable:!(V=sL(L,Y))||V.enumerable})}return B4.set(L,$),$},B4;var aL=(L)=>L;function eL(L,$){this[L]=aL.bind(null,$)}var p=(L,$)=>{for(var V in $)h1(L,V,{get:$[V],enumerable:!0,configurable:!0,set:eL.bind($,V)})};var v=(L,$)=>()=>(L&&($=L(L=0)),$);var Q=import.meta.require;function y(L,$,V,Y){return{jobName:L,payload:$,options:V,envelopeVersion:1,dispatchedAt:new Date().toISOString(),...Y?{traceId:Y}:{}}}function j(L){try{return JSON.stringify(L)}catch($){throw $0(L,$)}}function _1(L){j(L)}function $0(L,$){if(V0($)){let G=null;try{G=g1(L,"",new Set)}catch{}let U=G?`\`${G.path}\` is a BigInt (${G.value}n)`:"it holds a BigInt";return Error(`[queue] Job "${L.jobName}" was not dispatched: ${U}, `+"and job envelopes travel as JSON, which cannot represent one. Convert it where you dispatch \u2014 "+"`String(value)` keeps every digit, `Number(value)` is exact below 2^53 \u2014 and convert it back inside "+"the job handler. See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts.",{cause:$})}if(Y0($))return Error(`[queue] Job "${L.jobName}" was not dispatched: its payload contains a circular reference, which JSON cannot represent. Send the ids of the things the payload points at rather than the `+`objects themselves \u2014 that also keeps the job replayable once those objects have changed. ${"See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts."}`,{cause:$});let Y=$ instanceof Error?$.message:String($);return Error(`[queue] Job "${L.jobName}" was not dispatched: serializing its payload threw (${Y}). `+"JSON rejects only two things on its own and this was neither, so the throw came from the payload \u2014 "+"a `toJSON()` or a property getter. See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts.",{cause:$})}function V0(L){return L instanceof TypeError&&/bigint/i.test(L.message)}function Y0(L){return L instanceof TypeError&&/circular|cyclic/i.test(L.message)}function g1(L,$,V){if(typeof L==="bigint")return{path:$||"envelope",value:L};if(!L||typeof L!=="object")return null;if(V.has(L))return null;if(V.add(L),Array.isArray(L)){for(let Y=0;Y<L.length;Y++){let G=g1(L[Y],`${$}[${Y}]`,V);if(G)return G}return null}for(let[Y,G]of Object.entries(L)){let U=g1(G,$?`${$}.${Y}`:Y,V);if(U)return U}return null}function T4(L,$){if(p1.has(L))return;p1.add(L),console.warn($)}function G0(){p1.clear()}function T1(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(),...typeof $.traceId==="string"&&$.traceId?{traceId:$.traceId}:{}},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 T4("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(),...typeof $.traceId==="string"&&$.traceId?{traceId:$.traceId}:{}},source:"v0-implicit"};if(typeof $.job==="string")return T4("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 L0=1,p1;var a=v(()=>{p1=new Set});var i1={};p(i1,{runJob:()=>R4,restore:()=>l1,isFaked:()=>J4,getFakeQueue:()=>N4,fake:()=>b1,expectJobToFail:()=>C4,createQueueTester:()=>S4,QueueTester:()=>E1});class E4{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 b1(){return Z1=new E4,Z1}function N4(){return Z1}function J4(){return Z1!==null}function l1(){Z1=null}class E1{queue;constructor(){this.queue=b1()}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(){l1()}}function S4(){return new E1}async function R4(L,$){return await L.handle($)}async function C4(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 Z1=null;var O1={};p(O1,{setQueueManager:()=>F0,getQueueManager:()=>Z0,dispatchSync:()=>_0,dispatchAfter:()=>W0,dispatch:()=>z0,createRedisDispatcher:()=>Q0,chain:()=>X0,batch:()=>K0,StacksQueueManager:()=>q4,RedisQueue:()=>e,RedisJob:()=>k4,QueueManager:()=>O0});import{Queue as U0,batch as K0,chain as X0,dispatch as z0,dispatchAfter as W0,dispatchSync as _0,getQueueManager as Z0,QueueManager as O0,setQueueManager as F0}from"@stacksjs/bun-queue";import{log as c1}from"@stacksjs/logging";class e{queue;config;isProcessing=!1;constructor(L,$){this.config=$,this.queue=new U0(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}),c1.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){c1.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process(L,$),c1.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 q4{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 e($,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 e($,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 Q0(L,$){let V=new e(L,$);return async(Y,G)=>{return V.add(Y,G)}}class k4{data;options={};queue;constructor(L,$,V){this.data=V;this.queue=new e(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 F1=()=>{};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 m4={};p(m4,{retryDeadLetterJob:()=>f4,purgeDeadLetterJobs:()=>j4,moveToDeadLetter:()=>J1,listDeadLetterJobs:()=>w4});import{db as L1}from"@stacksjs/database";function N1(){if(y4)return;y4=!0,console.warn("[queue/dlq] dead_letter_jobs table missing \u2014 DLQ disabled. "+"Run migrations to enable poison-message isolation.")}async function J1(L,$,V=1){let Y=new Date().toISOString().slice(0,19).replace("T"," ");try{return await L1.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 N1(),!1;throw G}}async function w4(L={}){try{let $=L1.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 N1(),[];throw $}}async function f4(L){try{let $=await L1.selectFrom("dead_letter_jobs").where("id","=",L).selectAll().executeTakeFirst();if(!$)return!1;let V=Math.floor(Date.now()/1000);return await L1.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 L1.deleteFrom("dead_letter_jobs").where("id","=",L).execute(),!0}catch($){if(B($))return N1(),!1;throw $}}async function j4(L=30){try{let $=new Date(Date.now()-L*24*60*60*1000).toISOString().slice(0,19).replace("T"," "),V=await L1.deleteFrom("dead_letter_jobs").where("dead_lettered_at","<",$).execute();return Number(V?.numDeletedRows??V?.[0]?.numDeletedRows??V?.affectedRows??0)}catch($){if(B($))return N1(),0;throw $}}var y4=!1;var S1=()=>{};function d1(){if(h4)return;h4=!0,console.warn("[queue/idempotency] job_idempotency table missing \u2014 idempotency keys are accepted but NOT enforced. "+"Run migrations to enable dedup.")}async function A0(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 d1(),!1;throw $}}async function D0(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)){d1();return}let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return;throw Y}}async function n1(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 d1(),"unenforced";let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return"duplicate";throw Y}}async function s1(L){try{let{db:$}=await import("@stacksjs/database");await $.deleteFrom("job_idempotency").where("idempotency_key","=",L).execute()}catch{}}var h4=!1;var r1=()=>{};var i4={};p(i4,{unquarantineJob:()=>b4,recordFailureForPoison:()=>p4,quarantineJob:()=>u4,listQuarantined:()=>l4,isQuarantined:()=>R1,hashPayload:()=>H1});import{createHash as x0}from"crypto";import{db as C}from"@stacksjs/database";function Q1(){if(g4)return;g4=!0,console.warn("[queue/poison] job_quarantine table missing \u2014 poison detection disabled. "+"Run migrations to enable.")}function H1(L){let $;try{$=typeof L==="string"?L:JSON.stringify(L??null)}catch{$=String(L)}return x0("sha256").update($).digest("hex").slice(0,32)}async function p4(L,$,V={}){let Y=V.maxFailures??5,G=V.windowMinutes??60,U=H1($),K=new Date,W=K.toISOString().slice(0,19).replace("T"," ");try{let X=await C.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","=",U).selectAll().executeTakeFirst();if(!X)return await C.insertInto("job_quarantine").values({job_name:L,payload_hash:U,failure_count:1,window_start:W,quarantined_at:null}).execute(),!1;if(X.quarantined_at)return!0;let z=Date.parse(X.window_start.replace(" ","T")+"Z"),_=K.getTime()-z,H=G*60*1000;if(Number.isFinite(z)&&_>H)return await C.updateTable("job_quarantine").set({failure_count:1,window_start:W}).where("id","=",X.id).execute(),!1;let x=X.failure_count+1;if(x>=Y)return await C.updateTable("job_quarantine").set({failure_count:x,quarantined_at:W}).where("id","=",X.id).execute(),!0;return await C.updateTable("job_quarantine").set({failure_count:x}).where("id","=",X.id).execute(),!1}catch(X){if(B(X))return Q1(),!1;throw X}}async function R1(L,$){let V=H1($);try{let Y=await C.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 Q1(),!1;throw Y}}async function u4(L,$){let V=$===void 0?"*":H1($),Y=new Date().toISOString().slice(0,19).replace("T"," ");try{let G=await C.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","=",V).select(["id"]).executeTakeFirst();if(G)await C.updateTable("job_quarantine").set({quarantined_at:Y}).where("id","=",G.id).execute();else await C.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)){Q1();return}throw G}}async function b4(L){try{await C.deleteFrom("job_quarantine").where("job_name","=",L).execute()}catch($){if(B($)){Q1();return}throw $}}async function l4(){try{return await C.selectFrom("job_quarantine").whereNotNull("quarantined_at").selectAll().execute()??[]}catch(L){if(B(L))return Q1(),[];throw L}}var g4=!1;var C1=()=>{};function c(L){let $=L?.numUpdatedRows;if($===null||$===void 0)return 0;if(typeof $==="object")return Number($.changes??0);return Number($)}function P0(L,$){let V=y(L,$.payload||{},{queue:$.queue,tries:$.maxTries,timeout:$.timeout,backoff:Array.isArray($.backoff)?$.backoff:void 0});return{queue:$.queue||"default",payload:j(V),attempts:0,available_at:M0($.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}}async function t1(L,$){let V=P0(L,$),{db:Y}=await import("@stacksjs/database");await Y.insertInto("jobs").values(V).execute()}function M0(L){let $=Date.now();return Math.floor($/1000+L)}var q1=v(()=>{a()});var u={};p(u,{withEvents:()=>d4,onQueueEvent:()=>c4,getWorkerTracker:()=>A1,getQueueEvents:()=>$1,getGlobalMetrics:()=>v1,emitQueueEvent:()=>d,QueueMetrics:()=>I1,QueueEvents:()=>k1,OnQueueEvent:()=>n4});import{log as m}from"@stacksjs/logging";class k1{handlers=new Map;wildcardHandlers=new Set;listenerSubscriptions=new WeakMap;reclaim=new FinalizationRegistry((L)=>{L()});on(L,$){if(!this.handlers.has(L))this.handlers.set(L,new Set);return this.handlers.get(L).add($),()=>{this.handlers.get(L)?.delete($)}}subscribeListener(L,$,V){let Y=new WeakRef(L),G=(X)=>{let z=Y.deref();if(z===void 0){U();return}return V.call(z,X)},U=this.on($,G),K=()=>{U();let X=Y.deref();if(X===void 0)return;let z=this.listenerSubscriptions.get(X);if(!z)return;if(z.delete(K),z.size===0)this.listenerSubscriptions.delete(X),this.reclaim.unregister(X)},W=this.listenerSubscriptions.get(L);if(!W)W=new Set,this.listenerSubscriptions.set(L,W);return W.add(K),this.reclaim.register(L,U,L),K}unsubscribeListener(L){let $=this.listenerSubscriptions.get(L);if(!$)return 0;let V=Array.from($);for(let Y of V)Y();return this.listenerSubscriptions.delete(L),this.reclaim.unregister(L),V.length}listenerCount(L){if(L==="*")return this.wildcardHandlers.size;return this.handlers.get(L)?.size??0}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){m.error(`Error in queue event handler for ${L}:`,U)}for(let G of this.wildcardHandlers)try{await G(L,V)}catch(U){m.error("Error in wildcard queue event handler:",U)}}logEvent(L,$){let V=$.jobId?`[${$.jobId}]`:"",Y=$.queueName?`on ${$.queueName}`:"";switch(L){case"job:added":m.debug(`Job added ${V} ${Y}`);break;case"job:processing":m.debug(`Job processing ${V} ${Y}`);break;case"job:completed":m.info(`Job completed ${V} ${Y} in ${$.duration}ms`);break;case"job:failed":m.error(`Job failed ${V} ${Y}:`,$.error);break;case"job:retrying":m.warn(`Job retrying ${V} ${Y} (attempt ${$.attemptsMade})`);break;case"job:stalled":m.warn(`Job stalled ${V} ${Y}`);break;case"queue:error":m.error(`Queue error ${Y}:`,$.error);break}}off(L){this.handlers.delete(L)}removeAllListeners(){this.handlers.clear(),this.wildcardHandlers.clear(),this.listenerSubscriptions=new WeakMap}}function $1(){if(!o1)o1=new k1;return o1}function c4(L,$){let V=$1();if(L==="*")return V.onAny($);return V.on(L,$)}function d(L,$){return $1().emit(L,$)}function d4(L,$){return async(...V)=>{let Y=V[0]?.id||"unknown",G=Date.now();await d("job:processing",{jobId:Y,queueName:L,data:V[0]?.data});try{let U=await $(...V);return await d("job:completed",{jobId:Y,queueName:L,result:U,duration:Date.now()-G}),U}catch(U){throw await d("job:failed",{jobId:Y,queueName:L,error:U,duration:Date.now()-G}),U}}}function n4(L){return function($,V){if(typeof V!=="object"||V===null)throw TypeError(`@OnQueueEvent('${L}') requires standard (TC39) decorators. Legacy decorators ('experimentalDecorators: true') give a method decorator no construction-time hook, so the handler cannot be bound to an instance.`);if(V.kind!=="method")throw TypeError(`@OnQueueEvent('${L}') can only decorate a class method, but it was applied to a ${V.kind}${V.name===void 0?"":` ('${String(V.name)}')`}. Move the handler into a method, or call onQueueEvent('${L}', handler) directly.`);if(V.static)throw TypeError(`@OnQueueEvent('${L}') cannot decorate the static method '${String(V.name)}': a static method has no instance, so it would subscribe at class-definition time and stay subscribed for the life of the process, even if the class is never used. Use an instance method, or subscribe explicitly with onQueueEvent('${L}', MyClass.${String(V.name)}).`);return V.addInitializer(function(){$1().subscribeListener(this,L,$)}),$}}class I1{jobCounts={added:0,completed:0,failed:0,processing:0};completions=[];errors=[];unsubscribe=[];constructor(){this.setupListeners()}setupListeners(){let L=$1();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(!a1)a1=new I1;return a1}class s4{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 A1(){return B0}var o1=null,a1=null,B0;var k=v(()=>{B0=new s4});var l={};p(l,{recordBatchJobFailure:()=>U4,recordBatchJobCompletion:()=>G4,isBatchCancelled:()=>UL,getBatchCallbacks:()=>D1,batchRecordToHash:()=>$L,batchRecordFromHash:()=>Y4,batchCounterIncrements:()=>r4,PendingBatch:()=>w1,DispatchedBatch:()=>b,Batch:()=>e1});var{RedisClient:T0}=globalThis.Bun;import{log as T}from"@stacksjs/logging";import{env as E0}from"@stacksjs/env";function h(){return E0.QUEUE_DRIVER||"sync"}class w1{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=h();await J0({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}),N0(L,this.options);try{let{emitQueueEvent:Y}=await Promise.resolve().then(() => (k(),u));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:K}=G,W={...K,_batchId:L,_batchIndex:Y};if(this.options.queue&&!U.queue)U.queue=this.options.queue;if(V==="sync")try{await U.dispatchNow(W),await G4(L)}catch(X){await U4(L,`${L}:${Y}`,X)}else await U.dispatch(W)}return T.info(`[Batch] Dispatched batch "${this.options.name||L}" with ${$} jobs`),new b(L)}getJobs(){return[...this.jobs]}getOptions(){return this.options}}class b{id;constructor(L){this.id=L}async fresh(){return n(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(h()==="redis")await j0(this.id);else await v0(this.id);T.info(`[Batch] Cancelled batch ${this.id}`);let $=D1(this.id);if($)for(let V of $.finallyCallbacks)try{await V(this)}catch(Y){T.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((U)=>("job"in U)?U:{job:U});if(!await C0(this.id,V.length))throw Error(`Batch ${this.id} was cancelled, finished or deleted before the jobs could be added`);let G=JSON.parse($.options||"{}");for(let U=0;U<V.length;U++){let K=V[U];if(!K)continue;let{job:W,payload:X}=K,z={...X,_batchId:this.id,_batchIndex:$.total_jobs+U};if(G.queue&&!W.queue)W.queue=G.queue;await W.dispatch(z)}T.info(`[Batch] Added ${V.length} jobs to batch ${this.id}`)}async delete(){await q0(this.id),y1(this.id)}}class e1{static create(L){return new w1(L)}static async find(L){if(!await n(L))return null;return new b(L)}static async all(){return(await S0()).map(($)=>new b($.id))}static async prune(L=24){return k0(L)}}function N0(L,$){L4.set(L,$)}function D1(L){return L4.get(L)}function y1(L){L4.delete(L)}async function J0(L){if(h()==="redis")await w0(L);else await t4(L)}async function n(L){if(h()==="redis")return f0(L);return o4(L)}async function S0(){if(h()==="redis")return VL();return a4()}async function R0(L,$){if(h()==="redis")await YL(L,$);else await $4(L,$)}async function C0(L,$){if($===0)return!0;if(h()==="redis")try{let U=await U1(),K=`${G1}${L}`;return await U.hincrby(K,"total_jobs",$),await U.hincrby(K,"pending_jobs",$),U.close(),!0}catch{}let{db:V,sql:Y}=await import("@stacksjs/database"),G=await V.updateTable("job_batches").set(r4(Y,$)).where("id","=",L).whereNull("cancelled_at").whereNull("finished_at").executeTakeFirst();return c(G)>0}function r4(L,$){return{total_jobs:L`total_jobs + ${$}`,pending_jobs:L`pending_jobs + ${$}`}}async function q0(L){if(h()==="redis")await GL(L);else await e4(L)}async function k0(L){if(h()==="redis")return m0(L);return LL(L)}function I0(L){return!!(L.then_handler||L.catch_handler||L.finally_handler)}async function t4(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(!I0(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){T.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 o4(L){let{db:$}=await import("@stacksjs/database");return await $.selectFrom("job_batches").where("id","=",L).selectAll().executeTakeFirst()||null}async function a4(){let{db:L}=await import("@stacksjs/database");return await L.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function $4(L,$){let{db:V}=await import("@stacksjs/database");await V.updateTable("job_batches").set($).where("id","=",L).execute()}async function e4(L){let{db:$}=await import("@stacksjs/database");await $.deleteFrom("job_batches").where("id","=",L).execute()}async function v0(L){await $4(L,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function LL(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 y0(){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 U1(){let L=new T0(await y0());return await L.connect(),L}function $L(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 w0(L){try{let $=await U1();await $.hset(`${G1}${L.id}`,$L(L)),await $.sadd(V4,L.id),$.close()}catch{await t4(L)}}function Y4(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 f0(L){try{let $=await U1(),V=`${G1}${L}`,Y=await $.hgetall(V);return $.close(),Y4(Y)}catch{return o4(L)}}async function VL(){try{let L=await U1(),$=await L.smembers(V4),V=[];for(let Y of $){let G=Y4(await L.hgetall(`${G1}${Y}`));if(G)V.push(G)}return L.close(),V}catch{return a4()}}async function YL(L,$){try{let V=await U1(),Y=`${G1}${L}`,G={};for(let[U,K]of Object.entries($))G[U]=K===null?"":String(K);await V.hset(Y,G),V.close()}catch{await $4(L,$)}}async function GL(L){try{let $=await U1();await $.del(`${G1}${L}`),await $.srem(V4,L),$.close()}catch{await e4(L)}}async function j0(L){let $=new Date().toISOString().slice(0,19).replace("T"," ");await YL(L,{cancelled_at:$,finished_at:$})}async function m0(L){try{let $=await VL(),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 GL(G.id),Y++}return Y}catch{return LL(L)}}function V1(L){if(!L)return null;try{let $=JSON.parse(L);if($&&($.kind==="job"||$.kind==="module"))return $;return T.warn(`[Batch] handler JSON has unknown kind '${$?.kind}' \u2014 skipping`),null}catch($){return T.warn(`[Batch] failed to parse persistent handler: ${$.message}`),null}}async function Y1(L,$){try{if(L.kind==="job"){let{Jobs:G}=await Promise.resolve().then(() => (x1(),f1));await G.dispatch(L.name,{...L.payload??{},_batchId:$});return}let V=await import(L.module).catch((G)=>{return T.warn(`[Batch] persistent handler module not found: ${L.module} (${G.message})`),null});if(!V)return;let Y=V[L.export];if(typeof Y!=="function"){T.warn(`[Batch] persistent handler export '${L.export}' is not a function on ${L.module}`);return}await Y(L.payload,$)}catch(V){T.error(`[Batch] persistent handler threw for batch ${$}:`,V)}}async function G4(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=c(G)>0,K=D1(L),W=new b(L);if(K)for(let X of K.progressCallbacks)try{await X(W)}catch(z){T.error(`[Batch] Error in progress callback for batch ${L}:`,z)}if(U){try{let{emitQueueEvent:O}=await Promise.resolve().then(() => (k(),u));await O("batch:completed",{jobId:L})}catch{}let X=await n(L);if(!X){y1(L),T.info(`[Batch] Batch ${L} finished (record vanished)`);return}let z=JSON.parse(X.options||"{}"),H=!((X.failed_jobs||0)>0)||z.allowFailures;if(K){if(H)for(let O of K.thenCallbacks)try{await O(W)}catch(Z){T.error(`[Batch] Error in then callback for batch ${L}:`,Z)}for(let O of K.finallyCallbacks)try{await O(W)}catch(Z){T.error(`[Batch] Error in finally callback for batch ${L}:`,Z)}y1(L)}if(H){let O=V1(X.then_handler);if(O)await Y1(O,L)}else{let O=V1(X.catch_handler);if(O)await Y1(O,L)}let x=V1(X.finally_handler);if(x)await Y1(x,L);T.info(`[Batch] Batch ${L} finished`)}}async function U4(L,$,V){let{db:Y,sql:G}=await import("@stacksjs/database"),U=await n(L);if(!U)return;let K=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 Z=await n(L);if(Z){let F=[];try{F=JSON.parse(Z.failed_job_ids||"[]")}catch{F=[]}F.push($),await R0(L,{failed_job_ids:JSON.stringify(F)})}}catch{}let W=new b(L),X=D1(L);if(X)for(let Z of X.catchCallbacks)try{await Z(W,V)}catch(F){T.error(`[Batch] Error in catch callback for batch ${L}:`,F)}try{let{emitQueueEvent:Z}=await Promise.resolve().then(() => (k(),u));await Z("batch:failed",{jobId:L,error:V})}catch{}let z=new Date().toISOString().slice(0,19).replace("T"," "),_=Y.updateTable("job_batches").set(K.allowFailures?{finished_at:z}:{finished_at:z,cancelled_at:z}).where("id","=",L).whereNull("finished_at");if(K.allowFailures)_=_.where("pending_jobs","=",0);let H=await _.executeTakeFirst();if(c(H)===0)return;let x=!!K.allowFailures;if(X){if(x)for(let Z of X.thenCallbacks)try{await Z(W)}catch(F){T.error(`[Batch] Error in then callback for batch ${L}:`,F)}for(let Z of X.finallyCallbacks)try{await Z(W)}catch(F){T.error(`[Batch] Error in finally callback for batch ${L}:`,F)}y1(L)}let O=await n(L);if(O){if(x){let F=V1(O.then_handler);if(F)await Y1(F,L)}else{let F=V1(O.catch_handler);if(F)await Y1(F,L)}let Z=V1(O.finally_handler);if(Z)await Y1(Z,L)}T.info(`[Batch] Batch ${L} finished with failure(s)`)}async function UL(L){return(await n(L))?.cancelled_at!==null}var L4,G1="stacks:batch:",V4="stacks:batches";var g=v(()=>{q1();L4=new Map});var f1={};p(f1,{runJob:()=>j1,resolveJobFile:()=>ZL,jobBatch:()=>_L,job:()=>zL,Jobs:()=>WL});import{appPath as h0,frameworkPath as g0}from"@stacksjs/path";import{env as p0}from"@stacksjs/env";import{enqueueAfterCommit as u0,isInTransaction as b0}from"@stacksjs/database";function l0(){return p0.QUEUE_DRIVER||"sync"}function i0(){if(KL)return;KL=!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 w{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(() => i1);if(L()){$()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(b0()){let Y=this.runDispatchPipeline.bind(this);if(u0(async()=>{await Y()}))return}else if(this.txMode==="after")i0()}await this.runDispatchPipeline()}async runDispatchPipeline(){let L=!1;if(this.options.idempotencyKey){let $=await n1(this.options.idempotencyKey,this.name,this.options.queue);if($==="duplicate")return;L=$==="claimed"}try{if(await R1(this.name,this.payload)){let V=y(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await J1({queue:this.options.queue||"default",payload:j(V),exception:`quarantined: ${this.name}`},"poison-detected"))return}let $=l0();if($==="database")await this.dispatchToDatabase();else if($==="redis")await this.dispatchToRedis();else if($==="sync")await j1(this.name,{payload:this.payload,context:this.options.context,traceId:await XL()});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 s1(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=y(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff},await XL()),Y=j(V),{db:G}=await import("@stacksjs/database");await G.insertInto("jobs").values({queue:this.options.queue||"default",payload:Y,attempts:0,reserved_at:null,available_at:$,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){let L=y(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});_1(L);let{RedisQueue:$}=await Promise.resolve().then(() => (F1(),O1)),{queue:V}=await import("@stacksjs/config"),Y=V?.connections?.redis;if(!Y)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new $(this.options.queue||"default",Y).add(L,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await j1(this.name,{payload:this.payload,context:this.options.context})}}function zL(L,$){return new w(L,$)}function _L(L){let{PendingBatch:$}=(g(),oL(l));return new $(L)}async function ZL(L){let $=[h0(`Jobs/${L}.ts`),g0(`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 j1(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 ZL(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 K=(await import(G)).default;if(!K)throw Error(`Job ${L} does not export a default`);if(typeof K.handle==="function")await K.handle($.payload);else if(typeof K.action==="string"){let{runAction:W}=await import("@stacksjs/actions");await W(K.action)}else if(typeof K.action==="function")await K.action();else if(typeof K==="function")await K($.payload,$.context);else throw Error(`Job ${L} does not have a valid handler`)})}async function XL(){try{let{getTraceId:L}=await import("@stacksjs/router");return L()}catch{return}}var KL=!1,WL;var x1=v(()=>{S1();a();r1();C1();WL={make(L,$){return new w(L,$)},async dispatch(L,$){await new w(L,$).dispatch()},async dispatchIf(L,$,V){if(L)await new w($,V).dispatch()},async dispatchUnless(L,$,V){if(!L)await new w($,V).dispatch()},async dispatchNow(L,$){await new w(L,$).dispatchNow()},dispatchAfter(L,$,V){return new w($,V).delay(L)},async dispatchOnce(L,$,V){await new w($,V).withIdempotencyKey(L).dispatch()},async dispatchAfterCommit(L,$){await new w(L,$).afterCommit().dispatch()}}});var m1={};p(m1,{resumeQueue:()=>DL,recordCircuitSuccess:()=>QL,recordCircuitFailure:()=>HL,pauseQueue:()=>AL,listCircuitState:()=>xL,isCircuitOpen:()=>FL});import{db as S}from"@stacksjs/database";function s(){if(OL)return;OL=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing \u2014 circuit breaker disabled. "+"Run migrations to enable.")}async function X4(L,$){try{let V=await S.selectFrom("queue_circuit_state").where("queue_name","=",L).selectAll().executeTakeFirst();if(V)return V;return await S.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 s(),null;let Y=V?.message??"";if(Y.includes("UNIQUE constraint")||Y.includes("Duplicate entry"))return await S.selectFrom("queue_circuit_state").where("queue_name","=",L).selectAll().executeTakeFirst();throw V}}async function FL(L){try{let $=await S.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 S.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 s(),!1;throw $}}async function QL(L,$={}){let V=$.windowSeconds??300,Y=new Date().toISOString().slice(0,19).replace("T"," "),G=await X4(L,Y);if(!G)return;try{if(K4(G.window_start,V)){await S.updateTable("queue_circuit_state").set({success_count:1,failure_count:0,window_start:Y}).where("queue_name","=",L).execute();return}await S.updateTable("queue_circuit_state").set({success_count:G.success_count+1}).where("queue_name","=",L).execute()}catch(U){if(B(U)){s();return}throw U}}async function HL(L,$={}){let V=$.failureRateThreshold??0.5,Y=$.windowSeconds??300,G=$.pauseSeconds??300,U=$.minObservations??10,K=new Date,W=K.toISOString().slice(0,19).replace("T"," "),X=await X4(L,W);if(!X)return!1;if(X.paused_at)return!1;try{let{success_count:z,failure_count:_}=X;if(K4(X.window_start,Y))z=0,_=0;_+=1;let H=z+_,x=H===0?0:_/H;if(H>=U&&x>=V){let Z=new Date(K.getTime()+G*1000).toISOString().slice(0,19).replace("T"," ");return await S.updateTable("queue_circuit_state").set({success_count:z,failure_count:_,window_start:W,paused_at:W,resume_at:Z}).where("queue_name","=",L).execute(),!0}return await S.updateTable("queue_circuit_state").set({success_count:z,failure_count:_,window_start:K4(X.window_start,Y)?W:X.window_start}).where("queue_name","=",L).execute(),!1}catch(z){if(B(z))return s(),!1;throw z}}function K4(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 AL(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 X4(L,Y);try{await S.updateTable("queue_circuit_state").set({paused_at:Y,resume_at:G}).where("queue_name","=",L).execute()}catch(U){if(B(U)){s();return}throw U}}async function DL(L){try{await S.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($)){s();return}throw $}}async function xL(){try{return await S.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch(L){if(B(L))return s(),[];throw L}}var OL=!1;var P1=()=>{};a();import{env as H0}from"@stacksjs/env";function I4(){return H0.QUEUE_DRIVER||"sync"}class v4{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(() => i1);if($()){V()?.dispatch(this.name||"UnknownJob",L,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let Y=I4();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=I4();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=y(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}),U=j(G),{db:K}=await import("@stacksjs/database");await K.insertInto("jobs").values({queue:this.queue||"default",payload:U,attempts:0,reserved_at:null,available_at:Y,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(L,$){let V=y(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});_1(V);let{RedisQueue:Y}=await Promise.resolve().then(() => (F1(),O1)),{queue:G}=await import("@stacksjs/config"),U=G?.connections?.redis;if(!U)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new Y(this.queue||"default",U).add(V,{delay:$?.delay,maxTries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0})}}x1();r1();S1();C1();P1();a();var z4=(L)=>`__job_progress__:${L}`,W4=(L)=>`__job_cancel__:${L}`;function c0(L){return Number.isFinite(L)?Math.max(0,Math.min(100,L)):0}async function d0(L,$,V){let{cache:Y}=await import("@stacksjs/cache");await Y.set(z4(L),{percent:c0($),message:V,updatedAt:Date.now()},3600)}async function n0(L){let{cache:$}=await import("@stacksjs/cache");return await $.get(z4(L))??null}async function s0(L){let{cache:$}=await import("@stacksjs/cache");await $.set(W4(L),1,3600)}async function r0(L){let{cache:$}=await import("@stacksjs/cache");return Boolean(await $.get(W4(L)))}async function t0(L){let{cache:$}=await import("@stacksjs/cache");await Promise.all([$.del(z4(L)),$.del(W4(L))])}import{log as K1}from"@stacksjs/logging";import*as PL from"@stacksjs/path";class ML{jobs=new Map;initialized=!1;register(L){this.jobs.set(L.name,L),K1.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 r=new ML;async function _4(L){let $=L||PL.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 K=await o0(U);if(K)V.push(K),r.register(K)}catch(K){K1.warn(`Failed to load job from ${U}: ${K.message}`)}}return r.setInitialized(!0),K1.info(`Discovered ${V.length} jobs from ${$}`),V}catch(Y){return K1.error(`Failed to discover jobs: ${Y.message}`),[]}}async function o0(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 K1.debug(`Could not load job from ${L}: ${$.message}`),null}}function a0(L){return r.get(L)}function e0(){return r.all()}function Z4(){return r.scheduled()}async function L9(L,$){let V=r.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 K1.error(`Failed to execute job "${L}": ${Y.message}`),Y}}function $9(L){let $=L.config;return{name:$.name,queue:$.queue,tries:$.tries,backoff:$.backoff,timeout:$.timeout,backoffConfig:$.backoffConfig,rate:$.rate}}import{log as E}from"@stacksjs/logging";k();import{log as TL}from"@stacksjs/logging";var BL=!1;async function EL(){if(BL)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(),BL=!0,!0}catch(L){return TL.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${L instanceof Error?L.message:String(L)}`),!1}}async function NL(L){if(!await EL())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 JL(L,$){if(!await EL())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 V9(L){return`%"jobName":"${L.replace(/[\\%_]/g,(V)=>`\\${V}`)}"%`}async function SL(L){try{let{db:$}=await import("@stacksjs/database"),V=await $.unsafe("SELECT 1 AS present FROM jobs WHERE payload LIKE ? ESCAPE '\\' LIMIT 1",[V9(L)]).execute(),Y=Array.isArray(V)?V:V?.rows??[];return Array.isArray(Y)&&Y.length>0}catch($){return TL.debug(`[scheduler] overlap check unavailable, dispatching anyway: ${$ instanceof Error?$.message:String($)}`),!1}}q1();var IL={checkInterval:60000,preventOverlapping:!0},P={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...IL}},RL=new Set;function Y9(L,$){if(RL.has(L))return;RL.add(L),E.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 CL=!1,qL=new Map;function G9(L){let $=qL.get(L);if(!$)$=new Intl.DateTimeFormat("en-US",{timeZone:L,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),qL.set(L,$);return $}function O4(L,$){if($&&$!=="local"&&$!=="system")try{let V=G9($).formatToParts(L),Y=(K)=>V.find((W)=>W.type===K)?.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(!CL)CL=!0,E.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 U9(L,$,V){let Y=O4(new Date,V),G=Y.minute,U=Y.hour,K=Y.day,W=Y.month,X=Y.dayOfWeek;if($){let F=O4($,V);if(F.minute===G&&F.hour===U&&F.day===K)return!1}let z=L.trim().split(/\s+/);if(z.length===6){let F=z[0];if(F&&F!=="0"&&F!=="*")Y9(L,F);z=z.slice(1)}if(z.length<5)return E.warn(`Invalid cron expression: ${L}`),!1;let[_,H,x,O,Z]=z;return f(_,G,0,59)&&f(H,U,0,23)&&f(x,K,1,31)&&f(O,W,1,12)&&f(Z,X,0,6)}function f(L,$,V,Y){if(L==="*")return!0;if(L.includes(","))return L.split(",").map((K)=>Number.parseInt(K.trim(),10)).includes($);if(L.includes("-")){let[U,K]=L.split("-").map((W)=>Number.parseInt(W.trim(),10));return $>=U&&$<=K}if(L.includes("/")){let[U,K]=L.split("/"),W=Number.parseInt(K,10);if(U==="*")return $%W===0;if(U.includes("-")){let[X,z]=U.split("-").map((_)=>Number.parseInt(_.trim(),10));return $>=X&&$<=z&&($-X)%W===0}}let G=Number.parseInt(L,10);return!Number.isNaN(G)&&$===G}function vL(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 yL(L,$){let V=L.trim().split(/\s+/),Y=V.length===6?V.slice(1):V;if(Y.length<5)return null;let[G,U,K,W,X]=Y,z=new Date;z.setSeconds(0,0);let _=527040;for(let H=1;H<=_;H++){let x=new Date(z.getTime()+H*60000),O=O4(x,$);if(f(G,O.minute,0,59)&&f(U,O.hour,0,23)&&f(K,O.day,1,31)&&f(W,O.month,1,12)&&f(X,O.dayOfWeek,0,6))return x}return null}async function K9(L={}){if(P.isRunning){E.warn("Scheduler is already running");return}P.config={...IL,...L},P.isRunning=!0,P.isShuttingDown=!1,await _4();let $=Z4();for(let G of $){let U=G.config.rate||G.config.schedule;if(U){let K=vL(U);if(K){let W=await NL(G.name);P.jobs.set(G.name,{job:G,lastRun:W,nextRun:yL(K,P.config.timezone),isRunning:!1}),E.info(`Registered scheduled job: ${G.name} (${K})`)}else E.warn(`Invalid schedule for job ${G.name}: ${U}`)}}if(P.jobs.size===0){E.info("No scheduled jobs found");return}E.info(`Scheduler started with ${P.jobs.size} job(s)`),process.on("SIGINT",()=>F4()),process.on("SIGTERM",()=>F4());let V=!1,Y=()=>{if(P.isShuttingDown)return;let G=P.config.checkInterval,U=G-Date.now()%G;P.checkInterval=setTimeout(()=>{if(!P.isShuttingDown&&!V)V=!0,kL().catch((K)=>E.error("Scheduler check failed:",K)).finally(()=>{V=!1});Y()},U),P.checkInterval?.unref?.()};Y(),await kL()}async function kL(){for(let[L,$]of P.jobs){let V=$.job.config.rate||$.job.config.schedule;if(!V)continue;let Y=vL(V);if(!Y)continue;if(U9(Y,$.lastRun,P.config.timezone)){if((P.config.preventOverlapping||$.job.config.withoutOverlapping)&&await SL(L)){E.debug(`Skipping ${L}: previous execution still running`);continue}if($.isRunning){E.debug(`Skipping ${L}: a dispatch for it is already in flight`);continue}try{$.isRunning=!0,$.lastRun=new Date,$.nextRun=yL(Y,P.config.timezone),await JL(L,$.lastRun),E.info(`Dispatching scheduled job: ${L}`),await d("job:added",{jobId:`scheduled-${L}-${Date.now()}`,queueName:$.job.config.queue||"default",jobName:L}),await t1(L,{queue:$.job.config.queue||"default",payload:{},maxTries:$.job.config.tries||3,timeout:$.job.config.timeout||60}),$.isRunning=!1,E.info(`Scheduled job ${L} dispatched to queue`)}catch(U){$.isRunning=!1,E.error(`Failed to dispatch scheduled job ${L}:`,U)}}}}async function F4(){if(!P.isRunning)return;if(E.info("Stopping scheduler..."),P.isShuttingDown=!0,P.checkInterval)clearTimeout(P.checkInterval),P.checkInterval=null;P.isRunning=!1,P.jobs.clear(),E.info("Scheduler stopped")}function X9(){return{isRunning:P.isRunning,jobCount:P.jobs.size,jobs:Array.from(P.jobs.entries()).map(([L,$])=>({name:L,schedule:$.job.config.rate||$.job.config.schedule,lastRun:$.lastRun,nextRun:$.nextRun,isRunning:$.isRunning}))}}function z9(){return P.isRunning}function W9(){return new Map(P.jobs)}async function _9(L){let $=P.jobs.get(L);if(!$)throw Error(`Scheduled job "${L}" not found`);E.info(`Manually triggering scheduled job: ${L}`),await t1(L,{queue:$.job.config.queue||"default",payload:{},maxTries:$.job.config.tries||3,timeout:$.job.config.timeout||60})}k();k();import{log as Z9}from"@stacksjs/logging";function wL(L,$=Math.floor(Date.now()/1000)){if(L.reserved_at)return"processing";let V=typeof L.available_at==="number"?L.available_at:typeof L.available_at==="string"&&L.available_at.trim()?Number(L.available_at):Number.NaN;if(Number.isFinite(V)&&V>$)return"delayed";return"pending"}var O9={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function Q4(L={}){let $={...O9,...L},V=[],Y=new Date,G=Math.floor(Y.getTime()/1000);try{let{db:U}=await import("@stacksjs/database"),K=await U.selectFrom("jobs").selectAll().execute(),W=await U.selectFrom("failed_jobs").selectAll().execute(),X=new Map;for(let A of K){let M=A.queue||"default";if(!X.has(M))X.set(M,{pending:0,processing:0,delayed:0});let J=X.get(M),R=wL(A,G);if(R==="processing")J.processing++;else if(R==="delayed")J.delayed++;else if(J.pending++,A.created_at){let dL=typeof A.created_at==="number"?A.created_at:Math.floor(new Date(A.created_at).getTime()/1000),M4=G-dL;if(!J.oldestAge||M4>J.oldestAge)J.oldestAge=M4}}let z=new Map;for(let A of W){let M=A.queue||"default";z.set(M,(z.get(M)||0)+1)}let _=[],H=new Set([...X.keys(),...z.keys()]),x=$.queues?[...H].filter((A)=>$.queues.includes(A)):[...H],O=0,Z=0,F=0,I=0;for(let A of x){let M=X.get(A)||{pending:0,processing:0,delayed:0},J=z.get(A)||0;O+=M.pending,Z+=M.processing,F+=M.delayed,I+=J;let R="healthy";if(M.pending>=$.maxPendingCritical)R="unhealthy",V.push({level:"critical",message:`Queue "${A}" has ${M.pending} pending jobs (threshold: ${$.maxPendingCritical})`,queue:A,timestamp:Y.toISOString()});else if(M.pending>=$.maxPendingWarning)R="degraded",V.push({level:"warning",message:`Queue "${A}" has ${M.pending} pending jobs (threshold: ${$.maxPendingWarning})`,queue:A,timestamp:Y.toISOString()});if(J>=$.maxFailedCritical)R="unhealthy",V.push({level:"critical",message:`Queue "${A}" has ${J} failed jobs (threshold: ${$.maxFailedCritical})`,queue:A,timestamp:Y.toISOString()});else if(J>=$.maxFailedWarning){if(R==="healthy")R="degraded";V.push({level:"warning",message:`Queue "${A}" has ${J} failed jobs (threshold: ${$.maxFailedWarning})`,queue:A,timestamp:Y.toISOString()})}if(M.oldestAge){if(M.oldestAge>=$.maxJobAgeCritical)R="unhealthy",V.push({level:"critical",message:`Queue "${A}" has a job waiting for ${Math.floor(M.oldestAge/3600)} hours`,queue:A,timestamp:Y.toISOString()});else if(M.oldestAge>=$.maxJobAgeWarning){if(R==="healthy")R="degraded";V.push({level:"warning",message:`Queue "${A}" has a job waiting for ${Math.floor(M.oldestAge/60)} minutes`,queue:A,timestamp:Y.toISOString()})}}_.push({name:A,status:R,pending:M.pending,processing:M.processing,delayed:M.delayed,failed:J,oldestJobAge:M.oldestAge})}let x4=v1().getMetrics(),lL=x4.throughputPerMinute,iL=x4.averageDuration,P4=O+Z+F+I,W1=P4>0?I/P4:0,o="healthy";if(_.some((A)=>A.status==="unhealthy"))o="unhealthy";else if(_.some((A)=>A.status==="degraded"))o="degraded";if(W1>=$.maxErrorRateCritical)o="unhealthy",V.push({level:"critical",message:`Overall error rate is ${(W1*100).toFixed(1)}% (threshold: ${$.maxErrorRateCritical*100}%)`,timestamp:Y.toISOString()});else if(W1>=$.maxErrorRateWarning){if(o==="healthy")o="degraded";V.push({level:"warning",message:`Overall error rate is ${(W1*100).toFixed(1)}% (threshold: ${$.maxErrorRateWarning*100}%)`,timestamp:Y.toISOString()})}let cL=A1().getAll().map((A)=>({id:A.id,status:A.status,queue:A.queue,processedCount:A.processedCount,failedCount:A.failedCount,lastActivityAt:A.lastActivityAt}));return{status:o,timestamp:Y.toISOString(),queues:_,workers:cL,metrics:{totalPending:O,totalProcessing:Z,totalDelayed:F,totalFailed:I,throughputPerMinute:lL,averageProcessingTime:iL,errorRate:W1},alerts:V}}catch(U){Z9.error("Failed to perform queue health check:",U);let W=A1().getAll().map((X)=>({id:X.id,status:X.status,queue:X.queue,processedCount:X.processedCount,failedCount:X.failedCount,lastActivityAt:X.lastActivityAt}));return{status:"unhealthy",timestamp:Y.toISOString(),queues:[],workers:W,metrics:{totalPending:0,totalProcessing:0,totalDelayed:0,totalFailed:0,throughputPerMinute:0,averageProcessingTime:0,errorRate:0},alerts:[{level:"critical",message:`Health check failed: ${U.message}`,timestamp:Y.toISOString()}]}}}function F9(L={}){return async($)=>{let V=await Q4(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 Q9(L={}){return(await Q4(L)).status==="healthy"}import{log as q}from"@stacksjs/logging";var H9={"&":"&","<":"<",">":">",'"':""","'":"'"};function M1(L){return String(L).replace(/[&<>"']/g,($)=>H9[$]??$)}class H4{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){q.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(($)=>q.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)=>q.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){q.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){q.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;">${M1(String(V.id))}</td>
|
|
5
5
|
<td style="padding: 8px; border: 1px solid #ddd;">${M1(V.name)}</td>
|
|
@@ -28,4 +28,4 @@ var{defineProperty:h1,getOwnPropertyNames:cL,getOwnPropertyDescriptor:dL}=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 B1=null;function Q9(L){return B1=new H4(L),B1}function H9(){return B1}async function A9(L){if(B1)await B1.notify(L)}g();a();q1();import{err as D9,ok as wL}from"@stacksjs/error-handling";import{log as D}from"@stacksjs/logging";import X1 from"process";import{env as P9}from"@stacksjs/env";var fL=!1;function x9(){if(fL)return;fL=!0,X1.on("unhandledRejection",(L,$)=>{D.error(`Unhandled Rejection: ${L}`)}),X1.on("uncaughtException",(L)=>{D.error(`Uncaught Exception: ${L.message}`)})}var i=0,z1=!1,N="",t=new Set,jL=new Map;function gL(L){return t.add(L),L.finally(()=>t.delete(L)),L}function M9(){let L=X1.env.STACKS_QUEUE_RESERVATION_TTL_SEC,$=L===void 0?Number.NaN:Number.parseInt(L,10);return Number.isFinite($)&&$>0?$:3600}function B9(){let L=X1.env.STACKS_QUEUE_RETRY_JITTER;if(L===void 0)return 0.2;let $=Number(L);if(!Number.isFinite($)||$<0)return 0.2;return Math.min($,1)}function T9(L,$,V=Math.random){if(!(L>0)||$<=0)return L;return Math.round(L+L*$*V())}function E9(){let L=X1.env.STACKS_QUEUE_SWEEP_INTERVAL_SEC,$=L===void 0?Number.NaN:Number.parseInt(L,10);return(Number.isFinite($)&&$>0?$:60)*1000}async function mL(){let L=M9(),$=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=c(G);if(U>0)D.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 D.error("[queue] Reservation sweep failed",{reason:Y instanceof Error?Y.message:String(Y)}),0}}function N9(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 J9(){return P9.QUEUE_DRIVER||"sync"}async function S9(L,$={}){try{D.info("Starting queue processor..."),x9(),z1=!0,N=`worker-${X1.pid}-${Date.now()}`;let V=$.concurrency||1,Y=J9(),{getWorkerTracker:G,getGlobalMetrics:U}=await Promise.resolve().then(() => (k(),b));if(U(),G().register(N,L||"default"),Y==="redis")return D.info("Using Redis queue driver (bun-queue)"),await w9(L||"default",V),wL(void 0);let K;if(L)K=[L];else if(K=await pL(),K.length===0)K=["default"];return D.info(`Processing queues: ${K.join(", ")}`),await R9(K,V),wL(void 0)}catch(V){return z1=!1,D9(V)}}async function pL(){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 R9(L,$){D.info("Listening for jobs...");let V=L,Y=Date.now(),G=1e4,U=Date.now(),K=E9();await mL();while(z1)try{let W=Date.now();if(W-Y>G){try{let X=await pL();if(X.length>0)V=X}catch{}Y=W}if(W-U>K)await mL(),U=W;for(let X of V){try{let{isCircuitOpen:_}=await Promise.resolve().then(() => (P1(),m1));if(await _(X))continue}catch{}let z=[];try{z=await C9(X,$)}catch(_){let A=Date.now(),x=jL.get(X)??0;if(A-x>60000)jL.set(X,A),D.error(`[queue] Could not reserve jobs on "${X}" \u2014 retrying each cycle:`,_);continue}await Promise.all(z.map(async(_)=>{try{D.info(`Processing job ${_.id} from queue "${X}"`),await gL(q9(_))}catch{D.error(`Unexpected error processing job ${_.id}`)}}))}await D4(1000)}catch{await D4(3000)}}async function C9(L,$){let V=Math.floor(Date.now()/1000),{db:Y}=await import("@stacksjs/database"),G=[];for(let U=0;U<$;U++){let K=await Y.selectFrom("jobs").where("queue","=",L).whereNull("reserved_at").where("available_at","<=",V).orderBy("id","asc").limit(1).selectAll().executeTakeFirst();if(!K)break;let W=await Y.updateTable("jobs").set({reserved_at:V,attempts:(K.attempts||0)+1}).where("id","=",K.id).whereNull("reserved_at").executeTakeFirst();if(c(W)>0)G.push(K)}return G}async function q9(L){let $=L.id,V=L.queue||"default";i++;let Y=Date.now(),{emitQueueEvent:G,getWorkerTracker:U}=await Promise.resolve().then(() => (k(),b)),K=U();K.markActive(N);let W;try{W=JSON.parse(L.payload||"{}").jobName}catch{}await G("job:processing",{jobId:String($),queueName:V,jobName:W});try{let _=JSON.parse(L.payload||"{}").payload?._batchId;if(_){let{isBatchCancelled:A}=await Promise.resolve().then(() => (g(),l));if(await A(_)){D.info(`[Queue] Skipping job ${$} - batch ${_} has been cancelled`),await A4($),i--,K.markIdle(N);return}}}catch{}let X=null;try{let z=JSON.parse(L.payload||"{}"),_=v9(z);if(_===void 0)await hL(z);else await y9(hL(z),_*1000,`Job ${$} exceeded ${_}s timeout`)}catch(z){X=z instanceof Error?z:Error(String(z))}if(!X)try{await A4($),D.info(`[Queue] Job ${$} completed`),K.recordCompletion(N);try{let{recordCircuitSuccess:z}=await Promise.resolve().then(() => (P1(),m1));await z(L.queue??"default")}catch{}await G("job:completed",{jobId:String($),queueName:V,duration:Date.now()-Y});try{let _=JSON.parse(L.payload||"{}").payload?._batchId;if(_){let{recordBatchJobCompletion:A}=await Promise.resolve().then(() => (g(),l));await A(_)}}catch{}}catch{D.info(`[Queue] Failed to delete completed job ${$}`)}else{let z=X.message;D.info(`[Queue] Job ${$} failed: ${z}`),K.recordFailure(N),await G("job:failed",{jobId:String($),queueName:V,error:X,duration:Date.now()-Y,attemptsMade:(L.attempts||0)+1});let _=1,A={};try{A=JSON.parse(L.payload||"{}"),_=A.options?.tries||1}catch{}let x=(L.attempts||0)+1;if(x>=_){D.info(`[Queue] Job ${$} exceeded max attempts (${x}/${_})`);let O=!1;if(A?._retriedFromFailed===!0)try{let{moveToDeadLetter:Z}=await Promise.resolve().then(() => (S1(),m4));if(O=await Z({queue:L.queue,payload:L.payload,exception:X.stack||X.message},"repeat-failure",2),O)D.info(`[Queue] Job ${$} re-failed after retry \u2014 moved to dead_letter_jobs`)}catch{O=!1}if(!O){D.info(`[Queue] Moving job ${$} to failed_jobs`);try{O=await I9(L,X,{attempts:x,maxAttempts:_,durationMs:Date.now()-Y})}catch{O=!1}}if(O)try{await A4($)}catch{D.info(`[Queue] Failed to delete failed job ${$}`)}else D.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:Z}=await Promise.resolve().then(() => (C1(),i4)),{recordCircuitFailure:F}=await Promise.resolve().then(() => (P1(),m1)),I=A?.jobName??"unknown";await Z(I,A?.payload),await F(L.queue??"default")}catch{}try{let Z=A.payload?._batchId;if(Z){let{recordBatchJobFailure:F}=await Promise.resolve().then(() => (g(),l));await F(Z,String($),X)}}catch{}}else{let O=A.options?.backoff,Z=30;if(Array.isArray(O)&&O.length>0){let F=Math.min(x-1,O.length-1);Z=O[F]}else if(typeof O==="number"&&O>0)Z=O;if(Z=Number(Z),!Number.isFinite(Z)||Z<0)Z=30;Z=T9(Z,B9()),D.info(`[Queue] Job ${$} will be retried in ${Z}s (attempt ${x}/${_})`);try{await k9($,Z),D.info(`[Queue] Job ${$} released for retry`)}catch{D.info(`[Queue] Failed to release job ${$} for retry`)}}}i--,K.markIdle(N)}async function A4(L){let{db:$}=await import("@stacksjs/database");await $.deleteFrom("jobs").where("id","=",L).execute()}async function k9(L,$=30){let V=Math.floor(Date.now()/1000)+$;D.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(),D.debug(`Job ${L} released successfully`)}catch{D.error(`Failed to release job ${L}`)}}async function I9(L,$,V){try{let Y=new Date().toISOString().slice(0,19).replace("T"," "),G=crypto.randomUUID(),U=$.stack||$.message,{db:K}=await import("@stacksjs/database");return await K.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 D.error("Failed to log failed job:",Y),!1}}function v9(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 y9(L,$,V){let Y,G=new Promise((U,K)=>{Y=setTimeout(()=>K(Error(V)),$)});try{return await Promise.race([L,G])}finally{if(Y!==void 0)clearTimeout(Y)}}async function hL(L){let $=T1(L);if(!$.ok)throw Error(`[queue] Cannot deserialize job envelope: ${$.reason}`+($.detail?` (${$.detail})`:""));let{runJob:V}=await Promise.resolve().then(() => (x1(),f1));await V($.envelope.jobName,{payload:$.envelope.payload,traceId:$.envelope.traceId})}async function w9(L,$){let{RedisQueue:V}=await Promise.resolve().then(() => (F1(),O1)),{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:K,getWorkerTracker:W}=await Promise.resolve().then(() => (k(),b)),X=W(),z=async(_)=>{i++,X.markActive(N);let A=Date.now(),x=T1(_.data);if(!x.ok){i--,X.markIdle(N),D.error(`[Queue] Skipping Redis job ${_.id} - unparseable envelope: ${x.reason}${x.detail?` (${x.detail})`:""}`);return}let O=x.envelope,Z=O.payload?._batchId;if(Z)try{let{isBatchCancelled:F}=await Promise.resolve().then(() => (g(),l));if(await F(Z)){D.info(`[Queue] Skipping Redis job ${_.id} - batch ${Z} has been cancelled`),i--,X.markIdle(N);return}}catch{}await K("job:processing",{jobId:String(_.id),queueName:L});try{let{runJob:F}=await Promise.resolve().then(() => (x1(),f1));if(await F(O.jobName,{payload:O.payload,traceId:O.traceId}),X.recordCompletion(N),await K("job:completed",{jobId:String(_.id),queueName:L,duration:Date.now()-A}),Z)try{let{recordBatchJobCompletion:I}=await Promise.resolve().then(() => (g(),l));await I(Z)}catch{}D.info(`[Queue] Redis job ${_.id} completed`)}catch(F){if(X.recordFailure(N),await K("job:failed",{jobId:String(_.id),queueName:L,error:F instanceof Error?F:Error(String(F)),duration:Date.now()-A}),Z)try{let{recordBatchJobFailure:I}=await Promise.resolve().then(() => (g(),l));await I(Z,String(_.id),F instanceof Error?F:Error(String(F)))}catch{}if(D.error(`[Queue] Redis job ${_.id} failed: ${F}`),N9(F)){D.info(`[Queue] Redis job ${_.id} hit a non-retryable error \u2014 skipping retry`);return}throw F}finally{i--,X.markIdle(N)}};U.process($,(_)=>gL(z(_))),D.info(`Listening for Redis jobs on queue "${L}" with concurrency ${$}...`);while(z1)await D4(1000);await U.close()}async function f9(L={}){let $=L.graceMs??1e4;if(z1=!1,t.size>0&&$>0){D.info(`[queue] Draining ${t.size} in-flight job(s) (grace ${$}ms)`);let V=Promise.allSettled([...t]),Y=new Promise((U)=>setTimeout(()=>U("timeout"),$));if(await Promise.race([V.then(()=>"drained"),Y])==="timeout"&&t.size>0)D.warn(`[queue] Drain timed out with ${t.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(() => (k(),b));V().unregister(N)}D.info("Queue processor stopped")}async function j9(){let{db:L}=await import("@stacksjs/database"),$=await L.selectFrom("failed_jobs").selectAll().execute();for(let V of $)await bL(Number(V.id))}async function bL(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 K=U.payload;try{let W=JSON.parse(U.payload||"{}");W._retriedFromFailed=!0,K=JSON.stringify(W)}catch{}await Y.insertInto("jobs").values({queue:U.queue,payload:K,attempts:0,reserved_at:null,available_at:$,created_at:V}).execute(),await Y.deleteFrom("failed_jobs").where("id","=",L).execute(),D.info(`Failed job ${L} has been re-queued`)}function m9(){return i}function h9(){return z1}function D4(L){return new Promise(($)=>setTimeout($,L))}async function b$(){let{RedisQueue:L}=await Promise.resolve().then(() => (F1(),O1));return L}export{d4 as withEvents,u4 as unquarantineJob,z9 as triggerJob,e0 as toJobOptions,F4 as stopScheduler,f9 as stopProcessor,G9 as startScheduler,S9 as startProcessor,i0 as setJobProgress,j as serializeEnvelope,R4 as runTestJob,j1 as runJob,bL as retryFailedJob,f4 as retryDeadLetterJob,DL as resumeQueue,l1 as restore,s1 as releaseDispatchKey,p4 as recordFailureForPoison,H0 as recordDispatchedKey,QL as recordCircuitSuccess,HL as recordCircuitFailure,U4 as recordBatchJobFailure,G4 as recordBatchJobCompletion,b4 as quarantineJob,j4 as purgeDeadLetterJobs,AL as pauseQueue,T1 as parseEnvelope,c4 as onQueueEvent,A9 as notifyJobFailed,J1 as moveToDeadLetter,l4 as listQuarantined,w4 as listDeadLetterJobs,xL as listCircuitState,r as jobRegistry,_L as jobBatch,zL as job,h9 as isWorkerRunning,K9 as isSchedulerRunning,O9 as isQueueHealthy,R1 as isQuarantined,n0 as isJobCancelled,J4 as isFaked,FL as isCircuitOpen,UL as isBatchCancelled,H1 as hashPayload,Q0 as hasDispatchedKey,A1 as getWorkerTracker,U9 as getSchedulerStatus,Z4 as getScheduledJobs,X9 as getRegisteredJobs,b$ as getRedisQueue,$1 as getQueueEvents,c0 as getJobProgress,t0 as getJob,v1 as getGlobalMetrics,N4 as getFakeQueue,H9 as getFailedJobNotifier,D1 as getBatchCallbacks,o0 as getAllJobs,m9 as getActiveJobCount,u1 as fake,C4 as expectJobToFail,a0 as executeJob,j9 as executeFailedJobs,d as emitQueueEvent,_4 as discoverJobs,S4 as createQueueTester,Z9 as createHealthCheckHandler,y as createEnvelope,Q9 as configureFailedJobNotifications,s0 as clearJobState,V0 as clearEnvelopeWarnings,n1 as claimDispatchKey,Q4 as checkQueueHealth,d0 as cancelJob,_1 as assertEnvelopeSerializable,E1 as QueueTester,I1 as QueueMetrics,k1 as QueueEvents,w1 as PendingBatch,n4 as OnQueueEvent,WL as Jobs,v4 as Job,aL as JOB_ENVELOPE_VERSION,H4 as FailedJobNotifier,u as DispatchedBatch,e1 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)q.error(`Slack notification failed with status ${Y.status}`)}catch(Y){q.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)q.error(`Discord notification failed with status ${Y.status}`)}catch(Y){q.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)q.error(`Webhook notification failed with status ${G.status}`)}catch(G){q.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)q.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 B1=null;function A9(L){return B1=new H4(L),B1}function D9(){return B1}async function x9(L){if(B1)await B1.notify(L)}g();a();q1();import{err as P9,ok as fL}from"@stacksjs/error-handling";import{log as D}from"@stacksjs/logging";import X1 from"process";import{env as B9}from"@stacksjs/env";var jL=!1;function M9(){if(jL)return;jL=!0,X1.on("unhandledRejection",(L,$)=>{D.error(`Unhandled Rejection: ${L}`)}),X1.on("uncaughtException",(L)=>{D.error(`Uncaught Exception: ${L.message}`)})}var i=0,z1=!1,N="",t=new Set,mL=new Map;function pL(L){return t.add(L),L.finally(()=>t.delete(L)),L}function T9(){let L=X1.env.STACKS_QUEUE_RESERVATION_TTL_SEC,$=L===void 0?Number.NaN:Number.parseInt(L,10);return Number.isFinite($)&&$>0?$:3600}function E9(){let L=X1.env.STACKS_QUEUE_RETRY_JITTER;if(L===void 0)return 0.2;let $=Number(L);if(!Number.isFinite($)||$<0)return 0.2;return Math.min($,1)}function N9(L,$,V=Math.random){if(!(L>0)||$<=0)return L;return Math.round(L+L*$*V())}function J9(){let L=X1.env.STACKS_QUEUE_SWEEP_INTERVAL_SEC,$=L===void 0?Number.NaN:Number.parseInt(L,10);return(Number.isFinite($)&&$>0?$:60)*1000}async function hL(){let L=T9(),$=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=c(G);if(U>0)D.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 D.error("[queue] Reservation sweep failed",{reason:Y instanceof Error?Y.message:String(Y)}),0}}function S9(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 R9(){return B9.QUEUE_DRIVER||"sync"}async function C9(L,$={}){try{D.info("Starting queue processor..."),M9(),z1=!0,N=`worker-${X1.pid}-${Date.now()}`;let V=$.concurrency||1,Y=R9(),{getWorkerTracker:G,getGlobalMetrics:U}=await Promise.resolve().then(() => (k(),u));if(U(),G().register(N,L||"default"),Y==="redis")return D.info("Using Redis queue driver (bun-queue)"),await j9(L||"default",V),fL(void 0);let K;if(L)K=[L];else if(K=await uL(),K.length===0)K=["default"];return D.info(`Processing queues: ${K.join(", ")}`),await q9(K,V),fL(void 0)}catch(V){return z1=!1,P9(V)}}async function uL(){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 q9(L,$){D.info("Listening for jobs...");let V=L,Y=Date.now(),G=1e4,U=Date.now(),K=J9();await hL();while(z1)try{let W=Date.now();if(W-Y>G){try{let X=await uL();if(X.length>0)V=X}catch{}Y=W}if(W-U>K)await hL(),U=W;for(let X of V){try{let{isCircuitOpen:_}=await Promise.resolve().then(() => (P1(),m1));if(await _(X))continue}catch{}let z=[];try{z=await k9(X,$)}catch(_){let H=Date.now(),x=mL.get(X)??0;if(H-x>60000)mL.set(X,H),D.error(`[queue] Could not reserve jobs on "${X}" \u2014 retrying each cycle:`,_);continue}await Promise.all(z.map(async(_)=>{try{D.info(`Processing job ${_.id} from queue "${X}"`),await pL(I9(_))}catch{D.error(`Unexpected error processing job ${_.id}`)}}))}await D4(1000)}catch{await D4(3000)}}async function k9(L,$){let V=Math.floor(Date.now()/1000),{db:Y}=await import("@stacksjs/database"),G=[];for(let U=0;U<$;U++){let K=await Y.selectFrom("jobs").where("queue","=",L).whereNull("reserved_at").where("available_at","<=",V).orderBy("id","asc").limit(1).selectAll().executeTakeFirst();if(!K)break;let W=await Y.updateTable("jobs").set({reserved_at:V,attempts:(K.attempts||0)+1}).where("id","=",K.id).whereNull("reserved_at").executeTakeFirst();if(c(W)>0)G.push(K)}return G}async function I9(L){let $=L.id,V=L.queue||"default";i++;let Y=Date.now(),{emitQueueEvent:G,getWorkerTracker:U}=await Promise.resolve().then(() => (k(),u)),K=U();K.markActive(N);let W;try{W=JSON.parse(L.payload||"{}").jobName}catch{}await G("job:processing",{jobId:String($),queueName:V,jobName:W});try{let _=JSON.parse(L.payload||"{}").payload?._batchId;if(_){let{isBatchCancelled:H}=await Promise.resolve().then(() => (g(),l));if(await H(_)){D.info(`[Queue] Skipping job ${$} - batch ${_} has been cancelled`),await A4($),i--,K.markIdle(N);return}}}catch{}let X=null;try{let z=JSON.parse(L.payload||"{}"),_=w9(z);if(_===void 0)await gL(z);else await f9(gL(z),_*1000,`Job ${$} exceeded ${_}s timeout`)}catch(z){X=z instanceof Error?z:Error(String(z))}if(!X)try{await A4($),D.info(`[Queue] Job ${$} completed`),K.recordCompletion(N);try{let{recordCircuitSuccess:z}=await Promise.resolve().then(() => (P1(),m1));await z(L.queue??"default")}catch{}await G("job:completed",{jobId:String($),queueName:V,duration:Date.now()-Y});try{let _=JSON.parse(L.payload||"{}").payload?._batchId;if(_){let{recordBatchJobCompletion:H}=await Promise.resolve().then(() => (g(),l));await H(_)}}catch{}}catch{D.info(`[Queue] Failed to delete completed job ${$}`)}else{let z=X.message;D.info(`[Queue] Job ${$} failed: ${z}`),K.recordFailure(N),await G("job:failed",{jobId:String($),queueName:V,error:X,duration:Date.now()-Y,attemptsMade:(L.attempts||0)+1});let _=1,H={};try{H=JSON.parse(L.payload||"{}"),_=H.options?.tries||1}catch{}let x=(L.attempts||0)+1;if(x>=_){D.info(`[Queue] Job ${$} exceeded max attempts (${x}/${_})`);let O=!1;if(H?._retriedFromFailed===!0)try{let{moveToDeadLetter:Z}=await Promise.resolve().then(() => (S1(),m4));if(O=await Z({queue:L.queue,payload:L.payload,exception:X.stack||X.message},"repeat-failure",2),O)D.info(`[Queue] Job ${$} re-failed after retry \u2014 moved to dead_letter_jobs`)}catch{O=!1}if(!O){D.info(`[Queue] Moving job ${$} to failed_jobs`);try{O=await y9(L,X,{attempts:x,maxAttempts:_,durationMs:Date.now()-Y})}catch{O=!1}}if(O)try{await A4($)}catch{D.info(`[Queue] Failed to delete failed job ${$}`)}else D.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:Z}=await Promise.resolve().then(() => (C1(),i4)),{recordCircuitFailure:F}=await Promise.resolve().then(() => (P1(),m1)),I=H?.jobName??"unknown";await Z(I,H?.payload),await F(L.queue??"default")}catch{}try{let Z=H.payload?._batchId;if(Z){let{recordBatchJobFailure:F}=await Promise.resolve().then(() => (g(),l));await F(Z,String($),X)}}catch{}}else{let O=H.options?.backoff,Z=30;if(Array.isArray(O)&&O.length>0){let F=Math.min(x-1,O.length-1);Z=O[F]}else if(typeof O==="number"&&O>0)Z=O;if(Z=Number(Z),!Number.isFinite(Z)||Z<0)Z=30;Z=N9(Z,E9()),D.info(`[Queue] Job ${$} will be retried in ${Z}s (attempt ${x}/${_})`);try{await v9($,Z),D.info(`[Queue] Job ${$} released for retry`)}catch{D.info(`[Queue] Failed to release job ${$} for retry`)}}}i--,K.markIdle(N)}async function A4(L){let{db:$}=await import("@stacksjs/database");await $.deleteFrom("jobs").where("id","=",L).execute()}async function v9(L,$=30){let V=Math.floor(Date.now()/1000)+$;D.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(),D.debug(`Job ${L} released successfully`)}catch{D.error(`Failed to release job ${L}`)}}async function y9(L,$,V){try{let Y=new Date().toISOString().slice(0,19).replace("T"," "),G=crypto.randomUUID(),U=$.stack||$.message,{db:K}=await import("@stacksjs/database");return await K.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 D.error("Failed to log failed job:",Y),!1}}function w9(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 f9(L,$,V){let Y,G=new Promise((U,K)=>{Y=setTimeout(()=>K(Error(V)),$)});try{return await Promise.race([L,G])}finally{if(Y!==void 0)clearTimeout(Y)}}async function gL(L){let $=T1(L);if(!$.ok)throw Error(`[queue] Cannot deserialize job envelope: ${$.reason}`+($.detail?` (${$.detail})`:""));let{runJob:V}=await Promise.resolve().then(() => (x1(),f1));await V($.envelope.jobName,{payload:$.envelope.payload,traceId:$.envelope.traceId})}async function j9(L,$){let{RedisQueue:V}=await Promise.resolve().then(() => (F1(),O1)),{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:K,getWorkerTracker:W}=await Promise.resolve().then(() => (k(),u)),X=W(),z=async(_)=>{i++,X.markActive(N);let H=Date.now(),x=T1(_.data);if(!x.ok){i--,X.markIdle(N),D.error(`[Queue] Skipping Redis job ${_.id} - unparseable envelope: ${x.reason}${x.detail?` (${x.detail})`:""}`);return}let O=x.envelope,Z=O.payload?._batchId;if(Z)try{let{isBatchCancelled:F}=await Promise.resolve().then(() => (g(),l));if(await F(Z)){D.info(`[Queue] Skipping Redis job ${_.id} - batch ${Z} has been cancelled`),i--,X.markIdle(N);return}}catch{}await K("job:processing",{jobId:String(_.id),queueName:L});try{let{runJob:F}=await Promise.resolve().then(() => (x1(),f1));if(await F(O.jobName,{payload:O.payload,traceId:O.traceId}),X.recordCompletion(N),await K("job:completed",{jobId:String(_.id),queueName:L,duration:Date.now()-H}),Z)try{let{recordBatchJobCompletion:I}=await Promise.resolve().then(() => (g(),l));await I(Z)}catch{}D.info(`[Queue] Redis job ${_.id} completed`)}catch(F){if(X.recordFailure(N),await K("job:failed",{jobId:String(_.id),queueName:L,error:F instanceof Error?F:Error(String(F)),duration:Date.now()-H}),Z)try{let{recordBatchJobFailure:I}=await Promise.resolve().then(() => (g(),l));await I(Z,String(_.id),F instanceof Error?F:Error(String(F)))}catch{}if(D.error(`[Queue] Redis job ${_.id} failed: ${F}`),S9(F)){D.info(`[Queue] Redis job ${_.id} hit a non-retryable error \u2014 skipping retry`);return}throw F}finally{i--,X.markIdle(N)}};U.process($,(_)=>pL(z(_))),D.info(`Listening for Redis jobs on queue "${L}" with concurrency ${$}...`);while(z1)await D4(1000);await U.close()}async function m9(L={}){let $=L.graceMs??1e4;if(z1=!1,t.size>0&&$>0){D.info(`[queue] Draining ${t.size} in-flight job(s) (grace ${$}ms)`);let V=Promise.allSettled([...t]),Y=new Promise((U)=>setTimeout(()=>U("timeout"),$));if(await Promise.race([V.then(()=>"drained"),Y])==="timeout"&&t.size>0)D.warn(`[queue] Drain timed out with ${t.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(() => (k(),u));V().unregister(N)}D.info("Queue processor stopped")}async function h9(){let{db:L}=await import("@stacksjs/database"),$=await L.selectFrom("failed_jobs").selectAll().execute();for(let V of $)await bL(Number(V.id))}async function bL(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 K=U.payload;try{let W=JSON.parse(U.payload||"{}");W._retriedFromFailed=!0,K=JSON.stringify(W)}catch{}await Y.insertInto("jobs").values({queue:U.queue,payload:K,attempts:0,reserved_at:null,available_at:$,created_at:V}).execute(),await Y.deleteFrom("failed_jobs").where("id","=",L).execute(),D.info(`Failed job ${L} has been re-queued`)}function g9(){return i}function p9(){return z1}function D4(L){return new Promise(($)=>setTimeout($,L))}async function l$(){let{RedisQueue:L}=await Promise.resolve().then(() => (F1(),O1));return L}export{d4 as withEvents,b4 as unquarantineJob,_9 as triggerJob,$9 as toJobOptions,F4 as stopScheduler,m9 as stopProcessor,K9 as startScheduler,C9 as startProcessor,d0 as setJobProgress,j as serializeEnvelope,R4 as runTestJob,j1 as runJob,bL as retryFailedJob,f4 as retryDeadLetterJob,DL as resumeQueue,l1 as restore,s1 as releaseDispatchKey,p4 as recordFailureForPoison,D0 as recordDispatchedKey,QL as recordCircuitSuccess,HL as recordCircuitFailure,U4 as recordBatchJobFailure,G4 as recordBatchJobCompletion,wL as queuedJobState,u4 as quarantineJob,j4 as purgeDeadLetterJobs,AL as pauseQueue,T1 as parseEnvelope,c4 as onQueueEvent,x9 as notifyJobFailed,J1 as moveToDeadLetter,l4 as listQuarantined,w4 as listDeadLetterJobs,xL as listCircuitState,r as jobRegistry,_L as jobBatch,zL as job,p9 as isWorkerRunning,z9 as isSchedulerRunning,Q9 as isQueueHealthy,R1 as isQuarantined,r0 as isJobCancelled,J4 as isFaked,FL as isCircuitOpen,UL as isBatchCancelled,H1 as hashPayload,A0 as hasDispatchedKey,A1 as getWorkerTracker,X9 as getSchedulerStatus,Z4 as getScheduledJobs,W9 as getRegisteredJobs,l$ as getRedisQueue,$1 as getQueueEvents,n0 as getJobProgress,a0 as getJob,v1 as getGlobalMetrics,N4 as getFakeQueue,D9 as getFailedJobNotifier,D1 as getBatchCallbacks,e0 as getAllJobs,g9 as getActiveJobCount,b1 as fake,C4 as expectJobToFail,L9 as executeJob,h9 as executeFailedJobs,d as emitQueueEvent,_4 as discoverJobs,S4 as createQueueTester,F9 as createHealthCheckHandler,y as createEnvelope,A9 as configureFailedJobNotifications,t0 as clearJobState,G0 as clearEnvelopeWarnings,n1 as claimDispatchKey,Q4 as checkQueueHealth,s0 as cancelJob,_1 as assertEnvelopeSerializable,E1 as QueueTester,I1 as QueueMetrics,k1 as QueueEvents,w1 as PendingBatch,n4 as OnQueueEvent,WL as Jobs,v4 as Job,L0 as JOB_ENVELOPE_VERSION,H4 as FailedJobNotifier,b as DispatchedBatch,e1 as Batch};
|