@stacksjs/queue 0.74.35 → 0.74.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +2 -2
  2. package/package.json +11 -11
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var{defineProperty:He,getOwnPropertyNames:sr,getOwnPropertyDescriptor:cr}=Object,ur=Object.prototype.hasOwnProperty;function lr(e){return this[e]}var dr=(e)=>{var t=(St??=new WeakMap).get(e),n;if(t)return t;if(t=He({},"__esModule",{value:!0}),e&&typeof e==="object"||typeof e==="function"){for(var r of sr(e))if(!ur.call(t,r))He(t,r,{get:lr.bind(e,r),enumerable:!(n=cr(e,r))||n.enumerable})}return St.set(e,t),t},St;var hr=(e)=>e;function pr(e,t){this[e]=hr.bind(null,t)}var M=(e,t)=>{for(var n in t)He(e,n,{get:t[n],enumerable:!0,configurable:!0,set:pr.bind(t,n)})};var q=(e,t)=>()=>(e&&(t=e(e=0)),t);var m=import.meta.require;function N(e,t,n,r){return{jobName:e,payload:t,options:n,envelopeVersion:1,dispatchedAt:new Date().toISOString(),...r?{traceId:r}:{}}}function F(e){try{return JSON.stringify(e)}catch(t){throw mr(e,t)}}function de(e){F(e)}function mr(e,t){if(br(t)){let i=null;try{i=Le(e,"",new Set)}catch{}let o=i?`\`${i.path}\` is a BigInt (${i.value}n)`:"it holds a BigInt";return Error(`[queue] Job "${e.jobName}" was not dispatched: ${o}, and job envelopes travel as JSON, which cannot represent one. Convert it where you dispatch - \`String(value)\` keeps every digit, \`Number(value)\` is exact below 2^53 - and convert it back inside the job handler. See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts.`,{cause:t})}if(gr(t))return Error(`[queue] Job "${e.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 - 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:t});let r=t instanceof Error?t.message:String(t);return Error(`[queue] Job "${e.jobName}" was not dispatched: serializing its payload threw (${r}). JSON rejects only two things on its own and this was neither, so the throw came from the payload - a \`toJSON()\` or a property getter. See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts.`,{cause:t})}function br(e){return e instanceof TypeError&&/bigint/i.test(e.message)}function gr(e){return e instanceof TypeError&&/circular|cyclic/i.test(e.message)}function Le(e,t,n){if(typeof e==="bigint")return{path:t||"envelope",value:e};if(!e||typeof e!=="object")return null;if(n.has(e))return null;if(n.add(e),Array.isArray(e)){for(let r=0;r<e.length;r++){let i=Le(e[r],`${t}[${r}]`,n);if(i)return i}return null}for(let[r,i]of Object.entries(e)){let o=Le(i,t?`${t}.${r}`:r,n);if(o)return o}return null}function Et(e,t){if(Ue.has(e))return;Ue.add(e),console.warn(t)}function wr(){Ue.clear()}function Je(e){let t;if(typeof e==="string")try{t=JSON.parse(e)}catch{return{ok:!1,reason:"malformed",detail:"not valid JSON"}}else if(e&&typeof e==="object")t=e;else return{ok:!1,reason:"malformed",detail:`expected string or object, got ${typeof e}`};if(t.envelopeVersion===1){if(typeof t.jobName!=="string")return{ok:!1,reason:"missing-job-name"};return{ok:!0,envelope:{jobName:t.jobName,payload:t.payload,options:t.options??void 0,envelopeVersion:1,dispatchedAt:typeof t.dispatchedAt==="string"?t.dispatchedAt:new Date(0).toISOString(),...typeof t.traceId==="string"&&t.traceId?{traceId:t.traceId}:{}},source:"v1"}}if(typeof t.envelopeVersion==="number"&&t.envelopeVersion>1)return{ok:!1,reason:"unknown-version",detail:`envelopeVersion=${t.envelopeVersion}, this worker speaks v1`};if(typeof t.jobName==="string")return Et("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:t.jobName,payload:t.payload,options:t.options??void 0,envelopeVersion:1,dispatchedAt:typeof t.dispatchedAt==="string"?t.dispatchedAt:new Date(0).toISOString(),...typeof t.traceId==="string"&&t.traceId?{traceId:t.traceId}:{}},source:"v0-implicit"};if(typeof t.job==="string")return Et("laravel-legacy","[queue/envelope] Processing Laravel-legacy job envelope (`{ job, data }` shape). Will continue to process but the queue table contains migration-era rows - consider flushing once they drain."),{ok:!0,envelope:{jobName:t.job.replace(/^App\\+Jobs\\+/,"").replace(/^.*\\/,""),payload:t.data,options:void 0,envelopeVersion:1,dispatchedAt:new Date(0).toISOString()},source:"laravel-legacy"};return{ok:!1,reason:"missing-job-name"}}var fr=1,Ue;var te=q(()=>{Ue=new Set});import{getActionRunner as yr,runNamedAction as je,setActionRunner as vr}from"@stacksjs/action-runner";var Pe=()=>{};var Ke={};M(Ke,{QueueTester:()=>Se,createQueueTester:()=>qt,expectJobToFail:()=>$t,fake:()=>ze,getFakeQueue:()=>Rt,isFaked:()=>Ct,restore:()=>Ve,runJob:()=>Dt});class Tt{dispatchedJobs=[];pushedJobs=[];processedJobs=[];failedJobs=[];dispatch(e,t,n={}){this.dispatchedJobs.push({name:e,data:t,options:n,dispatchedAt:new Date,queue:n.queue||"default"})}push(e,t,n={}){this.pushedJobs.push({name:e,data:t,options:n,dispatchedAt:new Date,queue:n.queue||"default"})}dispatched(e){if(e)return this.dispatchedJobs.filter((t)=>t.name===e);return[...this.dispatchedJobs]}pushed(e){if(e)return this.pushedJobs.filter((t)=>t.name===e);return[...this.pushedJobs]}assertDispatched(e,t){let n=this.dispatched(e);if(n.length===0)throw Error(`Expected job "${e}" to be dispatched, but it was not.`);if(t){if(n.filter(t).length===0)throw Error(`Expected job "${e}" to be dispatched matching the callback, but no matching jobs were found.`)}}assertNotDispatched(e){let t=this.dispatched(e);if(t.length>0)throw Error(`Expected job "${e}" to not be dispatched, but it was dispatched ${t.length} time(s).`)}assertDispatchedTimes(e,t){let n=this.dispatched(e);if(n.length!==t)throw Error(`Expected job "${e}" to be dispatched ${t} time(s), but it was dispatched ${n.length} time(s).`)}assertNothingDispatched(){if(this.dispatchedJobs.length>0){let e=[...new Set(this.dispatchedJobs.map((t)=>t.name))].join(", ");throw Error(`Expected no jobs to be dispatched, but found: ${e}`)}}assertPushed(e,t){let n=this.pushed(e);if(n.length===0)throw Error(`Expected job "${e}" to be pushed, but it was not.`);if(t){if(n.filter(t).length===0)throw Error(`Expected job "${e}" to be pushed matching the callback, but no matching jobs were found.`)}}assertPushedWithDelay(e,t){if(this.pushed(e).filter((i)=>i.options?.delay===t).length===0)throw Error(`Expected job "${e}" to be pushed with delay ${t}ms, but no matching jobs were found.`)}assertPushedOn(e,t){if(this.pushed(t).filter((r)=>r.queue===e).length===0)throw Error(`Expected job "${t}" to be pushed on queue "${e}", but it was not.`)}async processJob(e,t){let n=this.dispatchedJobs.find((r)=>r.name===e);if(!n)throw Error(`No dispatched job found with name "${e}"`);try{await t(n.data),this.processedJobs.push(n)}catch(r){throw this.failedJobs.push({job:n,error:r}),r}}processed(e){if(e)return this.processedJobs.filter((t)=>t.name===e);return[...this.processedJobs]}failed(e){if(e)return this.failedJobs.filter((t)=>t.job.name===e);return[...this.failedJobs]}reset(){this.dispatchedJobs=[],this.pushedJobs=[],this.processedJobs=[],this.failedJobs=[]}}function ze(){return he=new Tt,he}function Rt(){return he}function Ct(){return he!==null}function Ve(){he=null}class Se{queue;constructor(){this.queue=ze()}dispatch(e,t,n={}){return this.queue.dispatch(e,t,n),this}push(e,t,n={}){return this.queue.push(e,t,n),this}assertDispatched(e,t){return this.queue.assertDispatched(e,t),this}assertNotDispatched(e){return this.queue.assertNotDispatched(e),this}assertDispatchedTimes(e,t){return this.queue.assertDispatchedTimes(e,t),this}assertNothingDispatched(){return this.queue.assertNothingDispatched(),this}dispatched(e){return this.queue.dispatched(e)}reset(){return this.queue.reset(),this}cleanup(){Ve()}}function qt(){return new Se}async function Dt(e,t){return await e.handle(t)}async function $t(e,t,n){try{throw await e.handle(t),Error("Expected job to fail, but it succeeded")}catch(r){if(r.message==="Expected job to fail, but it succeeded")throw r;if(n){let i=r.message;if(typeof n==="string"){if(!i.includes(n))throw Error(`Expected error to contain "${n}", got "${i}"`)}else if(!n.test(i))throw Error(`Expected error to match ${n}, got "${i}"`)}return r}}var he=null;var pe={};M(pe,{QueueManager:()=>Er,RedisJob:()=>At,RedisQueue:()=>ne,StacksQueueManager:()=>Nt,batch:()=>kr,chain:()=>xr,createRedisDispatcher:()=>Rr,dispatch:()=>Jr,dispatchAfter:()=>jr,dispatchSync:()=>Pr,getQueueManager:()=>Sr,setQueueManager:()=>Tr});import{Queue as _r,batch as kr,chain as xr,dispatch as Jr,dispatchAfter as jr,dispatchSync as Pr,getQueueManager as Sr,QueueManager as Er,setQueueManager as Tr}from"@stacksjs/bun-queue";import{log as Ge}from"@stacksjs/logging";class ne{queue;config;isProcessing=!1;constructor(e,t){this.config=t,this.queue=new _r(e,{driver:"redis",prefix:t.prefix,redis:t.redis?{url:t.redis.url||this.buildRedisUrl(t.redis)}:void 0,defaultJobOptions:t.defaultJobOptions?{delay:t.defaultJobOptions.delay,attempts:t.defaultJobOptions.attempts,backoff:t.defaultJobOptions.backoff,removeOnComplete:t.defaultJobOptions.removeOnComplete,removeOnFail:t.defaultJobOptions.removeOnFail,priority:t.defaultJobOptions.priority,lifo:t.defaultJobOptions.lifo,timeout:t.defaultJobOptions.timeout,jobId:t.defaultJobOptions.jobId,dependsOn:t.defaultJobOptions.dependsOn,keepJobs:t.defaultJobOptions.keepJobs,deadLetter:t.defaultJobOptions.deadLetter}:void 0,limiter:t.limiter,metrics:t.metrics,stalledJobCheckInterval:t.stalledJobCheckInterval,maxStalledJobRetries:t.maxStalledJobRetries,distributedLock:t.distributedLock,defaultDeadLetterOptions:t.defaultDeadLetterOptions,horizontalScaling:t.horizontalScaling,logLevel:t.logLevel}),Ge.debug(`Redis queue "${e}" initialized`)}buildRedisUrl(e){let t=e.host||"localhost",n=e.port||6379,r=e.password?`:${encodeURIComponent(e.password)}@`:"",i=Number.isFinite(e.db)&&e.db>=0?e.db:0;return`redis://${r}${t}:${n}/${i}`}async add(e,t){let n={delay:t?.delay?t.delay*1000:void 0,attempts:t?.maxTries,priority:t?.priority,timeout:t?.timeout?t.timeout*1000:void 0,backoff:Array.isArray(t?.backoff)?t.backoff.map((r)=>(Number(r)||1)*1000):typeof t?.backoff==="number"?{type:"fixed",delay:(t.backoff||1)*1000}:void 0};return this.queue.add(e,n)}process(e,t){if(this.isProcessing){Ge.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process(e,t),Ge.info(`Started processing queue with concurrency ${e}`)}async getJob(e){return this.queue.getJob(e)}async getJobs(e){return this.queue.getJobs(e)}async getJobCounts(){return this.queue.getJobCounts()}async removeJob(e){return this.queue.removeJob(e)}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(e){return this.queue.scheduleCron({cronExpression:e.cron,data:e.data,timezone:e.tz,jobId:e.name})}async unscheduleCron(e){return this.queue.unscheduleCron(e)}async getDeadLetterJobs(){return this.queue.getDeadLetterJobs()}async republishDeadLetterJob(e){return this.queue.republishDeadLetterJob(e)}async clearDeadLetterQueue(){return this.queue.clearDeadLetterQueue()}async bulkRemove(e){return this.queue.bulkRemove(e)}async getClusterInfo(){return this.queue.getClusterInfo()}isLeader(){return this.queue.isLeader()}getQueue(){return this.queue}on(e,t){this.queue.events.on(e,t)}}class Nt{config;queues=new Map;defaultConnection="default";constructor(e){this.config=e;for(let[t,n]of Object.entries(e))if(n.driver==="redis")this.queues.set(t,new ne(t,n))}queue(e){let t=e||this.defaultConnection,n=this.queues.get(t);if(!n){let r=this.config[t];if(!r)throw Error(`Queue "${t}" not configured`);n=new ne(t,r),this.queues.set(t,n)}return n}setDefaultConnection(e){this.defaultConnection=e}async closeAll(){let e=Array.from(this.queues.values()).map((t)=>t.close());await Promise.all(e),this.queues.clear()}}function Rr(e,t){let n=new ne(e,t);return async(r,i)=>n.add(r,i)}class At{data;options={};queue;constructor(e,t,n){this.data=n;this.queue=new ne(e,t)}async dispatch(){await this.queue.add(this.data,this.options)}async dispatchNow(){await this.queue.add(this.data,{...this.options,immediate:!0})}delay(e){return this.options.delay=e,this}afterResponse(){return this.options.afterResponse=!0,this}chain(e){return this.options.chainedJobs=e,this}onQueue(e){return this.options.queue=e,this}priority(e){return this.options.priority=e,this}tries(e){return this.options.maxTries=e,this}timeout(e){return this.options.timeout=e,this}backoff(e){return this.options.backoff=e,this}}var fe=()=>{};function _(e){let t=e,n=t?.errno;if(typeof n==="string"&&n.toUpperCase()==="42P01")return!0;if(typeof n==="number"&&n===1146)return!0;if((typeof t?.code==="string"?t.code.toUpperCase():"")==="42P01")return!0;let i=t?.message??"";return i.includes("no such table")||i.includes("doesn't exist")||i.includes("does not exist")}var Ut={};M(Ut,{listDeadLetterJobs:()=>Mt,moveToDeadLetter:()=>Te,purgeDeadLetterJobs:()=>Lt,retryDeadLetterJob:()=>Ht});import{db as re}from"@stacksjs/database";function Ee(){if(Bt)return;Bt=!0,console.warn("[queue/dlq] dead_letter_jobs table missing - DLQ disabled. Run migrations to enable poison-message isolation.")}async function Te(e,t,n=1){let r=new Date().toISOString().slice(0,19).replace("T"," ");try{return await re.insertInto("dead_letter_jobs").values({uuid:e.uuid??crypto.randomUUID(),connection:e.connection??"database",queue:e.queue??"default",payload:e.payload??"{}",exception:e.exception??"unknown",reason:t,total_failures:n,first_failed_at:e.failed_at??r,last_failed_at:r,dead_lettered_at:r}).execute(),!0}catch(i){if(_(i))return Ee(),!1;throw i}}async function Mt(e={}){try{let t=re.selectFrom("dead_letter_jobs").selectAll();if(e.queue)t=t.where("queue","=",e.queue);if(e.reason)t=t.where("reason","=",e.reason);if(e.sinceCutoffMs){let r=new Date(e.sinceCutoffMs).toISOString().slice(0,19).replace("T"," ");t=t.where("dead_lettered_at",">=",r)}if(e.limit&&e.limit>0)t=t.limit(e.limit);return await t.execute()??[]}catch(t){if(_(t))return Ee(),[];throw t}}async function Ht(e){try{let t=await re.selectFrom("dead_letter_jobs").where("id","=",e).selectAll().executeTakeFirst();if(!t)return!1;let n=Math.floor(Date.now()/1000);return await re.insertInto("jobs").values({queue:t.queue,payload:t.payload,attempts:0,reserved_at:null,available_at:n,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),await re.deleteFrom("dead_letter_jobs").where("id","=",e).execute(),!0}catch(t){if(_(t))return Ee(),!1;throw t}}async function Lt(e=30){try{let t=new Date(Date.now()-e*24*60*60*1000).toISOString().slice(0,19).replace("T"," "),n=await re.deleteFrom("dead_letter_jobs").where("dead_lettered_at","<",t).execute();return Number(n?.numDeletedRows??n?.[0]?.numDeletedRows??n?.affectedRows??0)}catch(t){if(_(t))return Ee(),0;throw t}}var Bt=!1;var Re=()=>{};function Ce(){return Wt??=import("@stacksjs/database").catch((e)=>{throw Wt=void 0,e})}function Ye(){if(zt)return;zt=!0,console.warn("[queue/idempotency] job_idempotency table missing - idempotency keys are accepted but NOT enforced. Run migrations to enable dedup.")}async function qr(e){try{let{db:t}=await Ce(),n=await t.selectFrom("job_idempotency").where("idempotency_key","=",e).select(["idempotency_key"]).executeTakeFirst();return Boolean(n)}catch(t){if(_(t))return Ye(),!1;throw t}}async function Dr(e,t,n){try{let{db:r}=await Ce();await r.insertInto("job_idempotency").values({idempotency_key:e,job_name:t,queue:n??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}catch(r){if(_(r)){Ye();return}let i=r?.message??"";if(i.includes("UNIQUE constraint")||i.includes("Duplicate entry"))return;throw r}}async function Xe(e,t,n){try{let{db:r}=await Ce();return await r.insertInto("job_idempotency").values({idempotency_key:e,job_name:t,queue:n??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),"claimed"}catch(r){if(_(r))return Ye(),"unenforced";let i=r?.message??"";if(i.includes("UNIQUE constraint")||i.includes("Duplicate entry"))return"duplicate";throw r}}async function Ze(e){try{let{db:t}=await Ce();await t.deleteFrom("job_idempotency").where("idempotency_key","=",e).execute()}catch{}}var Wt,zt=!1;var et=()=>{};var Zt={};M(Zt,{hashPayload:()=>be,isQuarantined:()=>qe,listQuarantined:()=>Xt,quarantineJob:()=>Gt,recordFailureForPoison:()=>Kt,unquarantineJob:()=>Yt});import{hash as $r}from"crypto";import{db as T}from"@stacksjs/database";function me(){if(Vt)return;Vt=!0,console.warn("[queue/poison] job_quarantine table missing - poison detection disabled. Run migrations to enable.")}function be(e){let t;try{t=typeof e==="string"?e:JSON.stringify(e??null)}catch{t=String(e)}return $r("sha256",t,"hex").slice(0,32)}async function Kt(e,t,n={}){let r=n.maxFailures??5,i=n.windowMinutes??60,o=be(t),a=new Date,l=a.toISOString().slice(0,19).replace("T"," ");try{let s=await T.selectFrom("job_quarantine").where("job_name","=",e).where("payload_hash","=",o).selectAll().executeTakeFirst();if(!s)return await T.insertInto("job_quarantine").values({job_name:e,payload_hash:o,failure_count:1,window_start:l,quarantined_at:null}).execute(),!1;if(s.quarantined_at)return!0;let u=Date.parse(s.window_start.replace(" ","T")+"Z"),c=a.getTime()-u,d=i*60*1000;if(Number.isFinite(u)&&c>d)return await T.updateTable("job_quarantine").set({failure_count:1,window_start:l}).where("id","=",s.id).execute(),!1;let w=s.failure_count+1;if(w>=r)return await T.updateTable("job_quarantine").set({failure_count:w,quarantined_at:l}).where("id","=",s.id).execute(),!0;return await T.updateTable("job_quarantine").set({failure_count:w}).where("id","=",s.id).execute(),!1}catch(s){if(_(s))return me(),!1;throw s}}async function qe(e,t){let n=be(t);try{let r=await T.selectFrom("job_quarantine").where("job_name","=",e).where("payload_hash","in",[n,"*"]).whereNotNull("quarantined_at").select(["id"]).executeTakeFirst();return Boolean(r)}catch(r){if(_(r))return me(),!1;throw r}}async function Gt(e,t){let n=t===void 0?"*":be(t),r=new Date().toISOString().slice(0,19).replace("T"," ");try{let i=await T.selectFrom("job_quarantine").where("job_name","=",e).where("payload_hash","=",n).select(["id"]).executeTakeFirst();if(i)await T.updateTable("job_quarantine").set({quarantined_at:r}).where("id","=",i.id).execute();else await T.insertInto("job_quarantine").values({job_name:e,payload_hash:n,failure_count:0,window_start:r,quarantined_at:r}).execute()}catch(i){if(_(i)){me();return}throw i}}async function Yt(e){try{await T.deleteFrom("job_quarantine").where("job_name","=",e).execute()}catch(t){if(_(t)){me();return}throw t}}async function Xt(){try{return await T.selectFrom("job_quarantine").whereNotNull("quarantined_at").selectAll().execute()??[]}catch(e){if(_(e))return me(),[];throw e}}var Vt=!1;var De=()=>{};function z(e){let t=e?.numUpdatedRows;if(t===null||t===void 0)return 0;if(typeof t==="object")return Number(t.changes??0);return Number(t)}function Nr(e,t){let n=N(e,t.payload||{},{queue:t.queue,tries:t.maxTries,timeout:t.timeout,backoff:Array.isArray(t.backoff)?t.backoff:void 0});return{queue:t.queue||"default",payload:F(n),attempts:0,available_at:Ar(t.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}}async function tt(e,t){let n=Nr(e,t),{db:r}=await import("@stacksjs/database");await r.insertInto("jobs").values(n).execute()}function Ar(e){let t=Date.now();return Math.floor(t/1000+e)}var $e=q(()=>{te()});var Q={};M(Q,{OnQueueEvent:()=>nn,QueueEvents:()=>Ne,QueueMetrics:()=>Ae,emitQueueEvent:()=>V,getGlobalMetrics:()=>Oe,getQueueEvents:()=>ie,getWorkerTracker:()=>ge,onQueueEvent:()=>en,withEvents:()=>tn});import{log as I}from"@stacksjs/logging";class Ne{handlers=new Map;wildcardHandlers=new Set;listenerSubscriptions=new WeakMap;reclaim=new FinalizationRegistry((e)=>{e()});on(e,t){if(!this.handlers.has(e))this.handlers.set(e,new Set);return this.handlers.get(e).add(t),()=>{this.handlers.get(e)?.delete(t)}}subscribeListener(e,t,n){let r=new WeakRef(e),i=(s)=>{let u=r.deref();if(u===void 0){o();return}return n.call(u,s)},o=this.on(t,i),a=()=>{o();let s=r.deref();if(s===void 0)return;let u=this.listenerSubscriptions.get(s);if(!u)return;if(u.delete(a),u.size===0)this.listenerSubscriptions.delete(s),this.reclaim.unregister(s)},l=this.listenerSubscriptions.get(e);if(!l)l=new Set,this.listenerSubscriptions.set(e,l);return l.add(a),this.reclaim.register(e,o,e),a}unsubscribeListener(e){let t=this.listenerSubscriptions.get(e);if(!t)return 0;let n=Array.from(t);for(let r of n)r();return this.listenerSubscriptions.delete(e),this.reclaim.unregister(e),n.length}listenerCount(e){if(e==="*")return this.wildcardHandlers.size;return this.handlers.get(e)?.size??0}onAny(e){return this.wildcardHandlers.add(e),()=>{this.wildcardHandlers.delete(e)}}once(e,t){let n=async(r)=>{this.handlers.get(e)?.delete(n),await t(r)};return this.on(e,n)}async emit(e,t){let n={...t,timestamp:Date.now()};this.logEvent(e,n);let r=this.handlers.get(e);if(r)for(let i of r)try{await i(n)}catch(o){I.error(`Error in queue event handler for ${e}:`,o)}for(let i of this.wildcardHandlers)try{await i(e,n)}catch(o){I.error("Error in wildcard queue event handler:",o)}}logEvent(e,t){let n=t.jobId?`[${t.jobId}]`:"",r=t.queueName?`on ${t.queueName}`:"";switch(e){case"job:added":I.debug(`Job added ${n} ${r}`);break;case"job:processing":I.debug(`Job processing ${n} ${r}`);break;case"job:completed":I.info(`Job completed ${n} ${r} in ${t.duration}ms`);break;case"job:failed":I.error(`Job failed ${n} ${r}:`,t.error);break;case"job:retrying":I.warn(`Job retrying ${n} ${r} (attempt ${t.attemptsMade})`);break;case"job:stalled":I.warn(`Job stalled ${n} ${r}`);break;case"queue:error":I.error(`Queue error ${r}:`,t.error);break}}off(e){this.handlers.delete(e)}removeAllListeners(){this.handlers.clear(),this.wildcardHandlers.clear(),this.listenerSubscriptions=new WeakMap}}function ie(){if(!nt)nt=new Ne;return nt}function en(e,t){let n=ie();if(e==="*")return n.onAny(t);return n.on(e,t)}function V(e,t){return ie().emit(e,t)}function tn(e,t){return async(...n)=>{let r=n[0]?.id||"unknown",i=Date.now();await V("job:processing",{jobId:r,queueName:e,data:n[0]?.data});try{let o=await t(...n);return await V("job:completed",{jobId:r,queueName:e,result:o,duration:Date.now()-i}),o}catch(o){throw await V("job:failed",{jobId:r,queueName:e,error:o,duration:Date.now()-i}),o}}}function nn(e){return function(t,n){if(typeof n!=="object"||n===null)throw TypeError(`@OnQueueEvent('${e}') 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(n.kind!=="method")throw TypeError(`@OnQueueEvent('${e}') can only decorate a class method, but it was applied to a ${n.kind}${n.name===void 0?"":` ('${String(n.name)}')`}. Move the handler into a method, or call onQueueEvent('${e}', handler) directly.`);if(n.static)throw TypeError(`@OnQueueEvent('${e}') cannot decorate the static method '${String(n.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('${e}', MyClass.${String(n.name)}).`);return n.addInitializer(function(){ie().subscribeListener(this,e,t)}),t}}class Ae{jobCounts={added:0,completed:0,failed:0,processing:0};completions=[];errors=[];unsubscribe=[];constructor(){this.setupListeners()}setupListeners(){let e=ie();this.unsubscribe.push(e.on("job:added",()=>{this.jobCounts.added++}),e.on("job:processing",()=>{this.jobCounts.processing++}),e.on("job:completed",(t)=>{this.jobCounts.completed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1);let n=t.duration||0;if(this.completions.push({timestamp:Date.now(),duration:n}),this.completions.length>1000)this.completions.shift()}),e.on("job:failed",(t)=>{if(this.jobCounts.failed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1),t.error){if(this.errors.push({error:t.error,timestamp:Date.now()}),this.errors.length>100)this.errors.shift()}}))}getThroughputPerMinute(){let e=Date.now()-60000;return this.completions.filter((n)=>n.timestamp>=e).length}getAverageProcessingTime(){let e=Date.now()-60000,t=this.completions.filter((n)=>n.timestamp>=e);if(t.length===0)return 0;return t.reduce((n,r)=>n+r.duration,0)/t.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((e)=>e()),this.unsubscribe=[]}}function Oe(){if(!rt)rt=new Ae;return rt}class rn{workers=new Map;register(e,t){this.workers.set(e,{id:e,status:"idle",queue:t,processedCount:0,failedCount:0,lastActivityAt:new Date().toISOString(),startedAt:new Date().toISOString()})}markActive(e){let t=this.workers.get(e);if(t)t.status="active",t.lastActivityAt=new Date().toISOString()}markIdle(e){let t=this.workers.get(e);if(t)t.status="idle",t.lastActivityAt=new Date().toISOString()}recordCompletion(e){let t=this.workers.get(e);if(t)t.processedCount++,t.lastActivityAt=new Date().toISOString()}recordFailure(e){let t=this.workers.get(e);if(t)t.failedCount++,t.lastActivityAt=new Date().toISOString()}unregister(e){let t=this.workers.get(e);if(t)t.status="stopped"}getAll(){return Array.from(this.workers.values())}clear(){this.workers.clear()}}function ge(){return Or}var nt=null,rt=null,Or;var R=q(()=>{Or=new rn});var U={};M(U,{Batch:()=>it,DispatchedBatch:()=>L,PendingBatch:()=>Ie,batchCounterIncrements:()=>on,batchRecordFromHash:()=>ct,batchRecordToHash:()=>dn,getBatchCallbacks:()=>we,isBatchCancelled:()=>mn,recordBatchJobCompletion:()=>lt,recordBatchJobFailure:()=>dt});var{RedisClient:Fr}=globalThis.Bun;import{log as k}from"@stacksjs/logging";import{env as Ir}from"@stacksjs/env";function D(){return Ir.QUEUE_DRIVER||"sync"}class Ie{jobs;options={thenCallbacks:[],catchCallbacks:[],finallyCallbacks:[],progressCallbacks:[]};constructor(e){this.jobs=e.map((t)=>("job"in t)?t:{job:t})}name(e){return this.options.name=e,this}onQueue(e){return this.options.queue=e,this}allowFailures(){return this.options.allowFailures=!0,this}then(e){return this.options.thenCallbacks.push(e),this}catch(e){return this.options.catchCallbacks.push(e),this}finally(e){return this.options.finallyCallbacks.push(e),this}progress(e){return this.options.progressCallbacks.push(e),this}thenHandler(e){return this.options.thenHandler=e,this}catchHandler(e){return this.options.catchHandler=e,this}finallyHandler(e){return this.options.finallyHandler=e,this}async dispatch(){let e=crypto.randomUUID(),t=this.jobs.length;if(t===0)throw Error("Cannot dispatch an empty batch");let n=D();await Br({id:e,name:this.options.name||"",total_jobs:t,pending_jobs:t,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}),Qr(e,this.options);try{let{emitQueueEvent:r}=await Promise.resolve().then(() => (R(),Q));await r("batch:added",{jobId:e,data:{name:this.options.name,totalJobs:t}})}catch{}for(let r=0;r<this.jobs.length;r++){let i=this.jobs[r];if(!i)continue;let{job:o,payload:a}=i,l={...a,_batchId:e,_batchIndex:r};if(this.options.queue&&!o.queue)o.queue=this.options.queue;if(n==="sync")try{await o.dispatchNow(l),await lt(e)}catch(s){await dt(e,`${e}:${r}`,s)}else await o.dispatch(l)}return k.info(`[Batch] Dispatched batch "${this.options.name||e}" with ${t} jobs`),new L(e)}getJobs(){return[...this.jobs]}getOptions(){return this.options}}class L{id;constructor(e){this.id=e}async fresh(){return H(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 e=await this.fresh();if(!e)return 0;return e.total_jobs-e.pending_jobs}async progress(){let e=await this.fresh();if(!e||e.total_jobs===0)return 0;let t=e.total_jobs-e.pending_jobs;return Math.round(t/e.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 e=await this.fresh();if(!e)return[];try{return JSON.parse(e.failed_job_ids||"[]")}catch{return[]}}async cancel(){if(D()==="redis")await Xr(this.id);else await Vr(this.id);k.info(`[Batch] Cancelled batch ${this.id}`);let t=we(this.id);if(t)for(let n of t.finallyCallbacks)try{await n(this)}catch(r){k.error(`[Batch] Error in finally callback for batch ${this.id}:`,r)}}async add(e){let t=await this.fresh();if(!t)throw Error(`Batch ${this.id} not found`);if(t.cancelled_at)throw Error(`Batch ${this.id} has been cancelled`);if(t.finished_at)throw Error(`Batch ${this.id} has already finished`);let n=e.map((o)=>("job"in o)?o:{job:o});if(!await Lr(this.id,n.length))throw Error(`Batch ${this.id} was cancelled, finished or deleted before the jobs could be added`);let i=JSON.parse(t.options||"{}");for(let o=0;o<n.length;o++){let a=n[o];if(!a)continue;let{job:l,payload:s}=a,u={...s,_batchId:this.id,_batchIndex:t.total_jobs+o};if(i.queue&&!l.queue)l.queue=i.queue;await l.dispatch(u)}k.info(`[Batch] Added ${n.length} jobs to batch ${this.id}`)}async delete(){await Ur(this.id),Fe(this.id)}}class it{static create(e){return new Ie(e)}static async find(e){if(!await H(e))return null;return new L(e)}static async all(){return(await Mr()).map((t)=>new L(t.id))}static async prune(e=24){return Wr(e)}}function Qr(e,t){ot.set(e,t)}function we(e){return ot.get(e)}function Fe(e){ot.delete(e)}async function Br(e){if(D()==="redis")await Gr(e);else await an(e)}async function H(e){if(D()==="redis")return Yr(e);return sn(e)}async function Mr(){if(D()==="redis")return hn();return cn()}async function Hr(e,t){if(D()==="redis")await ut(e,t);else await at(e,t)}async function Lr(e,t){if(t===0)return!0;if(D()==="redis")try{let o=await G(),a=`${K}${e}`;return await o.hincrby(a,"total_jobs",t),await o.hincrby(a,"pending_jobs",t),o.close(),!0}catch{}let{db:n,sql:r}=await import("@stacksjs/database"),i=await n.updateTable("job_batches").set(on(r,t)).where("id","=",e).whereNull("cancelled_at").whereNull("finished_at").executeTakeFirst();return z(i)>0}function on(e,t){return{total_jobs:e`total_jobs + ${t}`,pending_jobs:e`pending_jobs + ${t}`}}async function Ur(e){if(D()==="redis")await fn(e);else await un(e)}async function Wr(e){if(D()==="redis")return Zr(e);return ln(e)}function zr(e){return!!(e.then_handler||e.catch_handler||e.finally_handler)}async function an(e){let{db:t}=await import("@stacksjs/database"),n={id:e.id,name:e.name,total_jobs:e.total_jobs,pending_jobs:e.pending_jobs,failed_jobs:e.failed_jobs,failed_job_ids:e.failed_job_ids,options:e.options,cancelled_at:e.cancelled_at,created_at:e.created_at,finished_at:e.finished_at};if(!zr(e)){await t.insertInto("job_batches").values(n).execute();return}try{await t.insertInto("job_batches").values({...n,then_handler:e.then_handler??null,catch_handler:e.catch_handler??null,finally_handler:e.finally_handler??null}).execute()}catch(r){k.warn(`[Batch] Could not persist terminal handlers for batch ${e.id}: ${r?.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 t.insertInto("job_batches").values(n).execute()}}async function sn(e){let{db:t}=await import("@stacksjs/database");return await t.selectFrom("job_batches").where("id","=",e).selectAll().executeTakeFirst()||null}async function cn(){let{db:e}=await import("@stacksjs/database");return await e.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function at(e,t){let{db:n}=await import("@stacksjs/database");await n.updateTable("job_batches").set(t).where("id","=",e).execute()}async function un(e){let{db:t}=await import("@stacksjs/database");await t.deleteFrom("job_batches").where("id","=",e).execute()}async function Vr(e){await at(e,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function ln(e){let{db:t}=await import("@stacksjs/database"),n=new Date(Date.now()-e*60*60*1000).toISOString().slice(0,19).replace("T"," "),r=await t.deleteFrom("job_batches").whereNotNull("finished_at").where("finished_at","<",n).executeTakeFirst();return Number(r?.numDeletedRows??0)}async function Kr(){let{queue:e}=await import("@stacksjs/config"),t=e?.connections?.redis?.redis;if(t?.url)return t.url;let n=t?.password?`:${encodeURIComponent(t.password)}@`:"",r=t?.db?`/${t.db}`:"";return`redis://${n}${t?.host||"localhost"}:${t?.port||6379}${r}`}async function G(){let e=new Fr(await Kr());return await e.connect(),e}function dn(e){return{id:e.id,name:e.name,total_jobs:String(e.total_jobs),pending_jobs:String(e.pending_jobs),failed_jobs:String(e.failed_jobs),failed_job_ids:e.failed_job_ids,options:e.options,cancelled_at:e.cancelled_at||"",created_at:e.created_at,finished_at:e.finished_at||"",then_handler:e.then_handler||"",catch_handler:e.catch_handler||"",finally_handler:e.finally_handler||""}}async function Gr(e){try{let t=await G();await t.hset(`${K}${e.id}`,dn(e)),await t.sadd(st,e.id),t.close()}catch{await an(e)}}function ct(e){if(!e?.id)return null;return{id:e.id,name:e.name??"",total_jobs:Number(e.total_jobs),pending_jobs:Number(e.pending_jobs),failed_jobs:Number(e.failed_jobs),failed_job_ids:e.failed_job_ids??"",options:e.options??"",cancelled_at:e.cancelled_at||null,created_at:e.created_at??"",finished_at:e.finished_at||null,then_handler:e.then_handler||null,catch_handler:e.catch_handler||null,finally_handler:e.finally_handler||null}}async function Yr(e){try{let t=await G(),n=`${K}${e}`,r=await t.hgetall(n);return t.close(),ct(r)}catch{return sn(e)}}async function hn(){try{let e=await G(),t=await e.smembers(st),n=[];for(let r of t){let i=ct(await e.hgetall(`${K}${r}`));if(i)n.push(i)}return e.close(),n}catch{return cn()}}async function ut(e,t){try{let n=await G(),r=`${K}${e}`,i={};for(let[o,a]of Object.entries(t))i[o]=a===null?"":String(a);await n.hset(r,i),n.close()}catch{await at(e,t)}}async function pn(e,t,n){let r=await G();try{let i=`${K}${e}`,o=await r.hincrby(i,"pending_jobs",-1),a=Math.max(o,0);if(o<0)await r.hset(i,{pending_jobs:"0"});if(t)await r.hincrby(i,"failed_jobs",1);let s=!(await r.hgetall(i)).finished_at&&(!t||n?a===0:!0),u=new Date().toISOString().slice(0,19).replace("T"," "),c=s&&await r.hsetnx(i,"terminal_claimed","1");if(c)await r.hset(i,{finished_at:u});if(c&&t&&!n)await r.hset(i,{cancelled_at:u});return c}finally{r.close()}}async function fn(e){try{let t=await G();await t.del(`${K}${e}`),await t.srem(st,e),t.close()}catch{await un(e)}}async function Xr(e){let t=new Date().toISOString().slice(0,19).replace("T"," ");await ut(e,{cancelled_at:t,finished_at:t})}async function Zr(e){try{let t=await hn(),n=Date.now()-e*60*60*1000,r=0;for(let i of t)if(i.finished_at){if(new Date(i.finished_at).getTime()<n)await fn(i.id),r++}return r}catch{return ln(e)}}function oe(e){if(!e)return null;try{let t=JSON.parse(e);if(t&&(t.kind==="job"||t.kind==="module"))return t;return k.warn(`[Batch] handler JSON has unknown kind '${t?.kind}' - skipping`),null}catch(t){return k.warn(`[Batch] failed to parse persistent handler: ${t.message}`),null}}async function ae(e,t){try{if(e.kind==="job"){let{Jobs:i}=await Promise.resolve().then(() => (ye(),Qe));await i.dispatch(e.name,{...e.payload??{},_batchId:t});return}let n=await import(e.module).catch((i)=>(k.warn(`[Batch] persistent handler module not found: ${e.module} (${i.message})`),null));if(!n)return;let r=n[e.export];if(typeof r!=="function"){k.warn(`[Batch] persistent handler export '${e.export}' is not a function on ${e.module}`);return}await r(e.payload,t)}catch(n){k.error(`[Batch] persistent handler threw for batch ${t}:`,n)}}async function lt(e){let t=D(),n=!1;if(t==="redis")n=await pn(e,!1,!1);else{let{db:o,sql:a}=await import("@stacksjs/database");await o.updateTable("job_batches").set({pending_jobs:a`GREATEST(pending_jobs - 1, 0)`}).where("id","=",e).where("pending_jobs",">",0).execute();let l=new Date().toISOString().slice(0,19).replace("T"," "),s=await o.updateTable("job_batches").set({finished_at:l}).where("id","=",e).where("pending_jobs","=",0).whereNull("finished_at").executeTakeFirst();n=z(s)>0}let r=we(e),i=new L(e);if(r)for(let o of r.progressCallbacks)try{await o(i)}catch(a){k.error(`[Batch] Error in progress callback for batch ${e}:`,a)}if(n){try{let{emitQueueEvent:c}=await Promise.resolve().then(() => (R(),Q));await c("batch:completed",{jobId:e})}catch{}let o=await H(e);if(!o){Fe(e),k.info(`[Batch] Batch ${e} finished (record vanished)`);return}let a=JSON.parse(o.options||"{}"),s=!((o.failed_jobs||0)>0)||a.allowFailures;if(r){if(s)for(let c of r.thenCallbacks)try{await c(i)}catch(d){k.error(`[Batch] Error in then callback for batch ${e}:`,d)}for(let c of r.finallyCallbacks)try{await c(i)}catch(d){k.error(`[Batch] Error in finally callback for batch ${e}:`,d)}Fe(e)}if(s){let c=oe(o.then_handler);if(c)await ae(c,e)}else{let c=oe(o.catch_handler);if(c)await ae(c,e)}let u=oe(o.finally_handler);if(u)await ae(u,e);k.info(`[Batch] Batch ${e} finished`)}}async function dt(e,t,n){let r=await H(e);if(!r)return;let i=JSON.parse(r.options||"{}"),o=new L(e),a=we(e),l=!1;if(D()==="redis"){l=await pn(e,!0,!!i.allowFailures);let c=await H(e);if(c){let d=[];try{d=JSON.parse(c.failed_job_ids||"[]")}catch{d=[]}d.push(t),await ut(e,{failed_job_ids:JSON.stringify(d)})}if(a)for(let d of a.catchCallbacks)try{await d(o,n)}catch(w){k.error(`[Batch] Error in catch callback for batch ${e}:`,w)}try{let{emitQueueEvent:d}=await Promise.resolve().then(() => (R(),Q));await d("batch:failed",{jobId:e,error:n})}catch{}}else{let{db:c,sql:d}=await import("@stacksjs/database");await c.updateTable("job_batches").set({pending_jobs:d`GREATEST(pending_jobs - 1, 0)`,failed_jobs:d`failed_jobs + 1`}).where("id","=",e).where("pending_jobs",">",0).execute();try{let h=await H(e);if(h){let x=[];try{x=JSON.parse(h.failed_job_ids||"[]")}catch{x=[]}x.push(t),await Hr(e,{failed_job_ids:JSON.stringify(x)})}}catch{}if(a)for(let h of a.catchCallbacks)try{await h(o,n)}catch(x){k.error(`[Batch] Error in catch callback for batch ${e}:`,x)}try{let{emitQueueEvent:h}=await Promise.resolve().then(() => (R(),Q));await h("batch:failed",{jobId:e,error:n})}catch{}let w=new Date().toISOString().slice(0,19).replace("T"," "),p=c.updateTable("job_batches").set(i.allowFailures?{finished_at:w}:{finished_at:w,cancelled_at:w}).where("id","=",e).whereNull("finished_at");if(i.allowFailures)p=p.where("pending_jobs","=",0);let g=await p.executeTakeFirst();l=z(g)>0}if(!l)return;let s=!!i.allowFailures;if(a){if(s)for(let c of a.thenCallbacks)try{await c(o)}catch(d){k.error(`[Batch] Error in then callback for batch ${e}:`,d)}for(let c of a.finallyCallbacks)try{await c(o)}catch(d){k.error(`[Batch] Error in finally callback for batch ${e}:`,d)}Fe(e)}let u=await H(e);if(u){if(s){let d=oe(u.then_handler);if(d)await ae(d,e)}else{let d=oe(u.catch_handler);if(d)await ae(d,e)}let c=oe(u.finally_handler);if(c)await ae(c,e)}k.info(`[Batch] Batch ${e} finished with failure(s)`)}async function mn(e){return(await H(e))?.cancelled_at!==null}var ot,K="stacks:batch:",st="stacks:batches";var B=q(()=>{$e();ot=new Map});var Qe={};M(Qe,{Jobs:()=>xn,job:()=>kn,jobBatch:()=>Jn,resolveJobFile:()=>jn,runJob:()=>Be});import{appPath as ei,frameworkPath as ti}from"@stacksjs/path";import{env as ni}from"@stacksjs/env";import{enqueueAfterCommit as ri,isInTransaction as ii}from"@stacksjs/database";function _n(){return wn??=import("@stacksjs/router").catch((e)=>{throw wn=void 0,e})}function oi(){return ni.QUEUE_DRIVER||"sync"}function ai(){if(yn)return;yn=!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 A{name;payload;options={};txMode="auto";constructor(e,t){this.name=e;this.payload=t}onQueue(e){return this.options.queue=e,this}delay(e){return this.options.delay=e,this}tries(e){return this.options.tries=e,this}timeout(e){return this.options.timeout=e,this}backoff(e){return this.options.backoff=e,this}withContext(e){return this.options.context=e,this}withIdempotencyKey(e){return this.options.idempotencyKey=e,this}afterCommit(){return this.txMode="after",this}withoutCommit(){return this.txMode="immediate",this}async dispatch(){let{isFaked:e,getFakeQueue:t}=await(bn??=Promise.resolve().then(() => Ke).catch((n)=>{throw bn=void 0,n}));if(e()){t()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(ii()){let r=this.runDispatchPipeline.bind(this);if(ri(async()=>{await r()}))return}else if(this.txMode==="after")ai()}await this.runDispatchPipeline()}async runDispatchPipeline(){let e=!1;if(this.options.idempotencyKey){let t=await Xe(this.options.idempotencyKey,this.name,this.options.queue);if(t==="duplicate")return;e=t==="claimed"}try{if(await qe(this.name,this.payload)){let n=N(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await Te({queue:this.options.queue||"default",payload:F(n),exception:`quarantined: ${this.name}`},"poison-detected"))return}let t=oi();if(t==="database")await this.dispatchToDatabase();else if(t==="redis")await this.dispatchToRedis();else if(t==="sync")await Be(this.name,{payload:this.payload,context:this.options.context,traceId:await vn()});else if(t==="sqs"||t==="memory"||t==="beanstalkd")throw Error(`[queue] Driver "${t}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);else throw Error(`[queue] Unknown QUEUE_DRIVER "${t}". Allowed values: redis, database, sync.`)}catch(t){if(e&&this.options.idempotencyKey)await Ze(this.options.idempotencyKey);throw t}}async dispatchIf(e){if(e)await this.dispatch()}async dispatchUnless(e){if(!e)await this.dispatch()}async dispatchToDatabase(){let e=Math.floor(Date.now()/1000),t=this.options.delay?e+this.options.delay:e,n=N(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff},await vn()),r=F(n),{db:i}=await(gn??=import("@stacksjs/database").catch((o)=>{throw gn=void 0,o}));await i.insertInto("jobs").values({queue:this.options.queue||"default",payload:r,attempts:0,reserved_at:null,available_at:t,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){let e=N(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});de(e);let{RedisQueue:t}=await Promise.resolve().then(() => (fe(),pe)),{queue:n}=await import("@stacksjs/config"),r=n?.connections?.redis;if(!r)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new t(this.options.queue||"default",r).add(e,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await Be(this.name,{payload:this.payload,context:this.options.context})}}function kn(e,t){return new A(e,t)}function Jn(e){let{PendingBatch:t}=(B(),dr(U));return new t(e)}async function jn(e){let t=[ei(`Jobs/${e}.ts`),ti(`defaults/app/Jobs/${e}.ts`)];for(let r of t)if(await Bun.file(r).exists())return r;let n;try{let r=import.meta.resolve("@stacksjs/defaults/package.json");n=`${new URL(".",r).pathname}app/Jobs/${e}.ts`}catch{}if(n&&await Bun.file(n).exists())return n;return null}async function Be(e,t={}){let{withTraceId:n}=await _n(),r=t.traceId??`job:${e}:${Math.random().toString(36).slice(2,10)}`;await n(r,async()=>{let i=await jn(e);if(!i)throw Error(`Job ${e} not found. Looked in app/Jobs/${e}.ts and the framework defaults (storage/framework/defaults/app/Jobs, @stacksjs/defaults).`);let a=(await import(i)).default;if(!a)throw Error(`Job ${e} does not export a default`);if(typeof a.handle==="function")await a.handle(t.payload);else if(typeof a.action==="string")await je(a.action);else if(typeof a.action==="function")await a.action();else if(typeof a==="function")await a(t.payload,t.context);else throw Error(`Job ${e} does not have a valid handler`)})}async function vn(){try{let{getTraceId:e}=await _n();return e()}catch{return}}var bn,gn,wn,yn=!1,xn;var ye=q(()=>{Re();te();et();De();Pe();xn={make(e,t){return new A(e,t)},async dispatch(e,t){await new A(e,t).dispatch()},async dispatchIf(e,t,n){if(e)await new A(t,n).dispatch()},async dispatchUnless(e,t,n){if(!e)await new A(t,n).dispatch()},async dispatchNow(e,t){await new A(e,t).dispatchNow()},dispatchAfter(e,t,n){return new A(t,n).delay(e)},async dispatchOnce(e,t,n){await new A(t,n).withIdempotencyKey(e).dispatch()},async dispatchAfterCommit(e,t){await new A(e,t).afterCommit().dispatch()}}});var Me={};M(Me,{isCircuitOpen:()=>Sn,listCircuitState:()=>qn,pauseQueue:()=>Rn,recordCircuitFailure:()=>Tn,recordCircuitSuccess:()=>En,resumeQueue:()=>Cn});import{db as S}from"@stacksjs/database";function Y(){if(Pn)return;Pn=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing - circuit breaker disabled. Run migrations to enable.")}async function pt(e,t){try{let n=await S.selectFrom("queue_circuit_state").where("queue_name","=",e).selectAll().executeTakeFirst();if(n)return n;return await S.insertInto("queue_circuit_state").values({queue_name:e,success_count:0,failure_count:0,window_start:t,paused_at:null,resume_at:null}).execute(),{queue_name:e,success_count:0,failure_count:0,window_start:t,paused_at:null,resume_at:null}}catch(n){if(_(n))return Y(),null;let r=n?.message??"";if(r.includes("UNIQUE constraint")||r.includes("Duplicate entry"))return await S.selectFrom("queue_circuit_state").where("queue_name","=",e).selectAll().executeTakeFirst();throw n}}async function Sn(e){try{let t=await S.selectFrom("queue_circuit_state").where("queue_name","=",e).select(["paused_at","resume_at"]).executeTakeFirst();if(!t||!t.paused_at)return!1;if(t.resume_at){let n=Date.parse(t.resume_at.replace(" ","T")+"Z");if(Number.isFinite(n)&&Date.now()>=n)return await S.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",e).execute(),!1}return!0}catch(t){if(_(t))return Y(),!1;throw t}}async function En(e,t={}){let n=t.windowSeconds??300,r=new Date().toISOString().slice(0,19).replace("T"," "),i=await pt(e,r);if(!i)return;try{if(ht(i.window_start,n)){await S.updateTable("queue_circuit_state").set({success_count:1,failure_count:0,window_start:r}).where("queue_name","=",e).execute();return}await S.updateTable("queue_circuit_state").set({success_count:i.success_count+1}).where("queue_name","=",e).execute()}catch(o){if(_(o)){Y();return}throw o}}async function Tn(e,t={}){let n=t.failureRateThreshold??0.5,r=t.windowSeconds??300,i=t.pauseSeconds??300,o=t.minObservations??10,a=new Date,l=a.toISOString().slice(0,19).replace("T"," "),s=await pt(e,l);if(!s)return!1;if(s.paused_at)return!1;try{let{success_count:u,failure_count:c}=s;if(ht(s.window_start,r))u=0,c=0;c+=1;let d=u+c,w=d===0?0:c/d;if(d>=o&&w>=n){let g=new Date(a.getTime()+i*1000).toISOString().slice(0,19).replace("T"," ");return await S.updateTable("queue_circuit_state").set({success_count:u,failure_count:c,window_start:l,paused_at:l,resume_at:g}).where("queue_name","=",e).execute(),!0}return await S.updateTable("queue_circuit_state").set({success_count:u,failure_count:c,window_start:ht(s.window_start,r)?l:s.window_start}).where("queue_name","=",e).execute(),!1}catch(u){if(_(u))return Y(),!1;throw u}}function ht(e,t){if(!e)return!0;let n=Date.parse(e.replace(" ","T")+"Z");if(!Number.isFinite(n))return!0;return Date.now()-n>t*1000}async function Rn(e,t=300){let n=new Date,r=n.toISOString().slice(0,19).replace("T"," "),i=new Date(n.getTime()+t*1000).toISOString().slice(0,19).replace("T"," ");await pt(e,r);try{await S.updateTable("queue_circuit_state").set({paused_at:r,resume_at:i}).where("queue_name","=",e).execute()}catch(o){if(_(o)){Y();return}throw o}}async function Cn(e){try{await S.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",e).execute()}catch(t){if(_(t)){Y();return}throw t}}async function qn(){try{return await S.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch(e){if(_(e))return Y(),[];throw e}}var Pn=!1;var ve=()=>{};te();Pe();import{env as Cr}from"@stacksjs/env";var Ot,Ft;function It(){return Cr.QUEUE_DRIVER||"sync"}class Qt{name;description;action;handle;queue;rate;tries;timeout;backoff;backoffConfig;enabled;constructor(e){this.name=e.name,this.description=e.description,this.handle=e.handle,this.queue=e.queue,this.rate=e.rate,this.action=e.action,this.tries=e.tries,this.timeout=e.timeout,this.backoff=e.backoff,this.backoffConfig=e.backoffConfig,this.enabled=e.enabled}async dispatch(...[e]){let{isFaked:t,getFakeQueue:n}=await(Ot??=Promise.resolve().then(() => Ke).catch((i)=>{throw Ot=void 0,i}));if(t()){n()?.dispatch(this.name||"UnknownJob",e,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let r=It();if(r==="sync")return this.dispatchNow(...[e]);if(r==="redis")return this.dispatchToRedis(e);if(r==="database")return this.dispatchToDatabase(e);if(r==="sqs"||r==="memory"||r==="beanstalkd")throw Error(`[queue] Driver "${r}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);throw Error(`[queue] Unknown QUEUE_DRIVER "${r}". Allowed values: redis, database, sync.`)}async dispatchIf(e,...[t]){if(e)return this.dispatch(...[t])}async dispatchUnless(e,...[t]){if(!e)return this.dispatch(...[t])}async dispatchAfter(e,...[t]){let n=It();if(n==="redis")return this.dispatchToRedis(t,{delay:e});if(n==="database")return this.dispatchToDatabase(t,{delay:e});return await new Promise((r)=>setTimeout(r,e*1000)),await this.dispatchNow(...[t])}async dispatchNow(...[e]){if(typeof this.handle==="function")await this.handle(e);else if(typeof this.action==="string")await je(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(e,t){let n=Math.floor(Date.now()/1000),r=t?.delay?n+t.delay:n,i=N(this.name??this.constructor.name,e,{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}),o=F(i),{db:a}=await(Ft??=import("@stacksjs/database").catch((l)=>{throw Ft=void 0,l}));await a.insertInto("jobs").values({queue:this.queue||"default",payload:o,attempts:0,reserved_at:null,available_at:r,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(e,t){let n=N(this.name??this.constructor.name,e,{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});de(n);let{RedisQueue:r}=await Promise.resolve().then(() => (fe(),pe)),{queue:i}=await import("@stacksjs/config"),o=i?.connections?.redis;if(!o)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new r(this.queue||"default",o).add(n,{delay:t?.delay,maxTries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0})}}Pe();ye();et();Re();De();ve();te();var ft=(e)=>`__job_progress__:${e}`,mt=(e)=>`__job_cancel__:${e}`;var _e;function si(e){return Number.isFinite(e)?Math.max(0,Math.min(100,e)):0}async function ci(e,t,n){let{cache:r}=_e??=await import("@stacksjs/cache");await r.set(ft(e),{percent:si(t),message:n,updatedAt:Date.now()},3600)}async function ui(e){let{cache:t}=_e??=await import("@stacksjs/cache");return await t.get(ft(e))??null}async function li(e){let{cache:t}=_e??=await import("@stacksjs/cache");await t.set(mt(e),1,3600)}async function di(e){let{cache:t}=_e??=await import("@stacksjs/cache");return Boolean(await t.get(mt(e)))}async function hi(e){let{cache:t}=_e??=await import("@stacksjs/cache");await Promise.all([t.del(ft(e)),t.del(mt(e))])}import{log as se}from"@stacksjs/logging";import*as Dn from"@stacksjs/path";class $n{jobs=new Map;initialized=!1;register(e){this.jobs.set(e.name,e),se.debug(`Registered job: ${e.name} (${e.type})`)}get(e){return this.jobs.get(e)}all(){return Array.from(this.jobs.values())}byQueue(e){return this.all().filter((t)=>t.config.queue===e)}scheduled(){return this.all().filter((e)=>e.config.rate||e.config.schedule)}has(e){return this.jobs.has(e)}clear(){this.jobs.clear(),this.initialized=!1}setInitialized(e){this.initialized=e}isInitialized(){return this.initialized}}var X=new $n;async function bt(e){let t=e||Dn.userJobsPath(),n=[];try{let r=new Bun.Glob("**/*.{ts,js}"),i={cwd:t,onlyFiles:!0,absolute:!0};for await(let o of r.scan(i)){if(o.includes(".test.")||o.includes(".spec.")||o.endsWith("index.ts")||o.endsWith("index.js"))continue;try{let a=await pi(o);if(a)n.push(a),X.register(a)}catch(a){se.warn(`Failed to load job from ${o}: ${a.message}`)}}return X.setInitialized(!0),se.info(`Discovered ${n.length} jobs from ${t}`),n}catch(r){return se.error(`Failed to discover jobs: ${r.message}`),[]}}async function pi(e){try{let t=await import(e),n=e.split("/").pop()?.replace(/\.(ts|js)$/,"")||"UnknownJob";if(t.default&&typeof t.default==="function"){let r=t.default;if(typeof r.handle==="function"||typeof r.prototype?.handle==="function"){let i=r.config||{};return{name:i.name||n,path:e,config:{name:i.name||n,description:i.description,queue:i.queue||"default",tries:i.retries||3,timeout:i.timeout,withoutOverlapping:i.withoutOverlapping,schedule:i.schedule,retryAfter:i.retryAfter},type:"class",module:r}}}if(t.default&&typeof t.default==="object"){let r=t.default;if(typeof r.handle==="function"||typeof r.action==="string")return{name:r.name||n,path:e,config:{name:r.name||n,description:r.description,queue:r.queue||"default",tries:r.tries||3,backoff:r.backoff,rate:r.rate,timeout:r.timeout||r.timeOut,backoffConfig:r.backoffConfig},type:"function",module:r}}return null}catch(t){return se.debug(`Could not load job from ${e}: ${t.message}`),null}}function fi(e){return X.get(e)}function mi(){return X.all()}function gt(){return X.scheduled()}async function bi(e,t){let n=X.get(e);if(!n)throw Error(`Job "${e}" not found. Did you run discoverJobs()?`);try{if(n.type==="class"){if(typeof n.module.handle==="function")return await n.module.handle(t);return await new n.module().handle(t)}else{if(typeof n.module.handle==="function")return await n.module.handle(t);throw Error(`Job "${e}" does not have a handle method`)}}catch(r){throw se.error(`Failed to execute job "${e}": ${r.message}`),r}}function gi(e){let t=e.config;return{name:t.name,queue:t.queue,tries:t.tries,backoff:t.backoff,timeout:t.timeout,backoffConfig:t.backoffConfig,rate:t.rate}}import{log as J}from"@stacksjs/logging";R();import{log as An}from"@stacksjs/logging";var Nn=!1;async function On(){if(Nn)return!0;try{let{db:e}=await import("@stacksjs/database");return await e.unsafe("CREATE TABLE IF NOT EXISTS scheduled_job_runs (job_name VARCHAR(255) PRIMARY KEY, last_run_at VARCHAR(64) NOT NULL)").execute(),Nn=!0,!0}catch(e){return An.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${e instanceof Error?e.message:String(e)}`),!1}}async function Fn(e){if(!await On())return null;try{let{db:t}=await import("@stacksjs/database"),n=await t.selectFrom("scheduled_job_runs").where("job_name","=",e).select(["last_run_at"]).executeTakeFirst();if(!n?.last_run_at)return null;let r=new Date(n.last_run_at);return Number.isNaN(r.getTime())?null:r}catch{return null}}async function In(e,t){if(!await On())return;try{let{db:n}=await import("@stacksjs/database"),r=t.toISOString();await n.deleteFrom("scheduled_job_runs").where("job_name","=",e).execute(),await n.insertInto("scheduled_job_runs").values({job_name:e,last_run_at:r}).execute()}catch{}}function wi(e){return`%"jobName":"${e.replace(/[\\%_]/g,(n)=>`\\${n}`)}"%`}async function Qn(e){try{let{db:t}=await import("@stacksjs/database"),r=await t.unsafe("SELECT 1 AS present FROM jobs WHERE payload LIKE ? ESCAPE '\\' LIMIT 1",[wi(e)]).execute(),i=Array.isArray(r)?r:r?.rows??[];return Array.isArray(i)&&i.length>0}catch(t){return An.debug(`[scheduler] overlap check unavailable, dispatching anyway: ${t instanceof Error?t.message:String(t)}`),!1}}$e();var Un={checkInterval:60000,preventOverlapping:!0},y={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...Un}},Bn=new Set;function yi(e,t){if(Bn.has(e))return;Bn.add(e),J.warn(`[scheduler] Cron expression "${e}" specifies seconds="${t}" but the scheduler ticks at minute granularity - the seconds field is being ignored. Use a 5-field expression to avoid this warning, or wait for sub-minute scheduling support.`)}var Mn=!1,Hn=new Map;function vi(e){let t=Hn.get(e);if(!t)t=new Intl.DateTimeFormat("en-US",{timeZone:e,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),Hn.set(e,t);return t}function wt(e,t){if(t&&t!=="local"&&t!=="system")try{let n=vi(t).formatToParts(e),r=(a)=>n.find((l)=>l.type===a)?.value??"",i={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},o=Number(r("hour"));if(o===24)o=0;return{minute:Number(r("minute")),hour:o,day:Number(r("day")),month:Number(r("month")),dayOfWeek:i[r("weekday")]??e.getDay()}}catch{if(!Mn)Mn=!0,J.warn(`[scheduler] Invalid timezone "${t}"; falling back to system local time.`)}return{minute:e.getMinutes(),hour:e.getHours(),day:e.getDate(),month:e.getMonth()+1,dayOfWeek:e.getDay()}}function _i(e,t,n){let r=wt(new Date,n),i=r.minute,o=r.hour,a=r.day,l=r.month,s=r.dayOfWeek;if(t){let h=wt(t,n);if(h.minute===i&&h.hour===o&&h.day===a)return!1}let u=e.trim().split(/\s+/);if(u.length===6){let h=u[0];if(h&&h!=="0"&&h!=="*")yi(e,h);u=u.slice(1)}if(u.length<5)return J.warn(`Invalid cron expression: ${e}`),!1;let[c,d,w,p,g]=u;return O(c,i,0,59)&&O(d,o,0,23)&&O(w,a,1,31)&&O(p,l,1,12)&&O(g,s,0,6)}function O(e,t,n,r){if(e==="*")return!0;if(e.includes(","))return e.split(",").map((a)=>Number.parseInt(a.trim(),10)).includes(t);if(e.includes("-")){let[o,a]=e.split("-").map((l)=>Number.parseInt(l.trim(),10));return t>=o&&t<=a}if(e.includes("/")){let[o,a]=e.split("/"),l=Number.parseInt(a,10);if(o==="*")return t%l===0;if(o.includes("-")){let[s,u]=o.split("-").map((c)=>Number.parseInt(c.trim(),10));return t>=s&&t<=u&&(t-s)%l===0}}let i=Number.parseInt(e,10);return!Number.isNaN(i)&&t===i}function Wn(e){let n={"@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 * * * *"}[e.toLowerCase()];if(n)return n;let r=e.match(/^Every\.(\w+)$/i);if(r&&r[1]!==void 0){let o=r[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 * *"}[o]||null}let i=e.split(/\s+/).length;if(i>=5&&i<=6)return e;return null}function zn(e,t){let n=e.trim().split(/\s+/),r=n.length===6?n.slice(1):n;if(r.length<5)return null;let[i,o,a,l,s]=r,u=new Date;u.setSeconds(0,0);let c=527040;for(let d=1;d<=c;d++){let w=new Date(u.getTime()+d*60000),p=wt(w,t);if(O(i,p.minute,0,59)&&O(o,p.hour,0,23)&&O(a,p.day,1,31)&&O(l,p.month,1,12)&&O(s,p.dayOfWeek,0,6))return w}return null}async function ki(e={}){if(y.isRunning){J.warn("Scheduler is already running");return}y.config={...Un,...e},y.isRunning=!0,y.isShuttingDown=!1,await bt();let t=gt();for(let i of t){let o=i.config.rate||i.config.schedule;if(o){let a=Wn(o);if(a){let l=await Fn(i.name);y.jobs.set(i.name,{job:i,lastRun:l,nextRun:zn(a,y.config.timezone),isRunning:!1}),J.info(`Registered scheduled job: ${i.name} (${a})`)}else J.warn(`Invalid schedule for job ${i.name}: ${o}`)}}if(y.jobs.size===0){J.info("No scheduled jobs found");return}J.info(`Scheduler started with ${y.jobs.size} job(s)`),process.on("SIGINT",()=>yt()),process.on("SIGTERM",()=>yt());let n=!1,r=()=>{if(y.isShuttingDown)return;let i=y.config.checkInterval,o=i-Date.now()%i;y.checkInterval=setTimeout(()=>{if(!y.isShuttingDown&&!n)n=!0,Ln().catch((a)=>J.error("Scheduler check failed:",a)).finally(()=>{n=!1});r()},o),y.checkInterval?.unref?.()};r(),await Ln()}async function Ln(){for(let[e,t]of y.jobs){let n=t.job.config.rate||t.job.config.schedule;if(!n)continue;let r=Wn(n);if(!r)continue;if(_i(r,t.lastRun,y.config.timezone)){if((y.config.preventOverlapping||t.job.config.withoutOverlapping)&&await Qn(e)){J.debug(`Skipping ${e}: previous execution still running`);continue}if(t.isRunning){J.debug(`Skipping ${e}: a dispatch for it is already in flight`);continue}try{t.isRunning=!0,t.lastRun=new Date,t.nextRun=zn(r,y.config.timezone),await In(e,t.lastRun),J.info(`Dispatching scheduled job: ${e}`),await V("job:added",{jobId:`scheduled-${e}-${Date.now()}`,queueName:t.job.config.queue||"default",jobName:e}),await tt(e,{queue:t.job.config.queue||"default",payload:{},maxTries:t.job.config.tries||3,timeout:t.job.config.timeout||60}),t.isRunning=!1,J.info(`Scheduled job ${e} dispatched to queue`)}catch(o){t.isRunning=!1,J.error(`Failed to dispatch scheduled job ${e}:`,o)}}}}async function yt(){if(!y.isRunning)return;if(J.info("Stopping scheduler..."),y.isShuttingDown=!0,y.checkInterval)clearTimeout(y.checkInterval),y.checkInterval=null;y.isRunning=!1,y.jobs.clear(),J.info("Scheduler stopped")}function xi(){return{isRunning:y.isRunning,jobCount:y.jobs.size,jobs:Array.from(y.jobs.entries()).map(([e,t])=>({name:e,schedule:t.job.config.rate||t.job.config.schedule,lastRun:t.lastRun,nextRun:t.nextRun,isRunning:t.isRunning}))}}function Ji(){return y.isRunning}function ji(){return new Map(y.jobs)}async function Pi(e){let t=y.jobs.get(e);if(!t)throw Error(`Scheduled job "${e}" not found`);J.info(`Manually triggering scheduled job: ${e}`),await tt(e,{queue:t.job.config.queue||"default",payload:{},maxTries:t.job.config.tries||3,timeout:t.job.config.timeout||60})}R();R();import{log as Si}from"@stacksjs/logging";function Vn(e,t=Math.floor(Date.now()/1000)){if(e.reserved_at)return"processing";let n=typeof e.available_at==="number"?e.available_at:typeof e.available_at==="string"&&e.available_at.trim()?Number(e.available_at):Number.NaN;if(Number.isFinite(n)&&n>t)return"delayed";return"pending"}var Ei={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function vt(e={}){let t={...Ei,...e},n=[],r=new Date,i=Math.floor(r.getTime()/1000);try{let{db:o}=await import("@stacksjs/database"),a=await o.selectFrom("jobs").selectAll().execute(),l=await o.selectFrom("failed_jobs").selectAll().execute(),s=new Map;for(let f of a){let v=f.queue||"default";if(!s.has(v))s.set(v,{pending:0,processing:0,delayed:0});let P=s.get(v),E=Vn(f,i);if(E==="processing")P.processing++;else if(E==="delayed")P.delayed++;else if(P.pending++,f.created_at){let ar=typeof f.created_at==="number"?f.created_at:Math.floor(new Date(f.created_at).getTime()/1000),Pt=i-ar;if(!P.oldestAge||Pt>P.oldestAge)P.oldestAge=Pt}}let u=new Map;for(let f of l){let v=f.queue||"default";u.set(v,(u.get(v)||0)+1)}let c=[],d=new Set([...s.keys(),...u.keys()]),w=t.queues?[...d].filter((f)=>t.queues.includes(f)):[...d],p=0,g=0,h=0,x=0;for(let f of w){let v=s.get(f)||{pending:0,processing:0,delayed:0},P=u.get(f)||0;p+=v.pending,g+=v.processing,h+=v.delayed,x+=P;let E="healthy";if(v.pending>=t.maxPendingCritical)E="unhealthy",n.push({level:"critical",message:`Queue "${f}" has ${v.pending} pending jobs (threshold: ${t.maxPendingCritical})`,queue:f,timestamp:r.toISOString()});else if(v.pending>=t.maxPendingWarning)E="degraded",n.push({level:"warning",message:`Queue "${f}" has ${v.pending} pending jobs (threshold: ${t.maxPendingWarning})`,queue:f,timestamp:r.toISOString()});if(P>=t.maxFailedCritical)E="unhealthy",n.push({level:"critical",message:`Queue "${f}" has ${P} failed jobs (threshold: ${t.maxFailedCritical})`,queue:f,timestamp:r.toISOString()});else if(P>=t.maxFailedWarning){if(E==="healthy")E="degraded";n.push({level:"warning",message:`Queue "${f}" has ${P} failed jobs (threshold: ${t.maxFailedWarning})`,queue:f,timestamp:r.toISOString()})}if(v.oldestAge){if(v.oldestAge>=t.maxJobAgeCritical)E="unhealthy",n.push({level:"critical",message:`Queue "${f}" has a job waiting for ${Math.floor(v.oldestAge/3600)} hours`,queue:f,timestamp:r.toISOString()});else if(v.oldestAge>=t.maxJobAgeWarning){if(E==="healthy")E="degraded";n.push({level:"warning",message:`Queue "${f}" has a job waiting for ${Math.floor(v.oldestAge/60)} minutes`,queue:f,timestamp:r.toISOString()})}}c.push({name:f,status:E,pending:v.pending,processing:v.processing,delayed:v.delayed,failed:P,oldestJobAge:v.oldestAge})}let Jt=Oe().getMetrics(),rr=Jt.throughputPerMinute,ir=Jt.averageDuration,jt=p+g+h+x,le=jt>0?x/jt:0,ee="healthy";if(c.some((f)=>f.status==="unhealthy"))ee="unhealthy";else if(c.some((f)=>f.status==="degraded"))ee="degraded";if(le>=t.maxErrorRateCritical)ee="unhealthy",n.push({level:"critical",message:`Overall error rate is ${(le*100).toFixed(1)}% (threshold: ${t.maxErrorRateCritical*100}%)`,timestamp:r.toISOString()});else if(le>=t.maxErrorRateWarning){if(ee==="healthy")ee="degraded";n.push({level:"warning",message:`Overall error rate is ${(le*100).toFixed(1)}% (threshold: ${t.maxErrorRateWarning*100}%)`,timestamp:r.toISOString()})}let or=ge().getAll().map((f)=>({id:f.id,status:f.status,queue:f.queue,processedCount:f.processedCount,failedCount:f.failedCount,lastActivityAt:f.lastActivityAt}));return{status:ee,timestamp:r.toISOString(),queues:c,workers:or,metrics:{totalPending:p,totalProcessing:g,totalDelayed:h,totalFailed:x,throughputPerMinute:rr,averageProcessingTime:ir,errorRate:le},alerts:n}}catch(o){Si.error("Failed to perform queue health check:",o);let l=ge().getAll().map((s)=>({id:s.id,status:s.status,queue:s.queue,processedCount:s.processedCount,failedCount:s.failedCount,lastActivityAt:s.lastActivityAt}));return{status:"unhealthy",timestamp:r.toISOString(),queues:[],workers:l,metrics:{totalPending:0,totalProcessing:0,totalDelayed:0,totalFailed:0,throughputPerMinute:0,averageProcessingTime:0,errorRate:0},alerts:[{level:"critical",message:`Health check failed: ${o.message}`,timestamp:r.toISOString()}]}}}function Ti(e={}){return async(t)=>{let n=await vt(e),r=n.status==="healthy"?200:n.status==="degraded"?207:503;return new Response(JSON.stringify(n,null,2),{status:r,headers:{"Content-Type":"application/json","Cache-Control":"no-store"}})}}async function Ri(e={}){return(await vt(e)).status==="healthy"}import{log as C}from"@stacksjs/logging";var Ci={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function ke(e){return String(e).replace(/[&<>"']/g,(t)=>Ci[t]??t)}class _t{config;notificationCount=0;lastResetTime=Date.now();pendingBatch=[];batchTimeout=null;activeFlush=null;constructor(e){this.config=e}async notify(e){if(this.config.filter&&!this.config.filter(e))return;if(this.config.rateLimit){let t=Date.now();if(t-this.lastResetTime>3600000)this.notificationCount=0,this.lastResetTime=t;if(this.notificationCount>=this.config.rateLimit){C.debug("Rate limit reached for failed job notifications");return}}if(this.config.batch){if(this.pendingBatch.push(e),!this.batchTimeout)this.batchTimeout=setTimeout(()=>{this.batchTimeout=null,this.activeFlush=this.flushBatch().catch((t)=>C.error("Failed to flush notification batch:",t)).finally(()=>{this.activeFlush=null})},this.config.batchInterval||60000),this.batchTimeout.unref?.();return}await this.sendNotifications([e])}async flushBatch(){if(this.pendingBatch.length===0)return;let e=[...this.pendingBatch];if(this.pendingBatch=[],this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;await this.sendNotifications(e)}async shutdown(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.activeFlush)await this.activeFlush.catch(()=>{});await this.flushBatch().catch((e)=>C.error("Failed to flush notification batch on shutdown:",e))}async sendNotifications(e){let t=[];for(let n of this.config.channels)switch(n){case"email":if(this.config.email)t.push(this.sendEmail(e));break;case"slack":if(this.config.slack)t.push(this.sendSlack(e));break;case"discord":if(this.config.discord)t.push(this.sendDiscord(e));break;case"webhook":if(this.config.webhook)t.push(this.sendWebhook(e));break;case"log":this.logFailures(e);break}try{await Promise.all(t)}catch(n){C.error("Failed to send job failure notifications:",n)}finally{this.notificationCount+=e.length}}async sendEmail(e){let t=this.config.email,n=t.subject||`[Queue] ${e.length} Job(s) Failed`,r=this.formatEmailBody(e);try{let{mail:i}=await import("@stacksjs/email");await i.send({to:Array.isArray(t.to)?t.to:[t.to],from:t.from?{address:t.from}:void 0,subject:n,html:r})}catch(i){C.error("Failed to send email notification:",i)}}formatEmailBody(e){let t=e.map((n)=>`
2
+ var Cn=Object.defineProperty;var qn=(e)=>e;function Dn(e,t){this[e]=qn.bind(null,t)}var $n=(e,t)=>{for(var n in t)Cn(e,n,{get:t[n],enumerable:!0,configurable:!0,set:Dn.bind(t,n)})};var q=(e,t,n)=>()=>{if(e)try{t=e(e=0)}catch(r){n=[r]}if(n)throw n[0];return t};function A(e,t,n,r){return{jobName:e,payload:t,options:n,envelopeVersion:1,dispatchedAt:new Date().toISOString(),...r?{traceId:r}:{}}}function I(e){try{return JSON.stringify(e)}catch(t){throw An(e,t)}}function le(e){I(e)}function An(e,t){if(On(t)){let i=null;try{i=Ae(e,"",new Set)}catch{}let o=i?`\`${i.path}\` is a BigInt (${i.value}n)`:"it holds a BigInt";return Error(`[queue] Job "${e.jobName}" was not dispatched: ${o}, and job envelopes travel as JSON, which cannot represent one. Convert it where you dispatch - \`String(value)\` keeps every digit, \`Number(value)\` is exact below 2^53 - and convert it back inside the job handler. See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts.`,{cause:t})}if(Fn(t))return Error(`[queue] Job "${e.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 - 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:t});let r=t instanceof Error?t.message:String(t);return Error(`[queue] Job "${e.jobName}" was not dispatched: serializing its payload threw (${r}). JSON rejects only two things on its own and this was neither, so the throw came from the payload - a \`toJSON()\` or a property getter. See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts.`,{cause:t})}function On(e){return e instanceof TypeError&&/bigint/i.test(e.message)}function Fn(e){return e instanceof TypeError&&/circular|cyclic/i.test(e.message)}function Ae(e,t,n){if(typeof e==="bigint")return{path:t||"envelope",value:e};if(!e||typeof e!=="object")return null;if(n.has(e))return null;if(n.add(e),Array.isArray(e)){for(let r=0;r<e.length;r++){let i=Ae(e[r],`${t}[${r}]`,n);if(i)return i}return null}for(let[r,i]of Object.entries(e)){let o=Ae(i,t?`${t}.${r}`:r,n);if(o)return o}return null}function xt(e,t){if(Oe.has(e))return;Oe.add(e),console.warn(t)}function In(){Oe.clear()}function Je(e){let t;if(typeof e==="string")try{t=JSON.parse(e)}catch{return{ok:!1,reason:"malformed",detail:"not valid JSON"}}else if(e&&typeof e==="object")t=e;else return{ok:!1,reason:"malformed",detail:`expected string or object, got ${typeof e}`};if(t.envelopeVersion===1){if(typeof t.jobName!=="string")return{ok:!1,reason:"missing-job-name"};return{ok:!0,envelope:{jobName:t.jobName,payload:t.payload,options:t.options??void 0,envelopeVersion:1,dispatchedAt:typeof t.dispatchedAt==="string"?t.dispatchedAt:new Date(0).toISOString(),...typeof t.traceId==="string"&&t.traceId?{traceId:t.traceId}:{}},source:"v1"}}if(typeof t.envelopeVersion==="number"&&t.envelopeVersion>1)return{ok:!1,reason:"unknown-version",detail:`envelopeVersion=${t.envelopeVersion}, this worker speaks v1`};if(typeof t.jobName==="string")return xt("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:t.jobName,payload:t.payload,options:t.options??void 0,envelopeVersion:1,dispatchedAt:typeof t.dispatchedAt==="string"?t.dispatchedAt:new Date(0).toISOString(),...typeof t.traceId==="string"&&t.traceId?{traceId:t.traceId}:{}},source:"v0-implicit"};if(typeof t.job==="string")return xt("laravel-legacy","[queue/envelope] Processing Laravel-legacy job envelope (`{ job, data }` shape). Will continue to process but the queue table contains migration-era rows - consider flushing once they drain."),{ok:!0,envelope:{jobName:t.job.replace(/^App\\+Jobs\\+/,"").replace(/^.*\\/,""),payload:t.data,options:void 0,envelopeVersion:1,dispatchedAt:new Date(0).toISOString()},source:"laravel-legacy"};return{ok:!1,reason:"missing-job-name"}}var Nn=1,Oe;var ee=q(()=>{Oe=new Set});import{getActionRunner as Qn,runNamedAction as je,setActionRunner as Bn}from"@stacksjs/action-runner";var Pe=()=>{};var Be={};$n(Be,{QueueTester:()=>Se,createQueueTester:()=>St,expectJobToFail:()=>Tt,fake:()=>Ie,getFakeQueue:()=>jt,isFaked:()=>Pt,restore:()=>Qe,runJob:()=>Et});class Jt{dispatchedJobs=[];pushedJobs=[];processedJobs=[];failedJobs=[];dispatch(e,t,n={}){this.dispatchedJobs.push({name:e,data:t,options:n,dispatchedAt:new Date,queue:n.queue||"default"})}push(e,t,n={}){this.pushedJobs.push({name:e,data:t,options:n,dispatchedAt:new Date,queue:n.queue||"default"})}dispatched(e){if(e)return this.dispatchedJobs.filter((t)=>t.name===e);return[...this.dispatchedJobs]}pushed(e){if(e)return this.pushedJobs.filter((t)=>t.name===e);return[...this.pushedJobs]}assertDispatched(e,t){let n=this.dispatched(e);if(n.length===0)throw Error(`Expected job "${e}" to be dispatched, but it was not.`);if(t){if(n.filter(t).length===0)throw Error(`Expected job "${e}" to be dispatched matching the callback, but no matching jobs were found.`)}}assertNotDispatched(e){let t=this.dispatched(e);if(t.length>0)throw Error(`Expected job "${e}" to not be dispatched, but it was dispatched ${t.length} time(s).`)}assertDispatchedTimes(e,t){let n=this.dispatched(e);if(n.length!==t)throw Error(`Expected job "${e}" to be dispatched ${t} time(s), but it was dispatched ${n.length} time(s).`)}assertNothingDispatched(){if(this.dispatchedJobs.length>0){let e=[...new Set(this.dispatchedJobs.map((t)=>t.name))].join(", ");throw Error(`Expected no jobs to be dispatched, but found: ${e}`)}}assertPushed(e,t){let n=this.pushed(e);if(n.length===0)throw Error(`Expected job "${e}" to be pushed, but it was not.`);if(t){if(n.filter(t).length===0)throw Error(`Expected job "${e}" to be pushed matching the callback, but no matching jobs were found.`)}}assertPushedWithDelay(e,t){if(this.pushed(e).filter((i)=>i.options?.delay===t).length===0)throw Error(`Expected job "${e}" to be pushed with delay ${t}ms, but no matching jobs were found.`)}assertPushedOn(e,t){if(this.pushed(t).filter((r)=>r.queue===e).length===0)throw Error(`Expected job "${t}" to be pushed on queue "${e}", but it was not.`)}async processJob(e,t){let n=this.dispatchedJobs.find((r)=>r.name===e);if(!n)throw Error(`No dispatched job found with name "${e}"`);try{await t(n.data),this.processedJobs.push(n)}catch(r){throw this.failedJobs.push({job:n,error:r}),r}}processed(e){if(e)return this.processedJobs.filter((t)=>t.name===e);return[...this.processedJobs]}failed(e){if(e)return this.failedJobs.filter((t)=>t.job.name===e);return[...this.failedJobs]}reset(){this.dispatchedJobs=[],this.pushedJobs=[],this.processedJobs=[],this.failedJobs=[]}}function Ie(){return de=new Jt,de}function jt(){return de}function Pt(){return de!==null}function Qe(){de=null}class Se{queue;constructor(){this.queue=Ie()}dispatch(e,t,n={}){return this.queue.dispatch(e,t,n),this}push(e,t,n={}){return this.queue.push(e,t,n),this}assertDispatched(e,t){return this.queue.assertDispatched(e,t),this}assertNotDispatched(e){return this.queue.assertNotDispatched(e),this}assertDispatchedTimes(e,t){return this.queue.assertDispatchedTimes(e,t),this}assertNothingDispatched(){return this.queue.assertNothingDispatched(),this}dispatched(e){return this.queue.dispatched(e)}reset(){return this.queue.reset(),this}cleanup(){Qe()}}function St(){return new Se}async function Et(e,t){return await e.handle(t)}async function Tt(e,t,n){try{throw await e.handle(t),Error("Expected job to fail, but it succeeded")}catch(r){if(r.message==="Expected job to fail, but it succeeded")throw r;if(n){let i=r.message;if(typeof n==="string"){if(!i.includes(n))throw Error(`Expected error to contain "${n}", got "${i}"`)}else if(!n.test(i))throw Error(`Expected error to match ${n}, got "${i}"`)}return r}}var de=null;import{Queue as Mn,batch as Ii,chain as Qi,dispatch as Bi,dispatchAfter as Mi,dispatchSync as Hi,getQueueManager as Li,QueueManager as Ui,setQueueManager as Wi}from"@stacksjs/bun-queue";import{log as Me}from"@stacksjs/logging";class Q{queue;config;isProcessing=!1;constructor(e,t){this.config=t,this.queue=new Mn(e,{driver:"redis",prefix:t.prefix,redis:t.redis?{url:t.redis.url||this.buildRedisUrl(t.redis)}:void 0,defaultJobOptions:t.defaultJobOptions?{delay:t.defaultJobOptions.delay,attempts:t.defaultJobOptions.attempts,backoff:t.defaultJobOptions.backoff,removeOnComplete:t.defaultJobOptions.removeOnComplete,removeOnFail:t.defaultJobOptions.removeOnFail,priority:t.defaultJobOptions.priority,lifo:t.defaultJobOptions.lifo,timeout:t.defaultJobOptions.timeout,jobId:t.defaultJobOptions.jobId,dependsOn:t.defaultJobOptions.dependsOn,keepJobs:t.defaultJobOptions.keepJobs,deadLetter:t.defaultJobOptions.deadLetter}:void 0,limiter:t.limiter,metrics:t.metrics,stalledJobCheckInterval:t.stalledJobCheckInterval,maxStalledJobRetries:t.maxStalledJobRetries,distributedLock:t.distributedLock,defaultDeadLetterOptions:t.defaultDeadLetterOptions,horizontalScaling:t.horizontalScaling,logLevel:t.logLevel}),Me.debug(`Redis queue "${e}" initialized`)}buildRedisUrl(e){let t=e.host||"localhost",n=e.port||6379,r=e.password?`:${encodeURIComponent(e.password)}@`:"",i=Number.isFinite(e.db)&&e.db>=0?e.db:0;return`redis://${r}${t}:${n}/${i}`}async add(e,t){let n={delay:t?.delay?t.delay*1000:void 0,attempts:t?.maxTries,priority:t?.priority,timeout:t?.timeout?t.timeout*1000:void 0,backoff:Array.isArray(t?.backoff)?t.backoff.map((r)=>(Number(r)||1)*1000):typeof t?.backoff==="number"?{type:"fixed",delay:(t.backoff||1)*1000}:void 0};return this.queue.add(e,n)}process(e,t){if(this.isProcessing){Me.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process(e,t),Me.info(`Started processing queue with concurrency ${e}`)}async getJob(e){return this.queue.getJob(e)}async getJobs(e){return this.queue.getJobs(e)}async getJobCounts(){return this.queue.getJobCounts()}async removeJob(e){return this.queue.removeJob(e)}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(e){return this.queue.scheduleCron({cronExpression:e.cron,data:e.data,timezone:e.tz,jobId:e.name})}async unscheduleCron(e){return this.queue.unscheduleCron(e)}async getDeadLetterJobs(){return this.queue.getDeadLetterJobs()}async republishDeadLetterJob(e){return this.queue.republishDeadLetterJob(e)}async clearDeadLetterQueue(){return this.queue.clearDeadLetterQueue()}async bulkRemove(e){return this.queue.bulkRemove(e)}async getClusterInfo(){return this.queue.getClusterInfo()}isLeader(){return this.queue.isLeader()}getQueue(){return this.queue}on(e,t){this.queue.events.on(e,t)}}var he=()=>{};function v(e){let t=e,n=t?.errno;if(typeof n==="string"&&n.toUpperCase()==="42P01")return!0;if(typeof n==="number"&&n===1146)return!0;if((typeof t?.code==="string"?t.code.toUpperCase():"")==="42P01")return!0;let i=t?.message??"";return i.includes("no such table")||i.includes("doesn't exist")||i.includes("does not exist")}import{db as te}from"@stacksjs/database";function Ee(){if($t)return;$t=!0,console.warn("[queue/dlq] dead_letter_jobs table missing - DLQ disabled. Run migrations to enable poison-message isolation.")}async function pe(e,t,n=1){let r=new Date().toISOString().slice(0,19).replace("T"," ");try{return await te.insertInto("dead_letter_jobs").values({uuid:e.uuid??crypto.randomUUID(),connection:e.connection??"database",queue:e.queue??"default",payload:e.payload??"{}",exception:e.exception??"unknown",reason:t,total_failures:n,first_failed_at:e.failed_at??r,last_failed_at:r,dead_lettered_at:r}).execute(),!0}catch(i){if(v(i))return Ee(),!1;throw i}}async function Ln(e={}){try{let t=te.selectFrom("dead_letter_jobs").selectAll();if(e.queue)t=t.where("queue","=",e.queue);if(e.reason)t=t.where("reason","=",e.reason);if(e.sinceCutoffMs){let r=new Date(e.sinceCutoffMs).toISOString().slice(0,19).replace("T"," ");t=t.where("dead_lettered_at",">=",r)}if(e.limit&&e.limit>0)t=t.limit(e.limit);return await t.execute()??[]}catch(t){if(v(t))return Ee(),[];throw t}}async function Un(e){try{let t=await te.selectFrom("dead_letter_jobs").where("id","=",e).selectAll().executeTakeFirst();if(!t)return!1;let n=Math.floor(Date.now()/1000);return await te.insertInto("jobs").values({queue:t.queue,payload:t.payload,attempts:0,reserved_at:null,available_at:n,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),await te.deleteFrom("dead_letter_jobs").where("id","=",e).execute(),!0}catch(t){if(v(t))return Ee(),!1;throw t}}async function Wn(e=30){try{let t=new Date(Date.now()-e*24*60*60*1000).toISOString().slice(0,19).replace("T"," "),n=await te.deleteFrom("dead_letter_jobs").where("dead_lettered_at","<",t).execute();return Number(n?.numDeletedRows??n?.[0]?.numDeletedRows??n?.affectedRows??0)}catch(t){if(v(t))return Ee(),0;throw t}}var $t=!1;var Te=()=>{};function Re(){return Nt??=import("@stacksjs/database").catch((e)=>{throw Nt=void 0,e})}function He(){if(At)return;At=!0,console.warn("[queue/idempotency] job_idempotency table missing - idempotency keys are accepted but NOT enforced. Run migrations to enable dedup.")}async function zn(e){try{let{db:t}=await Re(),n=await t.selectFrom("job_idempotency").where("idempotency_key","=",e).select(["idempotency_key"]).executeTakeFirst();return Boolean(n)}catch(t){if(v(t))return He(),!1;throw t}}async function Vn(e,t,n){try{let{db:r}=await Re();await r.insertInto("job_idempotency").values({idempotency_key:e,job_name:t,queue:n??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}catch(r){if(v(r)){He();return}let i=r?.message??"";if(i.includes("UNIQUE constraint")||i.includes("Duplicate entry"))return;throw r}}async function Le(e,t,n){try{let{db:r}=await Re();return await r.insertInto("job_idempotency").values({idempotency_key:e,job_name:t,queue:n??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),"claimed"}catch(r){if(v(r))return He(),"unenforced";let i=r?.message??"";if(i.includes("UNIQUE constraint")||i.includes("Duplicate entry"))return"duplicate";throw r}}async function Ue(e){try{let{db:t}=await Re();await t.deleteFrom("job_idempotency").where("idempotency_key","=",e).execute()}catch{}}var Nt,At=!1;var We=()=>{};import{hash as Kn}from"crypto";import{db as D}from"@stacksjs/database";function fe(){if(Ot)return;Ot=!0,console.warn("[queue/poison] job_quarantine table missing - poison detection disabled. Run migrations to enable.")}function Ce(e){let t;try{t=typeof e==="string"?e:JSON.stringify(e??null)}catch{t=String(e)}return Kn("sha256",t,"hex").slice(0,32)}async function ze(e,t,n={}){let r=n.maxFailures??5,i=n.windowMinutes??60,o=Ce(t),a=new Date,l=a.toISOString().slice(0,19).replace("T"," ");try{let s=await D.selectFrom("job_quarantine").where("job_name","=",e).where("payload_hash","=",o).selectAll().executeTakeFirst();if(!s)return await D.insertInto("job_quarantine").values({job_name:e,payload_hash:o,failure_count:1,window_start:l,quarantined_at:null}).execute(),!1;if(s.quarantined_at)return!0;let u=Date.parse(s.window_start.replace(" ","T")+"Z"),c=a.getTime()-u,d=i*60*1000;if(Number.isFinite(u)&&c>d)return await D.updateTable("job_quarantine").set({failure_count:1,window_start:l}).where("id","=",s.id).execute(),!1;let m=s.failure_count+1;if(m>=r)return await D.updateTable("job_quarantine").set({failure_count:m,quarantined_at:l}).where("id","=",s.id).execute(),!0;return await D.updateTable("job_quarantine").set({failure_count:m}).where("id","=",s.id).execute(),!1}catch(s){if(v(s))return fe(),!1;throw s}}async function Ve(e,t){let n=Ce(t);try{let r=await D.selectFrom("job_quarantine").where("job_name","=",e).where("payload_hash","in",[n,"*"]).whereNotNull("quarantined_at").select(["id"]).executeTakeFirst();return Boolean(r)}catch(r){if(v(r))return fe(),!1;throw r}}async function Gn(e,t){let n=t===void 0?"*":Ce(t),r=new Date().toISOString().slice(0,19).replace("T"," ");try{let i=await D.selectFrom("job_quarantine").where("job_name","=",e).where("payload_hash","=",n).select(["id"]).executeTakeFirst();if(i)await D.updateTable("job_quarantine").set({quarantined_at:r}).where("id","=",i.id).execute();else await D.insertInto("job_quarantine").values({job_name:e,payload_hash:n,failure_count:0,window_start:r,quarantined_at:r}).execute()}catch(i){if(v(i)){fe();return}throw i}}async function Yn(e){try{await D.deleteFrom("job_quarantine").where("job_name","=",e).execute()}catch(t){if(v(t)){fe();return}throw t}}async function Xn(){try{return await D.selectFrom("job_quarantine").whereNotNull("quarantined_at").selectAll().execute()??[]}catch(e){if(v(e))return fe(),[];throw e}}var Ot=!1;var qe=()=>{};function U(e){let t=e?.numUpdatedRows;if(t===null||t===void 0)return 0;if(typeof t==="object")return Number(t.changes??0);return Number(t)}function Zn(e,t){let n=A(e,t.payload||{},{queue:t.queue,tries:t.maxTries,timeout:t.timeout,backoff:Array.isArray(t.backoff)?t.backoff:void 0});return{queue:t.queue||"default",payload:I(n),attempts:0,available_at:er(t.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}}async function Ke(e,t){let n=Zn(e,t),{db:r}=await import("@stacksjs/database");await r.insertInto("jobs").values(n).execute()}function er(e){let t=Date.now();return Math.floor(t/1000+e)}var De=q(()=>{ee()});import{log as B}from"@stacksjs/logging";class Xe{handlers=new Map;wildcardHandlers=new Set;listenerSubscriptions=new WeakMap;reclaim=new FinalizationRegistry((e)=>{e()});on(e,t){if(!this.handlers.has(e))this.handlers.set(e,new Set);return this.handlers.get(e).add(t),()=>{this.handlers.get(e)?.delete(t)}}subscribeListener(e,t,n){let r=new WeakRef(e),i=(s)=>{let u=r.deref();if(u===void 0){o();return}return n.call(u,s)},o=this.on(t,i),a=()=>{o();let s=r.deref();if(s===void 0)return;let u=this.listenerSubscriptions.get(s);if(!u)return;if(u.delete(a),u.size===0)this.listenerSubscriptions.delete(s),this.reclaim.unregister(s)},l=this.listenerSubscriptions.get(e);if(!l)l=new Set,this.listenerSubscriptions.set(e,l);return l.add(a),this.reclaim.register(e,o,e),a}unsubscribeListener(e){let t=this.listenerSubscriptions.get(e);if(!t)return 0;let n=Array.from(t);for(let r of n)r();return this.listenerSubscriptions.delete(e),this.reclaim.unregister(e),n.length}listenerCount(e){if(e==="*")return this.wildcardHandlers.size;return this.handlers.get(e)?.size??0}onAny(e){return this.wildcardHandlers.add(e),()=>{this.wildcardHandlers.delete(e)}}once(e,t){let n=async(r)=>{this.handlers.get(e)?.delete(n),await t(r)};return this.on(e,n)}async emit(e,t){let n={...t,timestamp:Date.now()};this.logEvent(e,n);let r=this.handlers.get(e);if(r)for(let i of r)try{await i(n)}catch(o){B.error(`Error in queue event handler for ${e}:`,o)}for(let i of this.wildcardHandlers)try{await i(e,n)}catch(o){B.error("Error in wildcard queue event handler:",o)}}logEvent(e,t){let n=t.jobId?`[${t.jobId}]`:"",r=t.queueName?`on ${t.queueName}`:"";switch(e){case"job:added":B.debug(`Job added ${n} ${r}`);break;case"job:processing":B.debug(`Job processing ${n} ${r}`);break;case"job:completed":B.info(`Job completed ${n} ${r} in ${t.duration}ms`);break;case"job:failed":B.error(`Job failed ${n} ${r}:`,t.error);break;case"job:retrying":B.warn(`Job retrying ${n} ${r} (attempt ${t.attemptsMade})`);break;case"job:stalled":B.warn(`Job stalled ${n} ${r}`);break;case"queue:error":B.error(`Queue error ${r}:`,t.error);break}}off(e){this.handlers.delete(e)}removeAllListeners(){this.handlers.clear(),this.wildcardHandlers.clear(),this.listenerSubscriptions=new WeakMap}}function me(){if(!Ge)Ge=new Xe;return Ge}function tr(e,t){let n=me();if(e==="*")return n.onAny(t);return n.on(e,t)}function _(e,t){return me().emit(e,t)}function nr(e,t){return async(...n)=>{let r=n[0]?.id||"unknown",i=Date.now();await _("job:processing",{jobId:r,queueName:e,data:n[0]?.data});try{let o=await t(...n);return await _("job:completed",{jobId:r,queueName:e,result:o,duration:Date.now()-i}),o}catch(o){throw await _("job:failed",{jobId:r,queueName:e,error:o,duration:Date.now()-i}),o}}}function rr(e){return function(t,n){if(typeof n!=="object"||n===null)throw TypeError(`@OnQueueEvent('${e}') 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(n.kind!=="method")throw TypeError(`@OnQueueEvent('${e}') can only decorate a class method, but it was applied to a ${n.kind}${n.name===void 0?"":` ('${String(n.name)}')`}. Move the handler into a method, or call onQueueEvent('${e}', handler) directly.`);if(n.static)throw TypeError(`@OnQueueEvent('${e}') cannot decorate the static method '${String(n.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('${e}', MyClass.${String(n.name)}).`);return n.addInitializer(function(){me().subscribeListener(this,e,t)}),t}}class Ze{jobCounts={added:0,completed:0,failed:0,processing:0};completions=[];errors=[];unsubscribe=[];constructor(){this.setupListeners()}setupListeners(){let e=me();this.unsubscribe.push(e.on("job:added",()=>{this.jobCounts.added++}),e.on("job:processing",()=>{this.jobCounts.processing++}),e.on("job:completed",(t)=>{this.jobCounts.completed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1);let n=t.duration||0;if(this.completions.push({timestamp:Date.now(),duration:n}),this.completions.length>1000)this.completions.shift()}),e.on("job:failed",(t)=>{if(this.jobCounts.failed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1),t.error){if(this.errors.push({error:t.error,timestamp:Date.now()}),this.errors.length>100)this.errors.shift()}}))}getThroughputPerMinute(){let e=Date.now()-60000;return this.completions.filter((n)=>n.timestamp>=e).length}getAverageProcessingTime(){let e=Date.now()-60000,t=this.completions.filter((n)=>n.timestamp>=e);if(t.length===0)return 0;return t.reduce((n,r)=>n+r.duration,0)/t.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((e)=>e()),this.unsubscribe=[]}}function be(){if(!Ye)Ye=new Ze;return Ye}class Ft{workers=new Map;register(e,t){this.workers.set(e,{id:e,status:"idle",queue:t,processedCount:0,failedCount:0,lastActivityAt:new Date().toISOString(),startedAt:new Date().toISOString()})}markActive(e){let t=this.workers.get(e);if(t)t.status="active",t.lastActivityAt=new Date().toISOString()}markIdle(e){let t=this.workers.get(e);if(t)t.status="idle",t.lastActivityAt=new Date().toISOString()}recordCompletion(e){let t=this.workers.get(e);if(t)t.processedCount++,t.lastActivityAt=new Date().toISOString()}recordFailure(e){let t=this.workers.get(e);if(t)t.failedCount++,t.lastActivityAt=new Date().toISOString()}unregister(e){let t=this.workers.get(e);if(t)t.status="stopped"}getAll(){return Array.from(this.workers.values())}clear(){this.workers.clear()}}function E(){return ir}var Ge=null,Ye=null,ir;var T=q(()=>{ir=new Ft});var{RedisClient:or}=globalThis.Bun;import{log as k}from"@stacksjs/logging";import{env as ar}from"@stacksjs/env";function N(){return ar.QUEUE_DRIVER||"sync"}class ge{jobs;options={thenCallbacks:[],catchCallbacks:[],finallyCallbacks:[],progressCallbacks:[]};constructor(e){this.jobs=e.map((t)=>("job"in t)?t:{job:t})}name(e){return this.options.name=e,this}onQueue(e){return this.options.queue=e,this}allowFailures(){return this.options.allowFailures=!0,this}then(e){return this.options.thenCallbacks.push(e),this}catch(e){return this.options.catchCallbacks.push(e),this}finally(e){return this.options.finallyCallbacks.push(e),this}progress(e){return this.options.progressCallbacks.push(e),this}thenHandler(e){return this.options.thenHandler=e,this}catchHandler(e){return this.options.catchHandler=e,this}finallyHandler(e){return this.options.finallyHandler=e,this}async dispatch(){let e=crypto.randomUUID(),t=this.jobs.length;if(t===0)throw Error("Cannot dispatch an empty batch");let n=N();await cr({id:e,name:this.options.name||"",total_jobs:t,pending_jobs:t,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}),sr(e,this.options);try{await Promise.resolve().then(() => T());await _("batch:added",{jobId:e,data:{name:this.options.name,totalJobs:t}})}catch{}for(let r=0;r<this.jobs.length;r++){let i=this.jobs[r];if(!i)continue;let{job:o,payload:a}=i,l={...a,_batchId:e,_batchIndex:r};if(this.options.queue&&!o.queue)o.queue=this.options.queue;if(n==="sync")try{await o.dispatchNow(l),await ie(e)}catch(s){await oe(e,`${e}:${r}`,s)}else await o.dispatch(l)}return k.info(`[Batch] Dispatched batch "${this.options.name||e}" with ${t} jobs`),new W(e)}getJobs(){return[...this.jobs]}getOptions(){return this.options}}class W{id;constructor(e){this.id=e}async fresh(){return H(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 e=await this.fresh();if(!e)return 0;return e.total_jobs-e.pending_jobs}async progress(){let e=await this.fresh();if(!e||e.total_jobs===0)return 0;let t=e.total_jobs-e.pending_jobs;return Math.round(t/e.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 e=await this.fresh();if(!e)return[];try{return JSON.parse(e.failed_job_ids||"[]")}catch{return[]}}async cancel(){if(N()==="redis")await _r(this.id);else await br(this.id);k.info(`[Batch] Cancelled batch ${this.id}`);let t=Ne(this.id);if(t)for(let n of t.finallyCallbacks)try{await n(this)}catch(r){k.error(`[Batch] Error in finally callback for batch ${this.id}:`,r)}}async add(e){let t=await this.fresh();if(!t)throw Error(`Batch ${this.id} not found`);if(t.cancelled_at)throw Error(`Batch ${this.id} has been cancelled`);if(t.finished_at)throw Error(`Batch ${this.id} has already finished`);let n=e.map((o)=>("job"in o)?o:{job:o});if(!await dr(this.id,n.length))throw Error(`Batch ${this.id} was cancelled, finished or deleted before the jobs could be added`);let i=JSON.parse(t.options||"{}");for(let o=0;o<n.length;o++){let a=n[o];if(!a)continue;let{job:l,payload:s}=a,u={...s,_batchId:this.id,_batchIndex:t.total_jobs+o};if(i.queue&&!l.queue)l.queue=i.queue;await l.dispatch(u)}k.info(`[Batch] Added ${n.length} jobs to batch ${this.id}`)}async delete(){await pr(this.id),$e(this.id)}}class It{static create(e){return new ge(e)}static async find(e){if(!await H(e))return null;return new W(e)}static async all(){return(await ur()).map((t)=>new W(t.id))}static async prune(e=24){return fr(e)}}function sr(e,t){et.set(e,t)}function Ne(e){return et.get(e)}function $e(e){et.delete(e)}async function cr(e){if(N()==="redis")await yr(e);else await Qt(e)}async function H(e){if(N()==="redis")return vr(e);return Bt(e)}async function ur(){if(N()==="redis")return Wt();return Mt()}async function lr(e,t){if(N()==="redis")await rt(e,t);else await tt(e,t)}async function dr(e,t){if(t===0)return!0;if(N()==="redis")try{let o=await V(),a=`${z}${e}`;return await o.hincrby(a,"total_jobs",t),await o.hincrby(a,"pending_jobs",t),o.close(),!0}catch{}let{db:n,sql:r}=await import("@stacksjs/database"),i=await n.updateTable("job_batches").set(hr(r,t)).where("id","=",e).whereNull("cancelled_at").whereNull("finished_at").executeTakeFirst();return U(i)>0}function hr(e,t){return{total_jobs:e`total_jobs + ${t}`,pending_jobs:e`pending_jobs + ${t}`}}async function pr(e){if(N()==="redis")await Vt(e);else await Ht(e)}async function fr(e){if(N()==="redis")return kr(e);return Lt(e)}function mr(e){return!!(e.then_handler||e.catch_handler||e.finally_handler)}async function Qt(e){let{db:t}=await import("@stacksjs/database"),n={id:e.id,name:e.name,total_jobs:e.total_jobs,pending_jobs:e.pending_jobs,failed_jobs:e.failed_jobs,failed_job_ids:e.failed_job_ids,options:e.options,cancelled_at:e.cancelled_at,created_at:e.created_at,finished_at:e.finished_at};if(!mr(e)){await t.insertInto("job_batches").values(n).execute();return}try{await t.insertInto("job_batches").values({...n,then_handler:e.then_handler??null,catch_handler:e.catch_handler??null,finally_handler:e.finally_handler??null}).execute()}catch(r){k.warn(`[Batch] Could not persist terminal handlers for batch ${e.id}: ${r?.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 t.insertInto("job_batches").values(n).execute()}}async function Bt(e){let{db:t}=await import("@stacksjs/database");return await t.selectFrom("job_batches").where("id","=",e).selectAll().executeTakeFirst()||null}async function Mt(){let{db:e}=await import("@stacksjs/database");return await e.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function tt(e,t){let{db:n}=await import("@stacksjs/database");await n.updateTable("job_batches").set(t).where("id","=",e).execute()}async function Ht(e){let{db:t}=await import("@stacksjs/database");await t.deleteFrom("job_batches").where("id","=",e).execute()}async function br(e){await tt(e,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function Lt(e){let{db:t}=await import("@stacksjs/database"),n=new Date(Date.now()-e*60*60*1000).toISOString().slice(0,19).replace("T"," "),r=await t.deleteFrom("job_batches").whereNotNull("finished_at").where("finished_at","<",n).executeTakeFirst();return Number(r?.numDeletedRows??0)}async function gr(){let{queue:e}=await import("@stacksjs/config"),t=e?.connections?.redis?.redis;if(t?.url)return t.url;let n=t?.password?`:${encodeURIComponent(t.password)}@`:"",r=t?.db?`/${t.db}`:"";return`redis://${n}${t?.host||"localhost"}:${t?.port||6379}${r}`}async function V(){let e=new or(await gr());return await e.connect(),e}function wr(e){return{id:e.id,name:e.name,total_jobs:String(e.total_jobs),pending_jobs:String(e.pending_jobs),failed_jobs:String(e.failed_jobs),failed_job_ids:e.failed_job_ids,options:e.options,cancelled_at:e.cancelled_at||"",created_at:e.created_at,finished_at:e.finished_at||"",then_handler:e.then_handler||"",catch_handler:e.catch_handler||"",finally_handler:e.finally_handler||""}}async function yr(e){try{let t=await V();await t.hset(`${z}${e.id}`,wr(e)),await t.sadd(nt,e.id),t.close()}catch{await Qt(e)}}function Ut(e){if(!e?.id)return null;return{id:e.id,name:e.name??"",total_jobs:Number(e.total_jobs),pending_jobs:Number(e.pending_jobs),failed_jobs:Number(e.failed_jobs),failed_job_ids:e.failed_job_ids??"",options:e.options??"",cancelled_at:e.cancelled_at||null,created_at:e.created_at??"",finished_at:e.finished_at||null,then_handler:e.then_handler||null,catch_handler:e.catch_handler||null,finally_handler:e.finally_handler||null}}async function vr(e){try{let t=await V(),n=`${z}${e}`,r=await t.hgetall(n);return t.close(),Ut(r)}catch{return Bt(e)}}async function Wt(){try{let e=await V(),t=await e.smembers(nt),n=[];for(let r of t){let i=Ut(await e.hgetall(`${z}${r}`));if(i)n.push(i)}return e.close(),n}catch{return Mt()}}async function rt(e,t){try{let n=await V(),r=`${z}${e}`,i={};for(let[o,a]of Object.entries(t))i[o]=a===null?"":String(a);await n.hset(r,i),n.close()}catch{await tt(e,t)}}async function zt(e,t,n){let r=await V();try{let i=`${z}${e}`,o=await r.hincrby(i,"pending_jobs",-1),a=Math.max(o,0);if(o<0)await r.hset(i,{pending_jobs:"0"});if(t)await r.hincrby(i,"failed_jobs",1);let s=!(await r.hgetall(i)).finished_at&&(!t||n?a===0:!0),u=new Date().toISOString().slice(0,19).replace("T"," "),c=s&&await r.hsetnx(i,"terminal_claimed","1");if(c)await r.hset(i,{finished_at:u});if(c&&t&&!n)await r.hset(i,{cancelled_at:u});return c}finally{r.close()}}async function Vt(e){try{let t=await V();await t.del(`${z}${e}`),await t.srem(nt,e),t.close()}catch{await Ht(e)}}async function _r(e){let t=new Date().toISOString().slice(0,19).replace("T"," ");await rt(e,{cancelled_at:t,finished_at:t})}async function kr(e){try{let t=await Wt(),n=Date.now()-e*60*60*1000,r=0;for(let i of t)if(i.finished_at){if(new Date(i.finished_at).getTime()<n)await Vt(i.id),r++}return r}catch{return Lt(e)}}function ne(e){if(!e)return null;try{let t=JSON.parse(e);if(t&&(t.kind==="job"||t.kind==="module"))return t;return k.warn(`[Batch] handler JSON has unknown kind '${t?.kind}' - skipping`),null}catch(t){return k.warn(`[Batch] failed to parse persistent handler: ${t.message}`),null}}async function re(e,t){try{if(e.kind==="job"){await Promise.resolve().then(() => ye());await it.dispatch(e.name,{...e.payload??{},_batchId:t});return}let n=await import(e.module).catch((i)=>(k.warn(`[Batch] persistent handler module not found: ${e.module} (${i.message})`),null));if(!n)return;let r=n[e.export];if(typeof r!=="function"){k.warn(`[Batch] persistent handler export '${e.export}' is not a function on ${e.module}`);return}await r(e.payload,t)}catch(n){k.error(`[Batch] persistent handler threw for batch ${t}:`,n)}}async function ie(e){let t=N(),n=!1;if(t==="redis")n=await zt(e,!1,!1);else{let{db:o,sql:a}=await import("@stacksjs/database");await o.updateTable("job_batches").set({pending_jobs:a`GREATEST(pending_jobs - 1, 0)`}).where("id","=",e).where("pending_jobs",">",0).execute();let l=new Date().toISOString().slice(0,19).replace("T"," "),s=await o.updateTable("job_batches").set({finished_at:l}).where("id","=",e).where("pending_jobs","=",0).whereNull("finished_at").executeTakeFirst();n=U(s)>0}let r=Ne(e),i=new W(e);if(r)for(let o of r.progressCallbacks)try{await o(i)}catch(a){k.error(`[Batch] Error in progress callback for batch ${e}:`,a)}if(n){try{await Promise.resolve().then(() => T());await _("batch:completed",{jobId:e})}catch{}let o=await H(e);if(!o){$e(e),k.info(`[Batch] Batch ${e} finished (record vanished)`);return}let a=JSON.parse(o.options||"{}"),s=!((o.failed_jobs||0)>0)||a.allowFailures;if(r){if(s)for(let c of r.thenCallbacks)try{await c(i)}catch(d){k.error(`[Batch] Error in then callback for batch ${e}:`,d)}for(let c of r.finallyCallbacks)try{await c(i)}catch(d){k.error(`[Batch] Error in finally callback for batch ${e}:`,d)}$e(e)}if(s){let c=ne(o.then_handler);if(c)await re(c,e)}else{let c=ne(o.catch_handler);if(c)await re(c,e)}let u=ne(o.finally_handler);if(u)await re(u,e);k.info(`[Batch] Batch ${e} finished`)}}async function oe(e,t,n){let r=await H(e);if(!r)return;let i=JSON.parse(r.options||"{}"),o=new W(e),a=Ne(e),l=!1;if(N()==="redis"){l=await zt(e,!0,!!i.allowFailures);let c=await H(e);if(c){let d=[];try{d=JSON.parse(c.failed_job_ids||"[]")}catch{d=[]}d.push(t),await rt(e,{failed_job_ids:JSON.stringify(d)})}if(a)for(let d of a.catchCallbacks)try{await d(o,n)}catch(m){k.error(`[Batch] Error in catch callback for batch ${e}:`,m)}try{await Promise.resolve().then(() => T());await _("batch:failed",{jobId:e,error:n})}catch{}}else{let{db:c,sql:d}=await import("@stacksjs/database");await c.updateTable("job_batches").set({pending_jobs:d`GREATEST(pending_jobs - 1, 0)`,failed_jobs:d`failed_jobs + 1`}).where("id","=",e).where("pending_jobs",">",0).execute();try{let w=await H(e);if(w){let j=[];try{j=JSON.parse(w.failed_job_ids||"[]")}catch{j=[]}j.push(t),await lr(e,{failed_job_ids:JSON.stringify(j)})}}catch{}if(a)for(let w of a.catchCallbacks)try{await w(o,n)}catch(j){k.error(`[Batch] Error in catch callback for batch ${e}:`,j)}try{await Promise.resolve().then(() => T());await _("batch:failed",{jobId:e,error:n})}catch{}let m=new Date().toISOString().slice(0,19).replace("T"," "),h=c.updateTable("job_batches").set(i.allowFailures?{finished_at:m}:{finished_at:m,cancelled_at:m}).where("id","=",e).whereNull("finished_at");if(i.allowFailures)h=h.where("pending_jobs","=",0);let b=await h.executeTakeFirst();l=U(b)>0}if(!l)return;let s=!!i.allowFailures;if(a){if(s)for(let c of a.thenCallbacks)try{await c(o)}catch(d){k.error(`[Batch] Error in then callback for batch ${e}:`,d)}for(let c of a.finallyCallbacks)try{await c(o)}catch(d){k.error(`[Batch] Error in finally callback for batch ${e}:`,d)}$e(e)}let u=await H(e);if(u){if(s){let d=ne(u.then_handler);if(d)await re(d,e)}else{let d=ne(u.catch_handler);if(d)await re(d,e)}let c=ne(u.finally_handler);if(c)await re(c,e)}k.info(`[Batch] Batch ${e} finished with failure(s)`)}async function we(e){return(await H(e))?.cancelled_at!==null}var et,z="stacks:batch:",nt="stacks:batches";var M=q(()=>{De();et=new Map});import{appPath as xr,frameworkPath as Jr}from"@stacksjs/path";import{env as jr}from"@stacksjs/env";import{enqueueAfterCommit as Pr,isInTransaction as Sr}from"@stacksjs/database";function en(){return Yt??=import("@stacksjs/router").catch((e)=>{throw Yt=void 0,e})}function Er(){return jr.QUEUE_DRIVER||"sync"}function Tr(){if(Xt)return;Xt=!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 O{name;payload;options={};txMode="auto";constructor(e,t){this.name=e;this.payload=t}onQueue(e){return this.options.queue=e,this}delay(e){return this.options.delay=e,this}tries(e){return this.options.tries=e,this}timeout(e){return this.options.timeout=e,this}backoff(e){return this.options.backoff=e,this}withContext(e){return this.options.context=e,this}withIdempotencyKey(e){return this.options.idempotencyKey=e,this}afterCommit(){return this.txMode="after",this}withoutCommit(){return this.txMode="immediate",this}async dispatch(){let{isFaked:e,getFakeQueue:t}=await(Kt??=Promise.resolve().then(() => Be).catch((n)=>{throw Kt=void 0,n}));if(e()){t()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(Sr()){let r=this.runDispatchPipeline.bind(this);if(Pr(async()=>{await r()}))return}else if(this.txMode==="after")Tr()}await this.runDispatchPipeline()}async runDispatchPipeline(){let e=!1;if(this.options.idempotencyKey){let t=await Le(this.options.idempotencyKey,this.name,this.options.queue);if(t==="duplicate")return;e=t==="claimed"}try{if(await Ve(this.name,this.payload)){let n=A(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await pe({queue:this.options.queue||"default",payload:I(n),exception:`quarantined: ${this.name}`},"poison-detected"))return}let t=Er();if(t==="database")await this.dispatchToDatabase();else if(t==="redis")await this.dispatchToRedis();else if(t==="sync")await K(this.name,{payload:this.payload,context:this.options.context,traceId:await Zt()});else if(t==="sqs"||t==="memory"||t==="beanstalkd")throw Error(`[queue] Driver "${t}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);else throw Error(`[queue] Unknown QUEUE_DRIVER "${t}". Allowed values: redis, database, sync.`)}catch(t){if(e&&this.options.idempotencyKey)await Ue(this.options.idempotencyKey);throw t}}async dispatchIf(e){if(e)await this.dispatch()}async dispatchUnless(e){if(!e)await this.dispatch()}async dispatchToDatabase(){let e=Math.floor(Date.now()/1000),t=this.options.delay?e+this.options.delay:e,n=A(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff},await Zt()),r=I(n),{db:i}=await(Gt??=import("@stacksjs/database").catch((o)=>{throw Gt=void 0,o}));await i.insertInto("jobs").values({queue:this.options.queue||"default",payload:r,attempts:0,reserved_at:null,available_at:t,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){let e=A(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});le(e);await Promise.resolve().then(() => he());let{queue:n}=await import("@stacksjs/config"),r=n?.connections?.redis;if(!r)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new Q(this.options.queue||"default",r).add(e,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await K(this.name,{payload:this.payload,context:this.options.context})}}function Rr(e,t){return new O(e,t)}function Cr(e){M();return new ge(e)}async function qr(e){let t=[xr(`Jobs/${e}.ts`),Jr(`defaults/app/Jobs/${e}.ts`)];for(let r of t)if(await Bun.file(r).exists())return r;let n;try{let r=import.meta.resolve("@stacksjs/defaults/package.json");n=`${new URL(".",r).pathname}app/Jobs/${e}.ts`}catch{}if(n&&await Bun.file(n).exists())return n;return null}async function K(e,t={}){let{withTraceId:n}=await en(),r=t.traceId??`job:${e}:${Math.random().toString(36).slice(2,10)}`;await n(r,async()=>{let i=await qr(e);if(!i)throw Error(`Job ${e} not found. Looked in app/Jobs/${e}.ts and the framework defaults (storage/framework/defaults/app/Jobs, @stacksjs/defaults).`);let a=(await import(i)).default;if(!a)throw Error(`Job ${e} does not export a default`);if(typeof a.handle==="function")await a.handle(t.payload);else if(typeof a.action==="string")await je(a.action);else if(typeof a.action==="function")await a.action();else if(typeof a==="function")await a(t.payload,t.context);else throw Error(`Job ${e} does not have a valid handler`)})}async function Zt(){try{let{getTraceId:e}=await en();return e()}catch{return}}var Kt,Gt,Yt,Xt=!1,it;var ye=q(()=>{Te();ee();We();qe();Pe();it={make(e,t){return new O(e,t)},async dispatch(e,t){await new O(e,t).dispatch()},async dispatchIf(e,t,n){if(e)await new O(t,n).dispatch()},async dispatchUnless(e,t,n){if(!e)await new O(t,n).dispatch()},async dispatchNow(e,t){await new O(e,t).dispatchNow()},dispatchAfter(e,t,n){return new O(t,n).delay(e)},async dispatchOnce(e,t,n){await new O(t,n).withIdempotencyKey(e).dispatch()},async dispatchAfterCommit(e,t){await new O(e,t).afterCommit().dispatch()}}});import{db as R}from"@stacksjs/database";function G(){if(tn)return;tn=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing - circuit breaker disabled. Run migrations to enable.")}async function at(e,t){try{let n=await R.selectFrom("queue_circuit_state").where("queue_name","=",e).selectAll().executeTakeFirst();if(n)return n;return await R.insertInto("queue_circuit_state").values({queue_name:e,success_count:0,failure_count:0,window_start:t,paused_at:null,resume_at:null}).execute(),{queue_name:e,success_count:0,failure_count:0,window_start:t,paused_at:null,resume_at:null}}catch(n){if(v(n))return G(),null;let r=n?.message??"";if(r.includes("UNIQUE constraint")||r.includes("Duplicate entry"))return await R.selectFrom("queue_circuit_state").where("queue_name","=",e).selectAll().executeTakeFirst();throw n}}async function st(e){try{let t=await R.selectFrom("queue_circuit_state").where("queue_name","=",e).select(["paused_at","resume_at"]).executeTakeFirst();if(!t||!t.paused_at)return!1;if(t.resume_at){let n=Date.parse(t.resume_at.replace(" ","T")+"Z");if(Number.isFinite(n)&&Date.now()>=n)return await R.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",e).execute(),!1}return!0}catch(t){if(v(t))return G(),!1;throw t}}async function ct(e,t={}){let n=t.windowSeconds??300,r=new Date().toISOString().slice(0,19).replace("T"," "),i=await at(e,r);if(!i)return;try{if(ot(i.window_start,n)){await R.updateTable("queue_circuit_state").set({success_count:1,failure_count:0,window_start:r}).where("queue_name","=",e).execute();return}await R.updateTable("queue_circuit_state").set({success_count:i.success_count+1}).where("queue_name","=",e).execute()}catch(o){if(v(o)){G();return}throw o}}async function ut(e,t={}){let n=t.failureRateThreshold??0.5,r=t.windowSeconds??300,i=t.pauseSeconds??300,o=t.minObservations??10,a=new Date,l=a.toISOString().slice(0,19).replace("T"," "),s=await at(e,l);if(!s)return!1;if(s.paused_at)return!1;try{let{success_count:u,failure_count:c}=s;if(ot(s.window_start,r))u=0,c=0;c+=1;let d=u+c,m=d===0?0:c/d;if(d>=o&&m>=n){let b=new Date(a.getTime()+i*1000).toISOString().slice(0,19).replace("T"," ");return await R.updateTable("queue_circuit_state").set({success_count:u,failure_count:c,window_start:l,paused_at:l,resume_at:b}).where("queue_name","=",e).execute(),!0}return await R.updateTable("queue_circuit_state").set({success_count:u,failure_count:c,window_start:ot(s.window_start,r)?l:s.window_start}).where("queue_name","=",e).execute(),!1}catch(u){if(v(u))return G(),!1;throw u}}function ot(e,t){if(!e)return!0;let n=Date.parse(e.replace(" ","T")+"Z");if(!Number.isFinite(n))return!0;return Date.now()-n>t*1000}async function Dr(e,t=300){let n=new Date,r=n.toISOString().slice(0,19).replace("T"," "),i=new Date(n.getTime()+t*1000).toISOString().slice(0,19).replace("T"," ");await at(e,r);try{await R.updateTable("queue_circuit_state").set({paused_at:r,resume_at:i}).where("queue_name","=",e).execute()}catch(o){if(v(o)){G();return}throw o}}async function $r(e){try{await R.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",e).execute()}catch(t){if(v(t)){G();return}throw t}}async function Nr(){try{return await R.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch(e){if(v(e))return G(),[];throw e}}var tn=!1;var ve=()=>{};ee();Pe();import{env as Hn}from"@stacksjs/env";var Rt,Ct;function qt(){return Hn.QUEUE_DRIVER||"sync"}class Dt{name;description;action;handle;queue;rate;tries;timeout;backoff;backoffConfig;enabled;constructor(e){this.name=e.name,this.description=e.description,this.handle=e.handle,this.queue=e.queue,this.rate=e.rate,this.action=e.action,this.tries=e.tries,this.timeout=e.timeout,this.backoff=e.backoff,this.backoffConfig=e.backoffConfig,this.enabled=e.enabled}async dispatch(...[e]){let{isFaked:t,getFakeQueue:n}=await(Rt??=Promise.resolve().then(() => Be).catch((i)=>{throw Rt=void 0,i}));if(t()){n()?.dispatch(this.name||"UnknownJob",e,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let r=qt();if(r==="sync")return this.dispatchNow(...[e]);if(r==="redis")return this.dispatchToRedis(e);if(r==="database")return this.dispatchToDatabase(e);if(r==="sqs"||r==="memory"||r==="beanstalkd")throw Error(`[queue] Driver "${r}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);throw Error(`[queue] Unknown QUEUE_DRIVER "${r}". Allowed values: redis, database, sync.`)}async dispatchIf(e,...[t]){if(e)return this.dispatch(...[t])}async dispatchUnless(e,...[t]){if(!e)return this.dispatch(...[t])}async dispatchAfter(e,...[t]){let n=qt();if(n==="redis")return this.dispatchToRedis(t,{delay:e});if(n==="database")return this.dispatchToDatabase(t,{delay:e});return await new Promise((r)=>setTimeout(r,e*1000)),await this.dispatchNow(...[t])}async dispatchNow(...[e]){if(typeof this.handle==="function")await this.handle(e);else if(typeof this.action==="string")await je(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(e,t){let n=Math.floor(Date.now()/1000),r=t?.delay?n+t.delay:n,i=A(this.name??this.constructor.name,e,{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}),o=I(i),{db:a}=await(Ct??=import("@stacksjs/database").catch((l)=>{throw Ct=void 0,l}));await a.insertInto("jobs").values({queue:this.queue||"default",payload:o,attempts:0,reserved_at:null,available_at:r,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(e,t){let n=A(this.name??this.constructor.name,e,{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});le(n);await Promise.resolve().then(() => he());let{queue:i}=await import("@stacksjs/config"),o=i?.connections?.redis;if(!o)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new Q(this.queue||"default",o).add(n,{delay:t?.delay,maxTries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0})}}Pe();ye();We();Te();qe();ve();ee();var lt=(e)=>`__job_progress__:${e}`,dt=(e)=>`__job_cancel__:${e}`;var _e;function Ar(e){return Number.isFinite(e)?Math.max(0,Math.min(100,e)):0}async function Or(e,t,n){let{cache:r}=_e??=await import("@stacksjs/cache");await r.set(lt(e),{percent:Ar(t),message:n,updatedAt:Date.now()},3600)}async function Fr(e){let{cache:t}=_e??=await import("@stacksjs/cache");return await t.get(lt(e))??null}async function Ir(e){let{cache:t}=_e??=await import("@stacksjs/cache");await t.set(dt(e),1,3600)}async function Qr(e){let{cache:t}=_e??=await import("@stacksjs/cache");return Boolean(await t.get(dt(e)))}async function Br(e){let{cache:t}=_e??=await import("@stacksjs/cache");await Promise.all([t.del(lt(e)),t.del(dt(e))])}import{log as ae}from"@stacksjs/logging";import*as nn from"@stacksjs/path";class rn{jobs=new Map;initialized=!1;register(e){this.jobs.set(e.name,e),ae.debug(`Registered job: ${e.name} (${e.type})`)}get(e){return this.jobs.get(e)}all(){return Array.from(this.jobs.values())}byQueue(e){return this.all().filter((t)=>t.config.queue===e)}scheduled(){return this.all().filter((e)=>e.config.rate||e.config.schedule)}has(e){return this.jobs.has(e)}clear(){this.jobs.clear(),this.initialized=!1}setInitialized(e){this.initialized=e}isInitialized(){return this.initialized}}var Y=new rn;async function ht(e){let t=e||nn.userJobsPath(),n=[];try{let r=new Bun.Glob("**/*.{ts,js}"),i={cwd:t,onlyFiles:!0,absolute:!0};for await(let o of r.scan(i)){if(o.includes(".test.")||o.includes(".spec.")||o.endsWith("index.ts")||o.endsWith("index.js"))continue;try{let a=await Mr(o);if(a)n.push(a),Y.register(a)}catch(a){ae.warn(`Failed to load job from ${o}: ${a.message}`)}}return Y.setInitialized(!0),ae.info(`Discovered ${n.length} jobs from ${t}`),n}catch(r){return ae.error(`Failed to discover jobs: ${r.message}`),[]}}async function Mr(e){try{let t=await import(e),n=e.split("/").pop()?.replace(/\.(ts|js)$/,"")||"UnknownJob";if(t.default&&typeof t.default==="function"){let r=t.default;if(typeof r.handle==="function"||typeof r.prototype?.handle==="function"){let i=r.config||{};return{name:i.name||n,path:e,config:{name:i.name||n,description:i.description,queue:i.queue||"default",tries:i.retries||3,timeout:i.timeout,withoutOverlapping:i.withoutOverlapping,schedule:i.schedule,retryAfter:i.retryAfter},type:"class",module:r}}}if(t.default&&typeof t.default==="object"){let r=t.default;if(typeof r.handle==="function"||typeof r.action==="string")return{name:r.name||n,path:e,config:{name:r.name||n,description:r.description,queue:r.queue||"default",tries:r.tries||3,backoff:r.backoff,rate:r.rate,timeout:r.timeout||r.timeOut,backoffConfig:r.backoffConfig},type:"function",module:r}}return null}catch(t){return ae.debug(`Could not load job from ${e}: ${t.message}`),null}}function Hr(e){return Y.get(e)}function Lr(){return Y.all()}function pt(){return Y.scheduled()}async function Ur(e,t){let n=Y.get(e);if(!n)throw Error(`Job "${e}" not found. Did you run discoverJobs()?`);try{if(n.type==="class"){if(typeof n.module.handle==="function")return await n.module.handle(t);return await new n.module().handle(t)}else{if(typeof n.module.handle==="function")return await n.module.handle(t);throw Error(`Job "${e}" does not have a handle method`)}}catch(r){throw ae.error(`Failed to execute job "${e}": ${r.message}`),r}}function Wr(e){let t=e.config;return{name:t.name,queue:t.queue,tries:t.tries,backoff:t.backoff,timeout:t.timeout,backoffConfig:t.backoffConfig,rate:t.rate}}import{log as x}from"@stacksjs/logging";T();import{log as an}from"@stacksjs/logging";var on=!1;async function sn(){if(on)return!0;try{let{db:e}=await import("@stacksjs/database");return await e.unsafe("CREATE TABLE IF NOT EXISTS scheduled_job_runs (job_name VARCHAR(255) PRIMARY KEY, last_run_at VARCHAR(64) NOT NULL)").execute(),on=!0,!0}catch(e){return an.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${e instanceof Error?e.message:String(e)}`),!1}}async function cn(e){if(!await sn())return null;try{let{db:t}=await import("@stacksjs/database"),n=await t.selectFrom("scheduled_job_runs").where("job_name","=",e).select(["last_run_at"]).executeTakeFirst();if(!n?.last_run_at)return null;let r=new Date(n.last_run_at);return Number.isNaN(r.getTime())?null:r}catch{return null}}async function un(e,t){if(!await sn())return;try{let{db:n}=await import("@stacksjs/database"),r=t.toISOString();await n.deleteFrom("scheduled_job_runs").where("job_name","=",e).execute(),await n.insertInto("scheduled_job_runs").values({job_name:e,last_run_at:r}).execute()}catch{}}function zr(e){return`%"jobName":"${e.replace(/[\\%_]/g,(n)=>`\\${n}`)}"%`}async function ln(e){try{let{db:t}=await import("@stacksjs/database"),r=await t.unsafe("SELECT 1 AS present FROM jobs WHERE payload LIKE ? ESCAPE '\\' LIMIT 1",[zr(e)]).execute(),i=Array.isArray(r)?r:r?.rows??[];return Array.isArray(i)&&i.length>0}catch(t){return an.debug(`[scheduler] overlap check unavailable, dispatching anyway: ${t instanceof Error?t.message:String(t)}`),!1}}De();var mn={checkInterval:60000,preventOverlapping:!0},g={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...mn}},dn=new Set;function Vr(e,t){if(dn.has(e))return;dn.add(e),x.warn(`[scheduler] Cron expression "${e}" specifies seconds="${t}" but the scheduler ticks at minute granularity - the seconds field is being ignored. Use a 5-field expression to avoid this warning, or wait for sub-minute scheduling support.`)}var hn=!1,pn=new Map;function Kr(e){let t=pn.get(e);if(!t)t=new Intl.DateTimeFormat("en-US",{timeZone:e,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),pn.set(e,t);return t}function ft(e,t){if(t&&t!=="local"&&t!=="system")try{let n=Kr(t).formatToParts(e),r=(a)=>n.find((l)=>l.type===a)?.value??"",i={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},o=Number(r("hour"));if(o===24)o=0;return{minute:Number(r("minute")),hour:o,day:Number(r("day")),month:Number(r("month")),dayOfWeek:i[r("weekday")]??e.getDay()}}catch{if(!hn)hn=!0,x.warn(`[scheduler] Invalid timezone "${t}"; falling back to system local time.`)}return{minute:e.getMinutes(),hour:e.getHours(),day:e.getDate(),month:e.getMonth()+1,dayOfWeek:e.getDay()}}function Gr(e,t,n){let r=ft(new Date,n),{minute:i,hour:o,day:a,month:l,dayOfWeek:s}=r;if(t){let w=ft(t,n);if(w.minute===i&&w.hour===o&&w.day===a)return!1}let u=e.trim().split(/\s+/);if(u.length===6){let w=u[0];if(w&&w!=="0"&&w!=="*")Vr(e,w);u=u.slice(1)}if(u.length<5)return x.warn(`Invalid cron expression: ${e}`),!1;let[c,d,m,h,b]=u;return F(c,i,0,59)&&F(d,o,0,23)&&F(m,a,1,31)&&F(h,l,1,12)&&F(b,s,0,6)}function F(e,t,n,r){if(e==="*")return!0;if(e.includes(","))return e.split(",").map((a)=>Number.parseInt(a.trim(),10)).includes(t);if(e.includes("-")){let[o,a]=e.split("-").map((l)=>Number.parseInt(l.trim(),10));return t>=o&&t<=a}if(e.includes("/")){let[o,a]=e.split("/"),l=Number.parseInt(a,10);if(o==="*")return t%l===0;if(o.includes("-")){let[s,u]=o.split("-").map((c)=>Number.parseInt(c.trim(),10));return t>=s&&t<=u&&(t-s)%l===0}}let i=Number.parseInt(e,10);return!Number.isNaN(i)&&t===i}function bn(e){let n={"@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 * * * *"}[e.toLowerCase()];if(n)return n;let r=e.match(/^Every\.(\w+)$/i);if(r&&r[1]!==void 0){let o=r[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 * *"}[o]||null}let i=e.split(/\s+/).length;if(i>=5&&i<=6)return e;return null}function gn(e,t){let n=e.trim().split(/\s+/),r=n.length===6?n.slice(1):n;if(r.length<5)return null;let[i,o,a,l,s]=r,u=new Date;u.setSeconds(0,0);let c=527040;for(let d=1;d<=c;d++){let m=new Date(u.getTime()+d*60000),h=ft(m,t);if(F(i,h.minute,0,59)&&F(o,h.hour,0,23)&&F(a,h.day,1,31)&&F(l,h.month,1,12)&&F(s,h.dayOfWeek,0,6))return m}return null}async function Yr(e={}){if(g.isRunning){x.warn("Scheduler is already running");return}g.config={...mn,...e},g.isRunning=!0,g.isShuttingDown=!1,await ht();let t=pt();for(let i of t){let o=i.config.rate||i.config.schedule;if(o){let a=bn(o);if(a){let l=await cn(i.name);g.jobs.set(i.name,{job:i,lastRun:l,nextRun:gn(a,g.config.timezone),isRunning:!1}),x.info(`Registered scheduled job: ${i.name} (${a})`)}else x.warn(`Invalid schedule for job ${i.name}: ${o}`)}}if(g.jobs.size===0){x.info("No scheduled jobs found");return}x.info(`Scheduler started with ${g.jobs.size} job(s)`),process.on("SIGINT",()=>mt()),process.on("SIGTERM",()=>mt());let n=!1,r=()=>{if(g.isShuttingDown)return;let i=g.config.checkInterval,o=i-Date.now()%i;g.checkInterval=setTimeout(()=>{if(!g.isShuttingDown&&!n)n=!0,fn().catch((a)=>x.error("Scheduler check failed:",a)).finally(()=>{n=!1});r()},o),g.checkInterval?.unref?.()};r(),await fn()}async function fn(){for(let[e,t]of g.jobs){let n=t.job.config.rate||t.job.config.schedule;if(!n)continue;let r=bn(n);if(!r)continue;if(Gr(r,t.lastRun,g.config.timezone)){if((g.config.preventOverlapping||t.job.config.withoutOverlapping)&&await ln(e)){x.debug(`Skipping ${e}: previous execution still running`);continue}if(t.isRunning){x.debug(`Skipping ${e}: a dispatch for it is already in flight`);continue}try{t.isRunning=!0,t.lastRun=new Date,t.nextRun=gn(r,g.config.timezone),await un(e,t.lastRun),x.info(`Dispatching scheduled job: ${e}`),await _("job:added",{jobId:`scheduled-${e}-${Date.now()}`,queueName:t.job.config.queue||"default",jobName:e}),await Ke(e,{queue:t.job.config.queue||"default",payload:{},maxTries:t.job.config.tries||3,timeout:t.job.config.timeout||60}),t.isRunning=!1,x.info(`Scheduled job ${e} dispatched to queue`)}catch(o){t.isRunning=!1,x.error(`Failed to dispatch scheduled job ${e}:`,o)}}}}async function mt(){if(!g.isRunning)return;if(x.info("Stopping scheduler..."),g.isShuttingDown=!0,g.checkInterval)clearTimeout(g.checkInterval),g.checkInterval=null;g.isRunning=!1,g.jobs.clear(),x.info("Scheduler stopped")}function Xr(){return{isRunning:g.isRunning,jobCount:g.jobs.size,jobs:Array.from(g.jobs.entries()).map(([e,t])=>({name:e,schedule:t.job.config.rate||t.job.config.schedule,lastRun:t.lastRun,nextRun:t.nextRun,isRunning:t.isRunning}))}}function Zr(){return g.isRunning}function ei(){return new Map(g.jobs)}async function ti(e){let t=g.jobs.get(e);if(!t)throw Error(`Scheduled job "${e}" not found`);x.info(`Manually triggering scheduled job: ${e}`),await Ke(e,{queue:t.job.config.queue||"default",payload:{},maxTries:t.job.config.tries||3,timeout:t.job.config.timeout||60})}T();T();import{log as ni}from"@stacksjs/logging";function wn(e,t=Math.floor(Date.now()/1000)){if(e.reserved_at)return"processing";let n=typeof e.available_at==="number"?e.available_at:typeof e.available_at==="string"&&e.available_at.trim()?Number(e.available_at):Number.NaN;if(Number.isFinite(n)&&n>t)return"delayed";return"pending"}var ri={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function bt(e={}){let t={...ri,...e},n=[],r=new Date,i=Math.floor(r.getTime()/1000);try{let{db:o}=await import("@stacksjs/database"),a=await o.selectFrom("jobs").selectAll().execute(),l=await o.selectFrom("failed_jobs").selectAll().execute(),s=new Map;for(let p of a){let y=p.queue||"default";if(!s.has(y))s.set(y,{pending:0,processing:0,delayed:0});let P=s.get(y),S=wn(p,i);if(S==="processing")P.processing++;else if(S==="delayed")P.delayed++;else if(P.pending++,p.created_at){let Rn=typeof p.created_at==="number"?p.created_at:Math.floor(new Date(p.created_at).getTime()/1000),kt=i-Rn;if(!P.oldestAge||kt>P.oldestAge)P.oldestAge=kt}}let u=new Map;for(let p of l){let y=p.queue||"default";u.set(y,(u.get(y)||0)+1)}let c=[],d=new Set([...s.keys(),...u.keys()]),m=t.queues?[...d].filter((p)=>t.queues.includes(p)):[...d],h=0,b=0,w=0,j=0;for(let p of m){let y=s.get(p)||{pending:0,processing:0,delayed:0},P=u.get(p)||0;h+=y.pending,b+=y.processing,w+=y.delayed,j+=P;let S="healthy";if(y.pending>=t.maxPendingCritical)S="unhealthy",n.push({level:"critical",message:`Queue "${p}" has ${y.pending} pending jobs (threshold: ${t.maxPendingCritical})`,queue:p,timestamp:r.toISOString()});else if(y.pending>=t.maxPendingWarning)S="degraded",n.push({level:"warning",message:`Queue "${p}" has ${y.pending} pending jobs (threshold: ${t.maxPendingWarning})`,queue:p,timestamp:r.toISOString()});if(P>=t.maxFailedCritical)S="unhealthy",n.push({level:"critical",message:`Queue "${p}" has ${P} failed jobs (threshold: ${t.maxFailedCritical})`,queue:p,timestamp:r.toISOString()});else if(P>=t.maxFailedWarning){if(S==="healthy")S="degraded";n.push({level:"warning",message:`Queue "${p}" has ${P} failed jobs (threshold: ${t.maxFailedWarning})`,queue:p,timestamp:r.toISOString()})}if(y.oldestAge){if(y.oldestAge>=t.maxJobAgeCritical)S="unhealthy",n.push({level:"critical",message:`Queue "${p}" has a job waiting for ${Math.floor(y.oldestAge/3600)} hours`,queue:p,timestamp:r.toISOString()});else if(y.oldestAge>=t.maxJobAgeWarning){if(S==="healthy")S="degraded";n.push({level:"warning",message:`Queue "${p}" has a job waiting for ${Math.floor(y.oldestAge/60)} minutes`,queue:p,timestamp:r.toISOString()})}}c.push({name:p,status:S,pending:y.pending,processing:y.processing,delayed:y.delayed,failed:P,oldestJobAge:y.oldestAge})}let vt=be().getMetrics(),{throughputPerMinute:Sn,averageDuration:En}=vt,_t=h+b+w+j,ue=_t>0?j/_t:0,Z="healthy";if(c.some((p)=>p.status==="unhealthy"))Z="unhealthy";else if(c.some((p)=>p.status==="degraded"))Z="degraded";if(ue>=t.maxErrorRateCritical)Z="unhealthy",n.push({level:"critical",message:`Overall error rate is ${(ue*100).toFixed(1)}% (threshold: ${t.maxErrorRateCritical*100}%)`,timestamp:r.toISOString()});else if(ue>=t.maxErrorRateWarning){if(Z==="healthy")Z="degraded";n.push({level:"warning",message:`Overall error rate is ${(ue*100).toFixed(1)}% (threshold: ${t.maxErrorRateWarning*100}%)`,timestamp:r.toISOString()})}let Tn=E().getAll().map((p)=>({id:p.id,status:p.status,queue:p.queue,processedCount:p.processedCount,failedCount:p.failedCount,lastActivityAt:p.lastActivityAt}));return{status:Z,timestamp:r.toISOString(),queues:c,workers:Tn,metrics:{totalPending:h,totalProcessing:b,totalDelayed:w,totalFailed:j,throughputPerMinute:Sn,averageProcessingTime:En,errorRate:ue},alerts:n}}catch(o){ni.error("Failed to perform queue health check:",o);let l=E().getAll().map((s)=>({id:s.id,status:s.status,queue:s.queue,processedCount:s.processedCount,failedCount:s.failedCount,lastActivityAt:s.lastActivityAt}));return{status:"unhealthy",timestamp:r.toISOString(),queues:[],workers:l,metrics:{totalPending:0,totalProcessing:0,totalDelayed:0,totalFailed:0,throughputPerMinute:0,averageProcessingTime:0,errorRate:0},alerts:[{level:"critical",message:`Health check failed: ${o.message}`,timestamp:r.toISOString()}]}}}function ii(e={}){return async(t)=>{let n=await bt(e),r=n.status==="healthy"?200:n.status==="degraded"?207:503;return new Response(JSON.stringify(n,null,2),{status:r,headers:{"Content-Type":"application/json","Cache-Control":"no-store"}})}}async function oi(e={}){return(await bt(e)).status==="healthy"}import{log as C}from"@stacksjs/logging";var ai={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function ke(e){return String(e).replace(/[&<>"']/g,(t)=>ai[t]??t)}class gt{config;notificationCount=0;lastResetTime=Date.now();pendingBatch=[];batchTimeout=null;activeFlush=null;constructor(e){this.config=e}async notify(e){if(this.config.filter&&!this.config.filter(e))return;if(this.config.rateLimit){let t=Date.now();if(t-this.lastResetTime>3600000)this.notificationCount=0,this.lastResetTime=t;if(this.notificationCount>=this.config.rateLimit){C.debug("Rate limit reached for failed job notifications");return}}if(this.config.batch){if(this.pendingBatch.push(e),!this.batchTimeout)this.batchTimeout=setTimeout(()=>{this.batchTimeout=null,this.activeFlush=this.flushBatch().catch((t)=>C.error("Failed to flush notification batch:",t)).finally(()=>{this.activeFlush=null})},this.config.batchInterval||60000),this.batchTimeout.unref?.();return}await this.sendNotifications([e])}async flushBatch(){if(this.pendingBatch.length===0)return;let e=[...this.pendingBatch];if(this.pendingBatch=[],this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;await this.sendNotifications(e)}async shutdown(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.activeFlush)await this.activeFlush.catch(()=>{});await this.flushBatch().catch((e)=>C.error("Failed to flush notification batch on shutdown:",e))}async sendNotifications(e){let t=[];for(let n of this.config.channels)switch(n){case"email":if(this.config.email)t.push(this.sendEmail(e));break;case"slack":if(this.config.slack)t.push(this.sendSlack(e));break;case"discord":if(this.config.discord)t.push(this.sendDiscord(e));break;case"webhook":if(this.config.webhook)t.push(this.sendWebhook(e));break;case"log":this.logFailures(e);break}try{await Promise.all(t)}catch(n){C.error("Failed to send job failure notifications:",n)}finally{this.notificationCount+=e.length}}async sendEmail(e){let t=this.config.email,n=t.subject||`[Queue] ${e.length} Job(s) Failed`,r=this.formatEmailBody(e);try{let{mail:i}=await import("@stacksjs/email");await i.send({to:Array.isArray(t.to)?t.to:[t.to],from:t.from?{address:t.from}:void 0,subject:n,html:r})}catch(i){C.error("Failed to send email notification:",i)}}formatEmailBody(e){let t=e.map((n)=>`
3
3
  <tr>
4
4
  <td style="padding: 8px; border: 1px solid #ddd;">${ke(String(n.id))}</td>
5
5
  <td style="padding: 8px; border: 1px solid #ddd;">${ke(n.name)}</td>
@@ -28,4 +28,4 @@ var{defineProperty:He,getOwnPropertyNames:sr,getOwnPropertyDescriptor:cr}=Object
28
28
  </table>
29
29
  `}async sendSlack(e){let t=this.config.slack,n=[{type:"header",text:{type:"plain_text",text:`\uD83D\uDEA8 ${e.length} Job(s) Failed`,emoji:!0}},...e.slice(0,10).map((r)=>({type:"section",text:{type:"mrkdwn",text:`*${r.name}*
30
30
  Queue: ${r.queue} | Attempts: ${r.attempts}/${r.maxAttempts}
31
- \`\`\`${r.exception.slice(0,200)}\`\`\``}}))];if(e.length>10)n.push({type:"section",text:{type:"mrkdwn",text:`_...and ${e.length-10} more failed jobs_`}});try{let r=await fetch(t.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:t.channel,username:t.username||"Queue Monitor",icon_emoji:t.iconEmoji||":warning:",blocks:n})});if(!r.ok)C.error(`Slack notification failed with status ${r.status}`)}catch(r){C.error("Failed to send Slack notification:",r)}}async sendDiscord(e){let t=this.config.discord,n=e.slice(0,10).map((r)=>({title:`\u274C ${r.name}`,color:15158332,fields:[{name:"Queue",value:r.queue,inline:!0},{name:"Attempts",value:`${r.attempts}/${r.maxAttempts}`,inline:!0},{name:"Failed At",value:r.failedAt.toISOString(),inline:!0},{name:"Exception",value:`\`\`\`${r.exception.slice(0,500)}\`\`\``}],timestamp:r.failedAt.toISOString()}));try{let r=await fetch(t.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:t.username||"Queue Monitor",avatar_url:t.avatarUrl,content:`\uD83D\uDEA8 **${e.length} Job(s) Failed**`,embeds:n})});if(!r.ok)C.error(`Discord notification failed with status ${r.status}`)}catch(r){C.error("Failed to send Discord notification:",r)}}async sendWebhook(e){let t=this.config.webhook,n={event:"jobs.failed",timestamp:new Date().toISOString(),count:e.length,jobs:e.map((i)=>({id:i.id,name:i.name,queue:i.queue,attempts:i.attempts,maxAttempts:i.maxAttempts,exception:i.exception,failedAt:i.failedAt.toISOString()}))},r={"Content-Type":"application/json",...t.headers};if(t.secret){let i=JSON.stringify(n),o=await this.generateSignature(i,t.secret);r["X-Signature"]=o}try{let i=await fetch(t.url,{method:"POST",headers:r,body:JSON.stringify(n)});if(!i.ok)C.error(`Webhook notification failed with status ${i.status}`)}catch(i){C.error("Failed to send webhook notification:",i)}}async generateSignature(e,t){let n=new TextEncoder,r=await crypto.subtle.importKey("raw",n.encode(t),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),i=await crypto.subtle.sign("HMAC",r,n.encode(e));return Array.from(new Uint8Array(i)).map((o)=>o.toString(16).padStart(2,"0")).join("")}logFailures(e){for(let t of e)C.error(`[Queue] Job "${t.name}" failed on queue "${t.queue}" after ${t.attempts} attempts: ${t.exception}`)}async cleanup(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.pendingBatch.length>0)await this.flushBatch()}}var xe=null;function qi(e){return xe=new _t(e),xe}function Di(){return xe}async function $i(e){if(xe)await xe.notify(e)}B();te();$e();import{err as Ni,ok as Kn}from"@stacksjs/error-handling";import{log as b}from"@stacksjs/logging";import ce from"process";import{env as Oi}from"@stacksjs/env";var Gn=!1;function Ai(){if(Gn)return;Gn=!0,ce.on("unhandledRejection",(e,t)=>{b.error(`Unhandled Rejection: ${e}`)}),ce.on("uncaughtException",(e)=>{b.error(`Uncaught Exception: ${e.message}`)})}var W=0,ue=!1,j="",Z=new Set,Yn=new Map;function er(e){return Z.add(e),e.finally(()=>Z.delete(e)),e}function Fi(){let e=ce.env.STACKS_QUEUE_RESERVATION_TTL_SEC,t=e===void 0?Number.NaN:Number.parseInt(e,10);return Number.isFinite(t)&&t>0?t:3600}function Ii(){let e=ce.env.STACKS_QUEUE_RETRY_JITTER;if(e===void 0)return 0.2;let t=Number(e);if(!Number.isFinite(t)||t<0)return 0.2;return Math.min(t,1)}function Qi(e,t,n=Math.random){if(!(e>0)||t<=0)return e;return Math.round(e+e*t*n())}function Bi(){let e=ce.env.STACKS_QUEUE_SWEEP_INTERVAL_SEC,t=e===void 0?Number.NaN:Number.parseInt(e,10);return(Number.isFinite(t)&&t>0?t:60)*1000}async function Xn(){let e=Fi(),t=Math.floor(Date.now()/1000)-e,n=Math.floor(Date.now()/1000);try{let{db:r}=await import("@stacksjs/database"),i=await r.updateTable("jobs").set({reserved_at:null,available_at:n}).where("reserved_at","<=",t).executeTakeFirst(),o=z(i);if(o>0)b.warn(`[queue] Requeued ${o} job(s) whose reservation exceeded the ${e}s TTL - likely victims of a worker crash. Set STACKS_QUEUE_RESERVATION_TTL_SEC to tune.`);return o}catch(r){return b.error("[queue] Reservation sweep failed",{reason:r instanceof Error?r.message:String(r)}),0}}function Mi(e){if(!e||typeof e!=="object")return!1;let t=e.status??e.statusCode;if(typeof t==="number"&&t>=400&&t<500)return!0;let n=e.name;if(typeof n==="string"&&(n==="ValidationError"||n==="ModelNotFoundError"))return!0;return!1}function Hi(){return Oi.QUEUE_DRIVER||"sync"}async function Li(e,t={}){try{b.info("Starting queue processor..."),Ai(),ue=!0,j=`worker-${ce.pid}-${Date.now()}`;let n=t.concurrency||1,r=Hi(),{getWorkerTracker:i,getGlobalMetrics:o}=await Promise.resolve().then(() => (R(),Q));if(o(),i().register(j,e||"default"),r==="redis")return b.info("Using Redis queue driver (bun-queue)"),await Xi(e||"default",n),Kn(void 0);let a;if(e)a=[e];else if(a=await tr(),a.length===0)a=["default"];return b.info(`Processing queues: ${a.join(", ")}`),await Ui(a,n),Kn(void 0)}catch(n){return ue=!1,Ni(n)}}async function tr(){try{let{db:e}=await import("@stacksjs/database"),n=(await e.selectFrom("jobs").select("queue").distinct().execute()).map((r)=>r.queue).filter((r)=>Boolean(r));return n.length>0?n:["default"]}catch{return["default"]}}async function Ui(e,t){b.info("Listening for jobs...");let n=e,r=Date.now(),i=1e4,o=Date.now(),a=Bi();await Xn();while(ue)try{let l=Date.now();if(l-r>i){try{let s=await tr();if(s.length>0)n=s}catch{}r=l}if(l-o>a)await Xn(),o=l;for(let s of n){try{let{isCircuitOpen:c}=await Promise.resolve().then(() => (ve(),Me));if(await c(s))continue}catch{}let u=[];try{u=await Wi(s,t)}catch(c){let d=Date.now(),w=Yn.get(s)??0;if(d-w>60000)Yn.set(s,d),b.error(`[queue] Could not reserve jobs on "${s}" - retrying each cycle:`,c);continue}await Promise.all(u.map(async(c)=>{try{b.info(`Processing job ${c.id} from queue "${s}"`),await er(zi(c))}catch{b.error(`Unexpected error processing job ${c.id}`)}}))}await xt(1000)}catch{await xt(3000)}}async function Wi(e,t){let n=Math.floor(Date.now()/1000),{db:r}=await import("@stacksjs/database"),i=[];for(let o=0;o<t;o++){let a=await r.selectFrom("jobs").where("queue","=",e).whereNull("reserved_at").where("available_at","<=",n).orderBy("id","asc").limit(1).selectAll().executeTakeFirst();if(!a)break;let l=await r.updateTable("jobs").set({reserved_at:n,attempts:Number(a.attempts||0)+1}).where("id","=",a.id).whereNull("reserved_at").executeTakeFirst();if(z(l)>0)i.push(a)}return i}async function zi(e){let t=e.id,n=e.queue||"default";W++;let r=Date.now(),{emitQueueEvent:i,getWorkerTracker:o}=await Promise.resolve().then(() => (R(),Q)),a=o();a.markActive(j);let l;try{l=JSON.parse(e.payload||"{}").jobName}catch{}await i("job:processing",{jobId:String(t),queueName:n,jobName:l});try{let c=JSON.parse(e.payload||"{}").payload?._batchId;if(c){let{isBatchCancelled:d}=await Promise.resolve().then(() => (B(),U));if(await d(c)){b.info(`[Queue] Skipping job ${t} - batch ${c} has been cancelled`),await kt(t),W--,a.markIdle(j);return}}}catch{}let s=null;try{let u=JSON.parse(e.payload||"{}"),c=Gi(u);if(c===void 0)await Zn(u);else await Yi(Zn(u),c*1000,`Job ${t} exceeded ${c}s timeout`)}catch(u){s=u instanceof Error?u:Error(String(u))}if(!s)try{await kt(t),b.info(`[Queue] Job ${t} completed`),a.recordCompletion(j);try{let{recordCircuitSuccess:u}=await Promise.resolve().then(() => (ve(),Me));await u(e.queue??"default")}catch{}await i("job:completed",{jobId:String(t),queueName:n,duration:Date.now()-r});try{let c=JSON.parse(e.payload||"{}").payload?._batchId;if(c){let{recordBatchJobCompletion:d}=await Promise.resolve().then(() => (B(),U));await d(c)}}catch{}}catch{b.info(`[Queue] Failed to delete completed job ${t}`)}else{let u=s.message;b.info(`[Queue] Job ${t} failed: ${u}`),a.recordFailure(j),await i("job:failed",{jobId:String(t),queueName:n,error:s,duration:Date.now()-r,attemptsMade:(e.attempts||0)+1});let c=1,d={};try{d=JSON.parse(e.payload||"{}"),c=d.options?.tries||1}catch{}let w=(e.attempts||0)+1;if(w>=c){b.info(`[Queue] Job ${t} exceeded max attempts (${w}/${c})`);let p=!1;if(d?._retriedFromFailed===!0)try{let{moveToDeadLetter:g}=await Promise.resolve().then(() => (Re(),Ut));if(p=await g({queue:e.queue,payload:e.payload,exception:s.stack||s.message},"repeat-failure",2),p)b.info(`[Queue] Job ${t} re-failed after retry - moved to dead_letter_jobs`)}catch{p=!1}if(!p){b.info(`[Queue] Moving job ${t} to failed_jobs`);try{p=await Ki(e,s,{attempts:w,maxAttempts:c,durationMs:Date.now()-r})}catch{p=!1}}if(p)try{await kt(t)}catch{b.info(`[Queue] Failed to delete failed job ${t}`)}else b.error(`[Queue] Job ${t} exhausted its retries but could NOT be persisted to failed_jobs - 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:g}=await Promise.resolve().then(() => (De(),Zt)),{recordCircuitFailure:h}=await Promise.resolve().then(() => (ve(),Me)),x=d?.jobName??"unknown";await g(x,d?.payload),await h(e.queue??"default")}catch{}try{let g=d.payload?._batchId;if(g){let{recordBatchJobFailure:h}=await Promise.resolve().then(() => (B(),U));await h(g,String(t),s)}}catch{}}else{let p=d.options?.backoff,g=30;if(Array.isArray(p)&&p.length>0){let h=Math.min(w-1,p.length-1);g=p[h]}else if(typeof p==="number"&&p>0)g=p;if(g=Number(g),!Number.isFinite(g)||g<0)g=30;g=Qi(g,Ii()),b.info(`[Queue] Job ${t} will be retried in ${g}s (attempt ${w}/${c})`);try{await Vi(t,g),b.info(`[Queue] Job ${t} released for retry`)}catch{b.info(`[Queue] Failed to release job ${t} for retry`)}}}W--,a.markIdle(j)}async function kt(e){let{db:t}=await import("@stacksjs/database");await t.deleteFrom("jobs").where("id","=",e).execute()}async function Vi(e,t=30){let n=Math.floor(Date.now()/1000)+t;b.debug(`Releasing job ${e} for retry at ${n}`);try{let{db:r}=await import("@stacksjs/database");await r.updateTable("jobs").set({reserved_at:null,available_at:n}).where("id","=",e).execute(),b.debug(`Job ${e} released successfully`)}catch{b.error(`Failed to release job ${e}`)}}async function Ki(e,t,n){try{let r=new Date().toISOString().slice(0,19).replace("T"," "),i=crypto.randomUUID(),o=t.stack||t.message,{db:a}=await import("@stacksjs/database");return await a.insertInto("failed_jobs").values({uuid:i,connection:"database",queue:e.queue,payload:e.payload,exception:o,attempts:n.attempts,max_attempts:n.maxAttempts,duration_ms:n.durationMs,failed_at:r}).execute(),!0}catch(r){return b.error("Failed to log failed job:",r),!1}}function Gi(e){if(!e||typeof e!=="object")return;let n=e.options?.timeout;if(typeof n!=="number"||!Number.isFinite(n)||n<=0)return;return n}async function Yi(e,t,n){let r,i=new Promise((o,a)=>{r=setTimeout(()=>a(Error(n)),t)});try{return await Promise.race([e,i])}finally{if(r!==void 0)clearTimeout(r)}}async function Zn(e){let t=Je(e);if(!t.ok)throw Error(`[queue] Cannot deserialize job envelope: ${t.reason}`+(t.detail?` (${t.detail})`:""));let{runJob:n}=await Promise.resolve().then(() => (ye(),Qe));await n(t.envelope.jobName,{payload:t.envelope.payload,traceId:t.envelope.traceId})}async function Xi(e,t){let{RedisQueue:n}=await Promise.resolve().then(() => (fe(),pe)),{queue:r}=await import("@stacksjs/config"),i=r?.connections?.redis;if(!i)throw Error("Redis queue connection is not configured. Check config/queue.ts");let o=new n(e,i),{emitQueueEvent:a,getWorkerTracker:l}=await Promise.resolve().then(() => (R(),Q)),s=l(),u=async(c)=>{W++,s.markActive(j);let d=Date.now(),w=Je(c.data);if(!w.ok){W--,s.markIdle(j),b.error(`[Queue] Skipping Redis job ${c.id} - unparseable envelope: ${w.reason}${w.detail?` (${w.detail})`:""}`);return}let p=w.envelope,g=p.payload?._batchId;if(g)try{let{isBatchCancelled:h}=await Promise.resolve().then(() => (B(),U));if(await h(g)){b.info(`[Queue] Skipping Redis job ${c.id} - batch ${g} has been cancelled`),W--,s.markIdle(j);return}}catch{}await a("job:processing",{jobId:String(c.id),queueName:e});try{let{runJob:h}=await Promise.resolve().then(() => (ye(),Qe));if(await h(p.jobName,{payload:p.payload,traceId:p.traceId}),s.recordCompletion(j),await a("job:completed",{jobId:String(c.id),queueName:e,duration:Date.now()-d}),g)try{let{recordBatchJobCompletion:x}=await Promise.resolve().then(() => (B(),U));await x(g)}catch{}b.info(`[Queue] Redis job ${c.id} completed`)}catch(h){if(s.recordFailure(j),await a("job:failed",{jobId:String(c.id),queueName:e,error:h instanceof Error?h:Error(String(h)),duration:Date.now()-d}),g)try{let{recordBatchJobFailure:x}=await Promise.resolve().then(() => (B(),U));await x(g,String(c.id),h instanceof Error?h:Error(String(h)))}catch{}if(b.error(`[Queue] Redis job ${c.id} failed: ${h}`),Mi(h)){b.info(`[Queue] Redis job ${c.id} hit a non-retryable error - skipping retry`);return}throw h}finally{W--,s.markIdle(j)}};o.process(t,(c)=>er(u(c))),b.info(`Listening for Redis jobs on queue "${e}" with concurrency ${t}...`);while(ue)await xt(1000);await o.close()}async function Zi(e={}){let t=e.graceMs??1e4;if(ue=!1,Z.size>0&&t>0){b.info(`[queue] Draining ${Z.size} in-flight job(s) (grace ${t}ms)`);let n=Promise.allSettled([...Z]),r=new Promise((o)=>setTimeout(()=>o("timeout"),t));if(await Promise.race([n.then(()=>"drained"),r])==="timeout"&&Z.size>0)b.warn(`[queue] Drain timed out with ${Z.size} job(s) still active. Their reservations will be reclaimed by the next worker's sweep (Q-2).`)}if(j){let{getWorkerTracker:n}=await Promise.resolve().then(() => (R(),Q));n().unregister(j)}b.info("Queue processor stopped")}async function eo(){let{db:e}=await import("@stacksjs/database"),t=await e.selectFrom("failed_jobs").selectAll().execute();for(let n of t)await nr(Number(n.id))}async function nr(e){let t=Math.floor(Date.now()/1000),n=new Date().toISOString().slice(0,19).replace("T"," "),{db:r}=await import("@stacksjs/database"),o=(await r.selectFrom("failed_jobs").where("id","=",e).selectAll().execute())[0];if(!o)throw Error(`Failed job ${e} not found`);let a=typeof o.payload==="string"?o.payload:"",l=a;try{let s=JSON.parse(a||"{}");s._retriedFromFailed=!0,l=JSON.stringify(s)}catch{}await r.insertInto("jobs").values({queue:o.queue,payload:l,attempts:0,reserved_at:null,available_at:t,created_at:n}).execute(),await r.deleteFrom("failed_jobs").where("id","=",e).execute(),b.info(`Failed job ${e} has been re-queued`)}function to(){return W}function no(){return ue}function xt(e){return new Promise((t)=>setTimeout(t,e))}async function la(){let{RedisQueue:e}=await Promise.resolve().then(() => (fe(),pe));return e}export{it as Batch,L as DispatchedBatch,_t as FailedJobNotifier,fr as JOB_ENVELOPE_VERSION,Qt as Job,xn as Jobs,nn as OnQueueEvent,Ie as PendingBatch,Ne as QueueEvents,Ae as QueueMetrics,Se as QueueTester,de as assertEnvelopeSerializable,li as cancelJob,vt as checkQueueHealth,Xe as claimDispatchKey,wr as clearEnvelopeWarnings,hi as clearJobState,qi as configureFailedJobNotifications,N as createEnvelope,Ti as createHealthCheckHandler,qt as createQueueTester,bt as discoverJobs,V as emitQueueEvent,eo as executeFailedJobs,bi as executeJob,$t as expectJobToFail,ze as fake,yr as getActionRunner,to as getActiveJobCount,mi as getAllJobs,we as getBatchCallbacks,Di as getFailedJobNotifier,Rt as getFakeQueue,Oe as getGlobalMetrics,fi as getJob,ui as getJobProgress,ie as getQueueEvents,la as getRedisQueue,ji as getRegisteredJobs,gt as getScheduledJobs,xi as getSchedulerStatus,ge as getWorkerTracker,qr as hasDispatchedKey,be as hashPayload,mn as isBatchCancelled,Sn as isCircuitOpen,Ct as isFaked,di as isJobCancelled,qe as isQuarantined,Ri as isQueueHealthy,Ji as isSchedulerRunning,no as isWorkerRunning,kn as job,Jn as jobBatch,X as jobRegistry,qn as listCircuitState,Mt as listDeadLetterJobs,Xt as listQuarantined,Te as moveToDeadLetter,$i as notifyJobFailed,en as onQueueEvent,Je as parseEnvelope,Rn as pauseQueue,Lt as purgeDeadLetterJobs,Gt as quarantineJob,Vn as queuedJobState,lt as recordBatchJobCompletion,dt as recordBatchJobFailure,Tn as recordCircuitFailure,En as recordCircuitSuccess,Dr as recordDispatchedKey,Kt as recordFailureForPoison,Ze as releaseDispatchKey,Ve as restore,Cn as resumeQueue,Ht as retryDeadLetterJob,nr as retryFailedJob,Be as runJob,Dt as runTestJob,F as serializeEnvelope,vr as setActionRunner,ci as setJobProgress,Li as startProcessor,ki as startScheduler,Zi as stopProcessor,yt as stopScheduler,gi as toJobOptions,Pi as triggerJob,Yt as unquarantineJob,tn as withEvents};
31
+ \`\`\`${r.exception.slice(0,200)}\`\`\``}}))];if(e.length>10)n.push({type:"section",text:{type:"mrkdwn",text:`_...and ${e.length-10} more failed jobs_`}});try{let r=await fetch(t.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:t.channel,username:t.username||"Queue Monitor",icon_emoji:t.iconEmoji||":warning:",blocks:n})});if(!r.ok)C.error(`Slack notification failed with status ${r.status}`)}catch(r){C.error("Failed to send Slack notification:",r)}}async sendDiscord(e){let t=this.config.discord,n=e.slice(0,10).map((r)=>({title:`\u274C ${r.name}`,color:15158332,fields:[{name:"Queue",value:r.queue,inline:!0},{name:"Attempts",value:`${r.attempts}/${r.maxAttempts}`,inline:!0},{name:"Failed At",value:r.failedAt.toISOString(),inline:!0},{name:"Exception",value:`\`\`\`${r.exception.slice(0,500)}\`\`\``}],timestamp:r.failedAt.toISOString()}));try{let r=await fetch(t.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:t.username||"Queue Monitor",avatar_url:t.avatarUrl,content:`\uD83D\uDEA8 **${e.length} Job(s) Failed**`,embeds:n})});if(!r.ok)C.error(`Discord notification failed with status ${r.status}`)}catch(r){C.error("Failed to send Discord notification:",r)}}async sendWebhook(e){let t=this.config.webhook,n={event:"jobs.failed",timestamp:new Date().toISOString(),count:e.length,jobs:e.map((i)=>({id:i.id,name:i.name,queue:i.queue,attempts:i.attempts,maxAttempts:i.maxAttempts,exception:i.exception,failedAt:i.failedAt.toISOString()}))},r={"Content-Type":"application/json",...t.headers};if(t.secret){let i=JSON.stringify(n),o=await this.generateSignature(i,t.secret);r["X-Signature"]=o}try{let i=await fetch(t.url,{method:"POST",headers:r,body:JSON.stringify(n)});if(!i.ok)C.error(`Webhook notification failed with status ${i.status}`)}catch(i){C.error("Failed to send webhook notification:",i)}}async generateSignature(e,t){let n=new TextEncoder,r=await crypto.subtle.importKey("raw",n.encode(t),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),i=await crypto.subtle.sign("HMAC",r,n.encode(e));return Array.from(new Uint8Array(i)).map((o)=>o.toString(16).padStart(2,"0")).join("")}logFailures(e){for(let t of e)C.error(`[Queue] Job "${t.name}" failed on queue "${t.queue}" after ${t.attempts} attempts: ${t.exception}`)}async cleanup(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.pendingBatch.length>0)await this.flushBatch()}}var xe=null;function si(e){return xe=new gt(e),xe}function ci(){return xe}async function ui(e){if(xe)await xe.notify(e)}M();ee();De();import{err as li,ok as yn}from"@stacksjs/error-handling";import{log as f}from"@stacksjs/logging";import se from"process";import{env as hi}from"@stacksjs/env";var vn=!1;function di(){if(vn)return;vn=!0,se.on("unhandledRejection",(e,t)=>{f.error(`Unhandled Rejection: ${e}`)}),se.on("uncaughtException",(e)=>{f.error(`Uncaught Exception: ${e.message}`)})}var L=0,ce=!1,J="",X=new Set,_n=new Map;function Jn(e){return X.add(e),e.finally(()=>X.delete(e)),e}function pi(){let e=se.env.STACKS_QUEUE_RESERVATION_TTL_SEC,t=e===void 0?Number.NaN:Number.parseInt(e,10);return Number.isFinite(t)&&t>0?t:3600}function fi(){let e=se.env.STACKS_QUEUE_RETRY_JITTER;if(e===void 0)return 0.2;let t=Number(e);if(!Number.isFinite(t)||t<0)return 0.2;return Math.min(t,1)}function mi(e,t,n=Math.random){if(!(e>0)||t<=0)return e;return Math.round(e+e*t*n())}function bi(){let e=se.env.STACKS_QUEUE_SWEEP_INTERVAL_SEC,t=e===void 0?Number.NaN:Number.parseInt(e,10);return(Number.isFinite(t)&&t>0?t:60)*1000}async function kn(){let e=pi(),t=Math.floor(Date.now()/1000)-e,n=Math.floor(Date.now()/1000);try{let{db:r}=await import("@stacksjs/database"),i=await r.updateTable("jobs").set({reserved_at:null,available_at:n}).where("reserved_at","<=",t).executeTakeFirst(),o=U(i);if(o>0)f.warn(`[queue] Requeued ${o} job(s) whose reservation exceeded the ${e}s TTL - likely victims of a worker crash. Set STACKS_QUEUE_RESERVATION_TTL_SEC to tune.`);return o}catch(r){return f.error("[queue] Reservation sweep failed",{reason:r instanceof Error?r.message:String(r)}),0}}function gi(e){if(!e||typeof e!=="object")return!1;let t=e.status??e.statusCode;if(typeof t==="number"&&t>=400&&t<500)return!0;let n=e.name;if(typeof n==="string"&&(n==="ValidationError"||n==="ModelNotFoundError"))return!0;return!1}function wi(){return hi.QUEUE_DRIVER||"sync"}async function yi(e,t={}){try{f.info("Starting queue processor..."),di(),ce=!0,J=`worker-${se.pid}-${Date.now()}`;let n=t.concurrency||1,r=wi();await Promise.resolve().then(() => T());if(be(),E().register(J,e||"default"),r==="redis")return f.info("Using Redis queue driver (bun-queue)"),await Si(e||"default",n),yn(void 0);let a;if(e)a=[e];else if(a=await jn(),a.length===0)a=["default"];return f.info(`Processing queues: ${a.join(", ")}`),await vi(a,n),yn(void 0)}catch(n){return ce=!1,li(n)}}async function jn(){try{let{db:e}=await import("@stacksjs/database"),n=(await e.selectFrom("jobs").select("queue").distinct().execute()).map((r)=>r.queue).filter((r)=>Boolean(r));return n.length>0?n:["default"]}catch{return["default"]}}async function vi(e,t){f.info("Listening for jobs...");let n=e,r=Date.now(),i=1e4,o=Date.now(),a=bi();await kn();while(ce)try{let l=Date.now();if(l-r>i){try{let s=await jn();if(s.length>0)n=s}catch{}r=l}if(l-o>a)await kn(),o=l;for(let s of n){try{await Promise.resolve().then(() => ve());if(await st(s))continue}catch{}let u=[];try{u=await _i(s,t)}catch(c){let d=Date.now(),m=_n.get(s)??0;if(d-m>60000)_n.set(s,d),f.error(`[queue] Could not reserve jobs on "${s}" - retrying each cycle:`,c);continue}await Promise.all(u.map(async(c)=>{try{f.info(`Processing job ${c.id} from queue "${s}"`),await Jn(ki(c))}catch{f.error(`Unexpected error processing job ${c.id}`)}}))}await yt(1000)}catch{await yt(3000)}}async function _i(e,t){let n=Math.floor(Date.now()/1000),{db:r}=await import("@stacksjs/database"),i=[];for(let o=0;o<t;o++){let a=await r.selectFrom("jobs").where("queue","=",e).whereNull("reserved_at").where("available_at","<=",n).orderBy("id","asc").limit(1).selectAll().executeTakeFirst();if(!a)break;let l=await r.updateTable("jobs").set({reserved_at:n,attempts:Number(a.attempts||0)+1}).where("id","=",a.id).whereNull("reserved_at").executeTakeFirst();if(U(l)>0)i.push(a)}return i}async function ki(e){let t=e.id,n=e.queue||"default";L++;let r=Date.now();await Promise.resolve().then(() => T());let a=E();a.markActive(J);let l;try{l=JSON.parse(e.payload||"{}").jobName}catch{}await _("job:processing",{jobId:String(t),queueName:n,jobName:l});try{let c=JSON.parse(e.payload||"{}").payload?._batchId;if(c){await Promise.resolve().then(() => M());if(await we(c)){f.info(`[Queue] Skipping job ${t} - batch ${c} has been cancelled`),await wt(t),L--,a.markIdle(J);return}}}catch{}let s=null;try{let u=JSON.parse(e.payload||"{}"),c=ji(u);if(c===void 0)await xn(u);else await Pi(xn(u),c*1000,`Job ${t} exceeded ${c}s timeout`)}catch(u){s=u instanceof Error?u:Error(String(u))}if(!s)try{await wt(t),f.info(`[Queue] Job ${t} completed`),a.recordCompletion(J);try{await Promise.resolve().then(() => ve());await ct(e.queue??"default")}catch{}await _("job:completed",{jobId:String(t),queueName:n,duration:Date.now()-r});try{let c=JSON.parse(e.payload||"{}").payload?._batchId;if(c){await Promise.resolve().then(() => M());await ie(c)}}catch{}}catch{f.info(`[Queue] Failed to delete completed job ${t}`)}else{let u=s.message;f.info(`[Queue] Job ${t} failed: ${u}`),a.recordFailure(J),await _("job:failed",{jobId:String(t),queueName:n,error:s,duration:Date.now()-r,attemptsMade:(e.attempts||0)+1});let c=1,d={};try{d=JSON.parse(e.payload||"{}"),c=d.options?.tries||1}catch{}let m=(e.attempts||0)+1;if(m>=c){f.info(`[Queue] Job ${t} exceeded max attempts (${m}/${c})`);let h=!1;if(d?._retriedFromFailed===!0)try{await Promise.resolve().then(() => Te());if(h=await pe({queue:e.queue,payload:e.payload,exception:s.stack||s.message},"repeat-failure",2),h)f.info(`[Queue] Job ${t} re-failed after retry - moved to dead_letter_jobs`)}catch{h=!1}if(!h){f.info(`[Queue] Moving job ${t} to failed_jobs`);try{h=await Ji(e,s,{attempts:m,maxAttempts:c,durationMs:Date.now()-r})}catch{h=!1}}if(h)try{await wt(t)}catch{f.info(`[Queue] Failed to delete failed job ${t}`)}else f.error(`[Queue] Job ${t} exhausted its retries but could NOT be persisted to failed_jobs - 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{await Promise.resolve().then(() => qe());await Promise.resolve().then(() => ve());let j=d?.jobName??"unknown";await ze(j,d?.payload),await ut(e.queue??"default")}catch{}try{let b=d.payload?._batchId;if(b){await Promise.resolve().then(() => M());await oe(b,String(t),s)}}catch{}}else{let h=d.options?.backoff,b=30;if(Array.isArray(h)&&h.length>0){let w=Math.min(m-1,h.length-1);b=h[w]}else if(typeof h==="number"&&h>0)b=h;if(b=Number(b),!Number.isFinite(b)||b<0)b=30;b=mi(b,fi()),f.info(`[Queue] Job ${t} will be retried in ${b}s (attempt ${m}/${c})`);try{await xi(t,b),f.info(`[Queue] Job ${t} released for retry`)}catch{f.info(`[Queue] Failed to release job ${t} for retry`)}}}L--,a.markIdle(J)}async function wt(e){let{db:t}=await import("@stacksjs/database");await t.deleteFrom("jobs").where("id","=",e).execute()}async function xi(e,t=30){let n=Math.floor(Date.now()/1000)+t;f.debug(`Releasing job ${e} for retry at ${n}`);try{let{db:r}=await import("@stacksjs/database");await r.updateTable("jobs").set({reserved_at:null,available_at:n}).where("id","=",e).execute(),f.debug(`Job ${e} released successfully`)}catch{f.error(`Failed to release job ${e}`)}}async function Ji(e,t,n){try{let r=new Date().toISOString().slice(0,19).replace("T"," "),i=crypto.randomUUID(),o=t.stack||t.message,{db:a}=await import("@stacksjs/database");return await a.insertInto("failed_jobs").values({uuid:i,connection:"database",queue:e.queue,payload:e.payload,exception:o,attempts:n.attempts,max_attempts:n.maxAttempts,duration_ms:n.durationMs,failed_at:r}).execute(),!0}catch(r){return f.error("Failed to log failed job:",r),!1}}function ji(e){if(!e||typeof e!=="object")return;let n=e.options?.timeout;if(typeof n!=="number"||!Number.isFinite(n)||n<=0)return;return n}async function Pi(e,t,n){let r,i=new Promise((o,a)=>{r=setTimeout(()=>a(Error(n)),t)});try{return await Promise.race([e,i])}finally{if(r!==void 0)clearTimeout(r)}}async function xn(e){let t=Je(e);if(!t.ok)throw Error(`[queue] Cannot deserialize job envelope: ${t.reason}`+(t.detail?` (${t.detail})`:""));await Promise.resolve().then(() => ye());await K(t.envelope.jobName,{payload:t.envelope.payload,traceId:t.envelope.traceId})}async function Si(e,t){await Promise.resolve().then(() => he());let{queue:r}=await import("@stacksjs/config"),i=r?.connections?.redis;if(!i)throw Error("Redis queue connection is not configured. Check config/queue.ts");let o=new Q(e,i);await Promise.resolve().then(() => T());let s=E(),u=async(c)=>{L++,s.markActive(J);let d=Date.now(),m=Je(c.data);if(!m.ok){L--,s.markIdle(J),f.error(`[Queue] Skipping Redis job ${c.id} - unparseable envelope: ${m.reason}${m.detail?` (${m.detail})`:""}`);return}let h=m.envelope,b=h.payload?._batchId;if(b)try{await Promise.resolve().then(() => M());if(await we(b)){f.info(`[Queue] Skipping Redis job ${c.id} - batch ${b} has been cancelled`),L--,s.markIdle(J);return}}catch{}await _("job:processing",{jobId:String(c.id),queueName:e});try{await Promise.resolve().then(() => ye());if(await K(h.jobName,{payload:h.payload,traceId:h.traceId}),s.recordCompletion(J),await _("job:completed",{jobId:String(c.id),queueName:e,duration:Date.now()-d}),b)try{await Promise.resolve().then(() => M());await ie(b)}catch{}f.info(`[Queue] Redis job ${c.id} completed`)}catch(w){if(s.recordFailure(J),await _("job:failed",{jobId:String(c.id),queueName:e,error:w instanceof Error?w:Error(String(w)),duration:Date.now()-d}),b)try{await Promise.resolve().then(() => M());await oe(b,String(c.id),w instanceof Error?w:Error(String(w)))}catch{}if(f.error(`[Queue] Redis job ${c.id} failed: ${w}`),gi(w)){f.info(`[Queue] Redis job ${c.id} hit a non-retryable error - skipping retry`);return}throw w}finally{L--,s.markIdle(J)}};o.process(t,(c)=>Jn(u(c))),f.info(`Listening for Redis jobs on queue "${e}" with concurrency ${t}...`);while(ce)await yt(1000);await o.close()}async function Ei(e={}){let t=e.graceMs??1e4;if(ce=!1,X.size>0&&t>0){f.info(`[queue] Draining ${X.size} in-flight job(s) (grace ${t}ms)`);let n=Promise.allSettled([...X]),r=new Promise((o)=>setTimeout(()=>o("timeout"),t));if(await Promise.race([n.then(()=>"drained"),r])==="timeout"&&X.size>0)f.warn(`[queue] Drain timed out with ${X.size} job(s) still active. Their reservations will be reclaimed by the next worker's sweep (Q-2).`)}if(J){await Promise.resolve().then(() => T());E().unregister(J)}f.info("Queue processor stopped")}async function Ti(){let{db:e}=await import("@stacksjs/database"),t=await e.selectFrom("failed_jobs").selectAll().execute();for(let n of t)await Pn(Number(n.id))}async function Pn(e){let t=Math.floor(Date.now()/1000),n=new Date().toISOString().slice(0,19).replace("T"," "),{db:r}=await import("@stacksjs/database"),o=(await r.selectFrom("failed_jobs").where("id","=",e).selectAll().execute())[0];if(!o)throw Error(`Failed job ${e} not found`);let a=typeof o.payload==="string"?o.payload:"",l=a;try{let s=JSON.parse(a||"{}");s._retriedFromFailed=!0,l=JSON.stringify(s)}catch{}await r.insertInto("jobs").values({queue:o.queue,payload:l,attempts:0,reserved_at:null,available_at:t,created_at:n}).execute(),await r.deleteFrom("failed_jobs").where("id","=",e).execute(),f.info(`Failed job ${e} has been re-queued`)}function Ri(){return L}function Ci(){return ce}function yt(e){return new Promise((t)=>setTimeout(t,e))}async function ea(){await Promise.resolve().then(() => he());return Q}export{It as Batch,W as DispatchedBatch,gt as FailedJobNotifier,Nn as JOB_ENVELOPE_VERSION,Dt as Job,it as Jobs,rr as OnQueueEvent,ge as PendingBatch,Xe as QueueEvents,Ze as QueueMetrics,Se as QueueTester,le as assertEnvelopeSerializable,Ir as cancelJob,bt as checkQueueHealth,Le as claimDispatchKey,In as clearEnvelopeWarnings,Br as clearJobState,si as configureFailedJobNotifications,A as createEnvelope,ii as createHealthCheckHandler,St as createQueueTester,ht as discoverJobs,_ as emitQueueEvent,Ti as executeFailedJobs,Ur as executeJob,Tt as expectJobToFail,Ie as fake,Qn as getActionRunner,Ri as getActiveJobCount,Lr as getAllJobs,Ne as getBatchCallbacks,ci as getFailedJobNotifier,jt as getFakeQueue,be as getGlobalMetrics,Hr as getJob,Fr as getJobProgress,me as getQueueEvents,ea as getRedisQueue,ei as getRegisteredJobs,pt as getScheduledJobs,Xr as getSchedulerStatus,E as getWorkerTracker,zn as hasDispatchedKey,Ce as hashPayload,we as isBatchCancelled,st as isCircuitOpen,Pt as isFaked,Qr as isJobCancelled,Ve as isQuarantined,oi as isQueueHealthy,Zr as isSchedulerRunning,Ci as isWorkerRunning,Rr as job,Cr as jobBatch,Y as jobRegistry,Nr as listCircuitState,Ln as listDeadLetterJobs,Xn as listQuarantined,pe as moveToDeadLetter,ui as notifyJobFailed,tr as onQueueEvent,Je as parseEnvelope,Dr as pauseQueue,Wn as purgeDeadLetterJobs,Gn as quarantineJob,wn as queuedJobState,ie as recordBatchJobCompletion,oe as recordBatchJobFailure,ut as recordCircuitFailure,ct as recordCircuitSuccess,Vn as recordDispatchedKey,ze as recordFailureForPoison,Ue as releaseDispatchKey,Qe as restore,$r as resumeQueue,Un as retryDeadLetterJob,Pn as retryFailedJob,K as runJob,Et as runTestJob,I as serializeEnvelope,Bn as setActionRunner,Or as setJobProgress,yi as startProcessor,Yr as startScheduler,Ei as stopProcessor,mt as stopScheduler,Wr as toJobOptions,ti as triggerJob,Yn as unquarantineJob,nr as withEvents};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/queue",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.35",
5
+ "version": "0.74.36",
6
6
  "description": "The Stacks Queue system powered by bun-queue.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -63,22 +63,22 @@
63
63
  "prepublishOnly": "bun run build"
64
64
  },
65
65
  "dependencies": {
66
- "@stacksjs/action-runner": "0.74.35",
66
+ "@stacksjs/action-runner": "0.74.36",
67
67
  "@stacksjs/bun-queue": "^0.1.8",
68
- "@stacksjs/cache": "0.74.35",
69
- "@stacksjs/config": "0.74.35",
70
- "@stacksjs/database": "0.74.35",
71
- "@stacksjs/env": "0.74.35",
72
- "@stacksjs/error-handling": "0.74.35",
73
- "@stacksjs/logging": "0.74.35",
74
- "@stacksjs/path": "0.74.35"
68
+ "@stacksjs/cache": "0.74.36",
69
+ "@stacksjs/config": "0.74.36",
70
+ "@stacksjs/database": "0.74.36",
71
+ "@stacksjs/env": "0.74.36",
72
+ "@stacksjs/error-handling": "0.74.36",
73
+ "@stacksjs/logging": "0.74.36",
74
+ "@stacksjs/path": "0.74.36"
75
75
  },
76
76
  "devDependencies": {
77
77
  "better-dx": "^0.2.24"
78
78
  },
79
79
  "peerDependencies": {
80
- "@stacksjs/email": "0.74.35",
81
- "@stacksjs/router": "0.74.35"
80
+ "@stacksjs/email": "0.74.36",
81
+ "@stacksjs/router": "0.74.36"
82
82
  },
83
83
  "peerDependenciesMeta": {
84
84
  "@stacksjs/email": {