@stacksjs/queue 0.74.19 → 0.74.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +7 -7
  2. package/package.json +11 -11
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  // @bun
2
- var{defineProperty:Be,getOwnPropertyNames:Xn,getOwnPropertyDescriptor:Zn}=Object,er=Object.prototype.hasOwnProperty;function tr(e){return this[e]}var nr=(e)=>{var t=(jt??=new WeakMap).get(e),n;if(t)return t;if(t=Be({},"__esModule",{value:!0}),e&&typeof e==="object"||typeof e==="function"){for(var r of Xn(e))if(!er.call(t,r))Be(t,r,{get:tr.bind(e,r),enumerable:!(n=Zn(e,r))||n.enumerable})}return jt.set(e,t),t},jt;var rr=(e)=>e;function ir(e,t){this[e]=rr.bind(null,t)}var M=(e,t)=>{for(var n in t)Be(e,n,{get:t[n],enumerable:!0,configurable:!0,set:ir.bind(t,n)})};var q=(e,t)=>()=>(e&&(t=e(e=0)),t);var h=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 ar(e,t)}}function de(e){F(e)}function ar(e,t){if(sr(t)){let i=null;try{i=Me(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(cr(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 sr(e){return e instanceof TypeError&&/bigint/i.test(e.message)}function cr(e){return e instanceof TypeError&&/circular|cyclic/i.test(e.message)}function Me(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=Me(e[r],`${t}[${r}]`,n);if(i)return i}return null}for(let[r,i]of Object.entries(e)){let o=Me(i,t?`${t}.${r}`:r,n);if(o)return o}return null}function Pt(e,t){if(He.has(e))return;He.add(e),console.warn(t)}function ur(){He.clear()}function xe(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 Pt("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 Pt("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 or=1,He;var te=q(()=>{He=new Set});import{getActionRunner as lr,runNamedAction as Je,setActionRunner as dr}from"@stacksjs/action-runner";var je=()=>{};var ze={};M(ze,{QueueTester:()=>Pe,createQueueTester:()=>Rt,expectJobToFail:()=>qt,fake:()=>Ue,getFakeQueue:()=>Et,isFaked:()=>Tt,restore:()=>We,runJob:()=>Ct});class St{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 Ue(){return he=new St,he}function Et(){return he}function Tt(){return he!==null}function We(){he=null}class Pe{queue;constructor(){this.queue=Ue()}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(){We()}}function Rt(){return new Pe}async function Ct(e,t){return await e.handle(t)}async function qt(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:()=>yr,RedisJob:()=>$t,RedisQueue:()=>ne,StacksQueueManager:()=>Dt,batch:()=>pr,chain:()=>fr,createRedisDispatcher:()=>_r,dispatch:()=>mr,dispatchAfter:()=>br,dispatchSync:()=>gr,getQueueManager:()=>wr,setQueueManager:()=>vr});import{Queue as hr,batch as pr,chain as fr,dispatch as mr,dispatchAfter as br,dispatchSync as gr,getQueueManager as wr,QueueManager as yr,setQueueManager as vr}from"@stacksjs/bun-queue";import{log as Ve}from"@stacksjs/logging";class ne{queue;config;isProcessing=!1;constructor(e,t){this.config=t,this.queue=new hr(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}),Ve.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){Ve.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process(e,t),Ve.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 Dt{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 _r(e,t){let n=new ne(e,t);return async(r,i)=>n.add(r,i)}class $t{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 Bt={};M(Bt,{listDeadLetterJobs:()=>Ft,moveToDeadLetter:()=>Ee,purgeDeadLetterJobs:()=>Qt,retryDeadLetterJob:()=>It});import{db as re}from"@stacksjs/database";function Se(){if(Ot)return;Ot=!0,console.warn("[queue/dlq] dead_letter_jobs table missing - DLQ disabled. Run migrations to enable poison-message isolation.")}async function Ee(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 Se(),!1;throw i}}async function Ft(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 Se(),[];throw t}}async function It(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 Se(),!1;throw t}}async function Qt(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 Se(),0;throw t}}var Ot=!1;var Te=()=>{};function Ke(){if(Mt)return;Mt=!0,console.warn("[queue/idempotency] job_idempotency table missing - idempotency keys are accepted but NOT enforced. Run migrations to enable dedup.")}async function xr(e){try{let{db:t}=await import("@stacksjs/database"),n=await t.selectFrom("job_idempotency").where("idempotency_key","=",e).select(["idempotency_key"]).executeTakeFirst();return Boolean(n)}catch(t){if(_(t))return Ke(),!1;throw t}}async function Jr(e,t,n){try{let{db:r}=await import("@stacksjs/database");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)){Ke();return}let i=r?.message??"";if(i.includes("UNIQUE constraint")||i.includes("Duplicate entry"))return;throw r}}async function Ge(e,t,n){try{let{db:r}=await import("@stacksjs/database");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 Ke(),"unenforced";let i=r?.message??"";if(i.includes("UNIQUE constraint")||i.includes("Duplicate entry"))return"duplicate";throw r}}async function Ye(e){try{let{db:t}=await import("@stacksjs/database");await t.deleteFrom("job_idempotency").where("idempotency_key","=",e).execute()}catch{}}var Mt=!1;var Xe=()=>{};var Vt={};M(Vt,{hashPayload:()=>be,isQuarantined:()=>Re,listQuarantined:()=>zt,quarantineJob:()=>Ut,recordFailureForPoison:()=>Lt,unquarantineJob:()=>Wt});import{createHash as jr}from"crypto";import{db as T}from"@stacksjs/database";function me(){if(Ht)return;Ht=!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 jr("sha256").update(t).digest("hex").slice(0,32)}async function Lt(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 Re(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 Ut(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 Wt(e){try{await T.deleteFrom("job_quarantine").where("job_name","=",e).execute()}catch(t){if(_(t)){me();return}throw t}}async function zt(){try{return await T.selectFrom("job_quarantine").whereNotNull("quarantined_at").selectAll().execute()??[]}catch(e){if(_(e))return me(),[];throw e}}var Ht=!1;var Ce=()=>{};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 Pr(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:Sr(t.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}}async function Ze(e,t){let n=Pr(e,t),{db:r}=await import("@stacksjs/database");await r.insertInto("jobs").values(n).execute()}function Sr(e){let t=Date.now();return Math.floor(t/1000+e)}var qe=q(()=>{te()});var Q={};M(Q,{OnQueueEvent:()=>Yt,QueueEvents:()=>De,QueueMetrics:()=>$e,emitQueueEvent:()=>V,getGlobalMetrics:()=>Ne,getQueueEvents:()=>ie,getWorkerTracker:()=>ge,onQueueEvent:()=>Kt,withEvents:()=>Gt});import{log as I}from"@stacksjs/logging";class De{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(!et)et=new De;return et}function Kt(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 Gt(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 Yt(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 $e{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 Ne(){if(!tt)tt=new $e;return tt}class Xt{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 Er}var et=null,tt=null,Er;var R=q(()=>{Er=new Xt});var U={};M(U,{Batch:()=>nt,DispatchedBatch:()=>L,PendingBatch:()=>Oe,batchCounterIncrements:()=>Zt,batchRecordFromHash:()=>at,batchRecordToHash:()=>an,getBatchCallbacks:()=>we,isBatchCancelled:()=>ln,recordBatchJobCompletion:()=>ct,recordBatchJobFailure:()=>ut});var{RedisClient:Tr}=globalThis.Bun;import{log as k}from"@stacksjs/logging";import{env as Rr}from"@stacksjs/env";function D(){return Rr.QUEUE_DRIVER||"sync"}class Oe{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 qr({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}),Cr(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 ct(e)}catch(s){await ut(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 Hr(this.id);else await Ir(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 Nr(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 Ar(this.id),Ae(this.id)}}class nt{static create(e){return new Oe(e)}static async find(e){if(!await H(e))return null;return new L(e)}static async all(){return(await Dr()).map((t)=>new L(t.id))}static async prune(e=24){return Or(e)}}function Cr(e,t){rt.set(e,t)}function we(e){return rt.get(e)}function Ae(e){rt.delete(e)}async function qr(e){if(D()==="redis")await Br(e);else await en(e)}async function H(e){if(D()==="redis")return Mr(e);return tn(e)}async function Dr(){if(D()==="redis")return sn();return nn()}async function $r(e,t){if(D()==="redis")await st(e,t);else await it(e,t)}async function Nr(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(Zt(r,t)).where("id","=",e).whereNull("cancelled_at").whereNull("finished_at").executeTakeFirst();return z(i)>0}function Zt(e,t){return{total_jobs:e`total_jobs + ${t}`,pending_jobs:e`pending_jobs + ${t}`}}async function Ar(e){if(D()==="redis")await un(e);else await rn(e)}async function Or(e){if(D()==="redis")return Lr(e);return on(e)}function Fr(e){return!!(e.then_handler||e.catch_handler||e.finally_handler)}async function en(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(!Fr(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 tn(e){let{db:t}=await import("@stacksjs/database");return await t.selectFrom("job_batches").where("id","=",e).selectAll().executeTakeFirst()||null}async function nn(){let{db:e}=await import("@stacksjs/database");return await e.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function it(e,t){let{db:n}=await import("@stacksjs/database");await n.updateTable("job_batches").set(t).where("id","=",e).execute()}async function rn(e){let{db:t}=await import("@stacksjs/database");await t.deleteFrom("job_batches").where("id","=",e).execute()}async function Ir(e){await it(e,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function on(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 Qr(){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 Tr(await Qr());return await e.connect(),e}function an(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 Br(e){try{let t=await G();await t.hset(`${K}${e.id}`,an(e)),await t.sadd(ot,e.id),t.close()}catch{await en(e)}}function at(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 Mr(e){try{let t=await G(),n=`${K}${e}`,r=await t.hgetall(n);return t.close(),at(r)}catch{return tn(e)}}async function sn(){try{let e=await G(),t=await e.smembers(ot),n=[];for(let r of t){let i=at(await e.hgetall(`${K}${r}`));if(i)n.push(i)}return e.close(),n}catch{return nn()}}async function st(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 it(e,t)}}async function cn(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 un(e){try{let t=await G();await t.del(`${K}${e}`),await t.srem(ot,e),t.close()}catch{await rn(e)}}async function Hr(e){let t=new Date().toISOString().slice(0,19).replace("T"," ");await st(e,{cancelled_at:t,finished_at:t})}async function Lr(e){try{let t=await sn(),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 un(i.id),r++}return r}catch{return on(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(),Fe));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 ct(e){let t=D(),n=!1;if(t==="redis")n=await cn(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){Ae(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)}Ae(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 ut(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 cn(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 st(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 p=await H(e);if(p){let x=[];try{x=JSON.parse(p.failed_job_ids||"[]")}catch{x=[]}x.push(t),await $r(e,{failed_job_ids:JSON.stringify(x)})}}catch{}if(a)for(let p of a.catchCallbacks)try{await p(o,n)}catch(x){k.error(`[Batch] Error in catch callback for batch ${e}:`,x)}try{let{emitQueueEvent:p}=await Promise.resolve().then(() => (R(),Q));await p("batch:failed",{jobId:e,error:n})}catch{}let w=new Date().toISOString().slice(0,19).replace("T"," "),f=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)f=f.where("pending_jobs","=",0);let g=await f.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)}Ae(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 ln(e){return(await H(e))?.cancelled_at!==null}var rt,K="stacks:batch:",ot="stacks:batches";var B=q(()=>{qe();rt=new Map});var Fe={};M(Fe,{Jobs:()=>fn,job:()=>pn,jobBatch:()=>mn,resolveJobFile:()=>bn,runJob:()=>Ie});import{appPath as Ur,frameworkPath as Wr}from"@stacksjs/path";import{env as zr}from"@stacksjs/env";import{enqueueAfterCommit as Vr,isInTransaction as Kr}from"@stacksjs/database";function Gr(){return zr.QUEUE_DRIVER||"sync"}function Yr(){if(dn)return;dn=!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 Promise.resolve().then(() => ze);if(e()){t()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(Kr()){let r=this.runDispatchPipeline.bind(this);if(Vr(async()=>{await r()}))return}else if(this.txMode==="after")Yr()}await this.runDispatchPipeline()}async runDispatchPipeline(){let e=!1;if(this.options.idempotencyKey){let t=await Ge(this.options.idempotencyKey,this.name,this.options.queue);if(t==="duplicate")return;e=t==="claimed"}try{if(await Re(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 Ee({queue:this.options.queue||"default",payload:F(n),exception:`quarantined: ${this.name}`},"poison-detected"))return}let t=Gr();if(t==="database")await this.dispatchToDatabase();else if(t==="redis")await this.dispatchToRedis();else if(t==="sync")await Ie(this.name,{payload:this.payload,context:this.options.context,traceId:await hn()});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 Ye(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 hn()),r=F(n),{db:i}=await import("@stacksjs/database");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 Ie(this.name,{payload:this.payload,context:this.options.context})}}function pn(e,t){return new A(e,t)}function mn(e){let{PendingBatch:t}=(B(),nr(U));return new t(e)}async function bn(e){let t=[Ur(`Jobs/${e}.ts`),Wr(`defaults/app/Jobs/${e}.ts`)];try{let n=import.meta.resolve("@stacksjs/defaults/package.json"),r=new URL(".",n).pathname;t.push(`${r}app/Jobs/${e}.ts`)}catch{}for(let n of t)if(await Bun.file(n).exists())return n;return null}async function Ie(e,t={}){let{withTraceId:n}=await import("@stacksjs/router"),r=t.traceId??`job:${e}:${Math.random().toString(36).slice(2,10)}`;await n(r,async()=>{let i=await bn(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 hn(){try{let{getTraceId:e}=await import("@stacksjs/router");return e()}catch{return}}var dn=!1,fn;var ye=q(()=>{Te();te();Xe();Ce();je();fn={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 Qe={};M(Qe,{isCircuitOpen:()=>wn,listCircuitState:()=>xn,pauseQueue:()=>_n,recordCircuitFailure:()=>vn,recordCircuitSuccess:()=>yn,resumeQueue:()=>kn});import{db as S}from"@stacksjs/database";function Y(){if(gn)return;gn=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing - circuit breaker disabled. Run migrations to enable.")}async function dt(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 wn(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 yn(e,t={}){let n=t.windowSeconds??300,r=new Date().toISOString().slice(0,19).replace("T"," "),i=await dt(e,r);if(!i)return;try{if(lt(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 vn(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 dt(e,l);if(!s)return!1;if(s.paused_at)return!1;try{let{success_count:u,failure_count:c}=s;if(lt(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:lt(s.window_start,r)?l:s.window_start}).where("queue_name","=",e).execute(),!1}catch(u){if(_(u))return Y(),!1;throw u}}function lt(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 _n(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 dt(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 kn(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 xn(){try{return await S.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch(e){if(_(e))return Y(),[];throw e}}var gn=!1;var ve=()=>{};te();je();import{env as kr}from"@stacksjs/env";function Nt(){return kr.QUEUE_DRIVER||"sync"}class At{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 Promise.resolve().then(() => ze);if(t()){n()?.dispatch(this.name||"UnknownJob",e,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let r=Nt();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=Nt();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 import("@stacksjs/database");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})}}je();ye();Xe();Te();Ce();ve();te();var ht=(e)=>`__job_progress__:${e}`,pt=(e)=>`__job_cancel__:${e}`;function Xr(e){return Number.isFinite(e)?Math.max(0,Math.min(100,e)):0}async function Zr(e,t,n){let{cache:r}=await import("@stacksjs/cache");await r.set(ht(e),{percent:Xr(t),message:n,updatedAt:Date.now()},3600)}async function ei(e){let{cache:t}=await import("@stacksjs/cache");return await t.get(ht(e))??null}async function ti(e){let{cache:t}=await import("@stacksjs/cache");await t.set(pt(e),1,3600)}async function ni(e){let{cache:t}=await import("@stacksjs/cache");return Boolean(await t.get(pt(e)))}async function ri(e){let{cache:t}=await import("@stacksjs/cache");await Promise.all([t.del(ht(e)),t.del(pt(e))])}import{log as se}from"@stacksjs/logging";import*as Jn from"@stacksjs/path";class jn{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 jn;async function ft(e){let t=e||Jn.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 ii(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 ii(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 oi(e){return X.get(e)}function ai(){return X.all()}function mt(){return X.scheduled()}async function si(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 ci(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 Sn}from"@stacksjs/logging";var Pn=!1;async function En(){if(Pn)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(),Pn=!0,!0}catch(e){return Sn.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${e instanceof Error?e.message:String(e)}`),!1}}async function Tn(e){if(!await En())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 Rn(e,t){if(!await En())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 ui(e){return`%"jobName":"${e.replace(/[\\%_]/g,(n)=>`\\${n}`)}"%`}async function Cn(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",[ui(e)]).execute(),i=Array.isArray(r)?r:r?.rows??[];return Array.isArray(i)&&i.length>0}catch(t){return Sn.debug(`[scheduler] overlap check unavailable, dispatching anyway: ${t instanceof Error?t.message:String(t)}`),!1}}qe();var An={checkInterval:60000,preventOverlapping:!0},y={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...An}},qn=new Set;function li(e,t){if(qn.has(e))return;qn.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 Dn=!1,$n=new Map;function di(e){let t=$n.get(e);if(!t)t=new Intl.DateTimeFormat("en-US",{timeZone:e,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),$n.set(e,t);return t}function bt(e,t){if(t&&t!=="local"&&t!=="system")try{let n=di(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(!Dn)Dn=!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 hi(e,t,n){let r=bt(new Date,n),i=r.minute,o=r.hour,a=r.day,l=r.month,s=r.dayOfWeek;if(t){let p=bt(t,n);if(p.minute===i&&p.hour===o&&p.day===a)return!1}let u=e.trim().split(/\s+/);if(u.length===6){let p=u[0];if(p&&p!=="0"&&p!=="*")li(e,p);u=u.slice(1)}if(u.length<5)return J.warn(`Invalid cron expression: ${e}`),!1;let[c,d,w,f,g]=u;return O(c,i,0,59)&&O(d,o,0,23)&&O(w,a,1,31)&&O(f,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 On(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 Fn(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),f=bt(w,t);if(O(i,f.minute,0,59)&&O(o,f.hour,0,23)&&O(a,f.day,1,31)&&O(l,f.month,1,12)&&O(s,f.dayOfWeek,0,6))return w}return null}async function pi(e={}){if(y.isRunning){J.warn("Scheduler is already running");return}y.config={...An,...e},y.isRunning=!0,y.isShuttingDown=!1,await ft();let t=mt();for(let i of t){let o=i.config.rate||i.config.schedule;if(o){let a=On(o);if(a){let l=await Tn(i.name);y.jobs.set(i.name,{job:i,lastRun:l,nextRun:Fn(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",()=>gt()),process.on("SIGTERM",()=>gt());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,Nn().catch((a)=>J.error("Scheduler check failed:",a)).finally(()=>{n=!1});r()},o),y.checkInterval?.unref?.()};r(),await Nn()}async function Nn(){for(let[e,t]of y.jobs){let n=t.job.config.rate||t.job.config.schedule;if(!n)continue;let r=On(n);if(!r)continue;if(hi(r,t.lastRun,y.config.timezone)){if((y.config.preventOverlapping||t.job.config.withoutOverlapping)&&await Cn(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=Fn(r,y.config.timezone),await Rn(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 Ze(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 gt(){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 fi(){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 mi(){return y.isRunning}function bi(){return new Map(y.jobs)}async function gi(e){let t=y.jobs.get(e);if(!t)throw Error(`Scheduled job "${e}" not found`);J.info(`Manually triggering scheduled job: ${e}`),await Ze(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 wi}from"@stacksjs/logging";function In(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 yi={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function wt(e={}){let t={...yi,...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 m of a){let v=m.queue||"default";if(!s.has(v))s.set(v,{pending:0,processing:0,delayed:0});let P=s.get(v),E=In(m,i);if(E==="processing")P.processing++;else if(E==="delayed")P.delayed++;else if(P.pending++,m.created_at){let Yn=typeof m.created_at==="number"?m.created_at:Math.floor(new Date(m.created_at).getTime()/1000),Jt=i-Yn;if(!P.oldestAge||Jt>P.oldestAge)P.oldestAge=Jt}}let u=new Map;for(let m of l){let v=m.queue||"default";u.set(v,(u.get(v)||0)+1)}let c=[],d=new Set([...s.keys(),...u.keys()]),w=t.queues?[...d].filter((m)=>t.queues.includes(m)):[...d],f=0,g=0,p=0,x=0;for(let m of w){let v=s.get(m)||{pending:0,processing:0,delayed:0},P=u.get(m)||0;f+=v.pending,g+=v.processing,p+=v.delayed,x+=P;let E="healthy";if(v.pending>=t.maxPendingCritical)E="unhealthy",n.push({level:"critical",message:`Queue "${m}" has ${v.pending} pending jobs (threshold: ${t.maxPendingCritical})`,queue:m,timestamp:r.toISOString()});else if(v.pending>=t.maxPendingWarning)E="degraded",n.push({level:"warning",message:`Queue "${m}" has ${v.pending} pending jobs (threshold: ${t.maxPendingWarning})`,queue:m,timestamp:r.toISOString()});if(P>=t.maxFailedCritical)E="unhealthy",n.push({level:"critical",message:`Queue "${m}" has ${P} failed jobs (threshold: ${t.maxFailedCritical})`,queue:m,timestamp:r.toISOString()});else if(P>=t.maxFailedWarning){if(E==="healthy")E="degraded";n.push({level:"warning",message:`Queue "${m}" has ${P} failed jobs (threshold: ${t.maxFailedWarning})`,queue:m,timestamp:r.toISOString()})}if(v.oldestAge){if(v.oldestAge>=t.maxJobAgeCritical)E="unhealthy",n.push({level:"critical",message:`Queue "${m}" has a job waiting for ${Math.floor(v.oldestAge/3600)} hours`,queue:m,timestamp:r.toISOString()});else if(v.oldestAge>=t.maxJobAgeWarning){if(E==="healthy")E="degraded";n.push({level:"warning",message:`Queue "${m}" has a job waiting for ${Math.floor(v.oldestAge/60)} minutes`,queue:m,timestamp:r.toISOString()})}}c.push({name:m,status:E,pending:v.pending,processing:v.processing,delayed:v.delayed,failed:P,oldestJobAge:v.oldestAge})}let kt=Ne().getMetrics(),Vn=kt.throughputPerMinute,Kn=kt.averageDuration,xt=f+g+p+x,le=xt>0?x/xt:0,ee="healthy";if(c.some((m)=>m.status==="unhealthy"))ee="unhealthy";else if(c.some((m)=>m.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 Gn=ge().getAll().map((m)=>({id:m.id,status:m.status,queue:m.queue,processedCount:m.processedCount,failedCount:m.failedCount,lastActivityAt:m.lastActivityAt}));return{status:ee,timestamp:r.toISOString(),queues:c,workers:Gn,metrics:{totalPending:f,totalProcessing:g,totalDelayed:p,totalFailed:x,throughputPerMinute:Vn,averageProcessingTime:Kn,errorRate:le},alerts:n}}catch(o){wi.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 vi(e={}){return async(t)=>{let n=await wt(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 _i(e={}){return(await wt(e)).status==="healthy"}import{log as C}from"@stacksjs/logging";var ki={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function _e(e){return String(e).replace(/[&<>"']/g,(t)=>ki[t]??t)}class yt{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 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 yn(e,t)}}function le(e){I(e)}function yn(e,t){if(vn(t)){let i=null;try{i=Ne(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(_n(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 vn(e){return e instanceof TypeError&&/bigint/i.test(e.message)}function _n(e){return e instanceof TypeError&&/circular|cyclic/i.test(e.message)}function Ne(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=Ne(e[r],`${t}[${r}]`,n);if(i)return i}return null}for(let[r,i]of Object.entries(e)){let o=Ne(i,t?`${t}.${r}`:r,n);if(o)return o}return null}function vt(e,t){if(Ae.has(e))return;Ae.add(e),console.warn(t)}function kn(){Ae.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 vt("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 vt("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 wn=1,Ae;var ee=q(()=>{Ae=new Set});import{getActionRunner as xn,runNamedAction as Pe,setActionRunner as Jn}from"@stacksjs/action-runner";var Se=()=>{};class _t{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 kt(){return de=new _t,de}function he(){return de}function pe(){return de!==null}function xt(){de=null}class Fe{queue;constructor(){this.queue=kt()}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(){xt()}}function jn(){return new Fe}async function Pn(e,t){return await e.handle(t)}async function Sn(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 En,batch as Pi,chain as Si,dispatch as Ei,dispatchAfter as Ti,dispatchSync as Ri,getQueueManager as Ci,QueueManager as qi,setQueueManager as Di}from"@stacksjs/bun-queue";import{log as Ie}from"@stacksjs/logging";class Q{queue;config;isProcessing=!1;constructor(e,t){this.config=t,this.queue=new En(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}),Ie.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){Ie.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process(e,t),Ie.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 fe=()=>{};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(Pt)return;Pt=!0,console.warn("[queue/dlq] dead_letter_jobs table missing - DLQ disabled. Run migrations to enable poison-message isolation.")}async function me(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 Rn(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 Cn(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 qn(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 Pt=!1;var Te=()=>{};function Qe(){if(St)return;St=!0,console.warn("[queue/idempotency] job_idempotency table missing - idempotency keys are accepted but NOT enforced. Run migrations to enable dedup.")}async function Dn(e){try{let{db:t}=await import("@stacksjs/database"),n=await t.selectFrom("job_idempotency").where("idempotency_key","=",e).select(["idempotency_key"]).executeTakeFirst();return Boolean(n)}catch(t){if(v(t))return Qe(),!1;throw t}}async function $n(e,t,n){try{let{db:r}=await import("@stacksjs/database");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)){Qe();return}let i=r?.message??"";if(i.includes("UNIQUE constraint")||i.includes("Duplicate entry"))return;throw r}}async function Be(e,t,n){try{let{db:r}=await import("@stacksjs/database");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 Qe(),"unenforced";let i=r?.message??"";if(i.includes("UNIQUE constraint")||i.includes("Duplicate entry"))return"duplicate";throw r}}async function Me(e){try{let{db:t}=await import("@stacksjs/database");await t.deleteFrom("job_idempotency").where("idempotency_key","=",e).execute()}catch{}}var St=!1;var He=()=>{};import{createHash as Nn}from"crypto";import{db as D}from"@stacksjs/database";function be(){if(Et)return;Et=!0,console.warn("[queue/poison] job_quarantine table missing - poison detection disabled. Run migrations to enable.")}function Re(e){let t;try{t=typeof e==="string"?e:JSON.stringify(e??null)}catch{t=String(e)}return Nn("sha256").update(t).digest("hex").slice(0,32)}async function Le(e,t,n={}){let r=n.maxFailures??5,i=n.windowMinutes??60,o=Re(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 be(),!1;throw s}}async function Ue(e,t){let n=Re(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 be(),!1;throw r}}async function An(e,t){let n=t===void 0?"*":Re(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)){be();return}throw i}}async function On(e){try{await D.deleteFrom("job_quarantine").where("job_name","=",e).execute()}catch(t){if(v(t)){be();return}throw t}}async function Fn(){try{return await D.selectFrom("job_quarantine").whereNotNull("quarantined_at").selectAll().execute()??[]}catch(e){if(v(e))return be(),[];throw e}}var Et=!1;var Ce=()=>{};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 In(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:Qn(t.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}}async function We(e,t){let n=In(e,t),{db:r}=await import("@stacksjs/database");await r.insertInto("jobs").values(n).execute()}function Qn(e){let t=Date.now();return Math.floor(t/1000+e)}var qe=q(()=>{ee()});import{log as B}from"@stacksjs/logging";class Ke{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 ge(){if(!ze)ze=new Ke;return ze}function Bn(e,t){let n=ge();if(e==="*")return n.onAny(t);return n.on(e,t)}function _(e,t){return ge().emit(e,t)}function Mn(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 Hn(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(){ge().subscribeListener(this,e,t)}),t}}class Ge{jobCounts={added:0,completed:0,failed:0,processing:0};completions=[];errors=[];unsubscribe=[];constructor(){this.setupListeners()}setupListeners(){let e=ge();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 we(){if(!Ve)Ve=new Ge;return Ve}class Tt{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 Ln}var ze=null,Ve=null,Ln;var T=q(()=>{Ln=new Tt});var{RedisClient:Un}=globalThis.Bun;import{log as k}from"@stacksjs/logging";import{env as Wn}from"@stacksjs/env";function N(){return Wn.QUEUE_DRIVER||"sync"}class ye{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 Vn({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}),zn(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 sr(this.id);else await nr(this.id);k.info(`[Batch] Cancelled batch ${this.id}`);let t=$e(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 Yn(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 Zn(this.id),De(this.id)}}class Rt{static create(e){return new ye(e)}static async find(e){if(!await H(e))return null;return new W(e)}static async all(){return(await Kn()).map((t)=>new W(t.id))}static async prune(e=24){return er(e)}}function zn(e,t){Ye.set(e,t)}function $e(e){return Ye.get(e)}function De(e){Ye.delete(e)}async function Vn(e){if(N()==="redis")await or(e);else await Ct(e)}async function H(e){if(N()==="redis")return ar(e);return qt(e)}async function Kn(){if(N()==="redis")return Ot();return Dt()}async function Gn(e,t){if(N()==="redis")await et(e,t);else await Xe(e,t)}async function Yn(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(Xn(r,t)).where("id","=",e).whereNull("cancelled_at").whereNull("finished_at").executeTakeFirst();return U(i)>0}function Xn(e,t){return{total_jobs:e`total_jobs + ${t}`,pending_jobs:e`pending_jobs + ${t}`}}async function Zn(e){if(N()==="redis")await It(e);else await $t(e)}async function er(e){if(N()==="redis")return cr(e);return Nt(e)}function tr(e){return!!(e.then_handler||e.catch_handler||e.finally_handler)}async function Ct(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(!tr(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 qt(e){let{db:t}=await import("@stacksjs/database");return await t.selectFrom("job_batches").where("id","=",e).selectAll().executeTakeFirst()||null}async function Dt(){let{db:e}=await import("@stacksjs/database");return await e.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function Xe(e,t){let{db:n}=await import("@stacksjs/database");await n.updateTable("job_batches").set(t).where("id","=",e).execute()}async function $t(e){let{db:t}=await import("@stacksjs/database");await t.deleteFrom("job_batches").where("id","=",e).execute()}async function nr(e){await Xe(e,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function Nt(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 rr(){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 Un(await rr());return await e.connect(),e}function ir(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 or(e){try{let t=await V();await t.hset(`${z}${e.id}`,ir(e)),await t.sadd(Ze,e.id),t.close()}catch{await Ct(e)}}function At(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 ar(e){try{let t=await V(),n=`${z}${e}`,r=await t.hgetall(n);return t.close(),At(r)}catch{return qt(e)}}async function Ot(){try{let e=await V(),t=await e.smembers(Ze),n=[];for(let r of t){let i=At(await e.hgetall(`${z}${r}`));if(i)n.push(i)}return e.close(),n}catch{return Dt()}}async function et(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 Xe(e,t)}}async function Ft(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 It(e){try{let t=await V();await t.del(`${z}${e}`),await t.srem(Ze,e),t.close()}catch{await $t(e)}}async function sr(e){let t=new Date().toISOString().slice(0,19).replace("T"," ");await et(e,{cancelled_at:t,finished_at:t})}async function cr(e){try{let t=await Ot(),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 It(i.id),r++}return r}catch{return Nt(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(() => _e());await tt.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 Ft(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=$e(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){De(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)}De(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=$e(e),l=!1;if(N()==="redis"){l=await Ft(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 et(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 Gn(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)}De(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 ve(e){return(await H(e))?.cancelled_at!==null}var Ye,z="stacks:batch:",Ze="stacks:batches";var M=q(()=>{qe();Ye=new Map});import{appPath as ur,frameworkPath as lr}from"@stacksjs/path";import{env as dr}from"@stacksjs/env";import{enqueueAfterCommit as hr,isInTransaction as pr}from"@stacksjs/database";function fr(){return dr.QUEUE_DRIVER||"sync"}function mr(){if(Qt)return;Qt=!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(){await Promise.resolve();if(pe()){he()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(pr()){let r=this.runDispatchPipeline.bind(this);if(hr(async()=>{await r()}))return}else if(this.txMode==="after")mr()}await this.runDispatchPipeline()}async runDispatchPipeline(){let e=!1;if(this.options.idempotencyKey){let t=await Be(this.options.idempotencyKey,this.name,this.options.queue);if(t==="duplicate")return;e=t==="claimed"}try{if(await Ue(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 me({queue:this.options.queue||"default",payload:I(n),exception:`quarantined: ${this.name}`},"poison-detected"))return}let t=fr();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 Bt()});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 Me(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 Bt()),r=I(n),{db:i}=await import("@stacksjs/database");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(() => fe());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 br(e,t){return new O(e,t)}function gr(e){M();return new ye(e)}async function wr(e){let t=[ur(`Jobs/${e}.ts`),lr(`defaults/app/Jobs/${e}.ts`)];try{let n=import.meta.resolve("@stacksjs/defaults/package.json"),r=new URL(".",n).pathname;t.push(`${r}app/Jobs/${e}.ts`)}catch{}for(let n of t)if(await Bun.file(n).exists())return n;return null}async function K(e,t={}){let{withTraceId:n}=await import("@stacksjs/router"),r=t.traceId??`job:${e}:${Math.random().toString(36).slice(2,10)}`;await n(r,async()=>{let i=await wr(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 Pe(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 Bt(){try{let{getTraceId:e}=await import("@stacksjs/router");return e()}catch{return}}var Qt=!1,tt;var _e=q(()=>{Te();ee();He();Ce();Se();tt={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(Mt)return;Mt=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing - circuit breaker disabled. Run migrations to enable.")}async function rt(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 it(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 ot(e,t={}){let n=t.windowSeconds??300,r=new Date().toISOString().slice(0,19).replace("T"," "),i=await rt(e,r);if(!i)return;try{if(nt(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 at(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 rt(e,l);if(!s)return!1;if(s.paused_at)return!1;try{let{success_count:u,failure_count:c}=s;if(nt(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:nt(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 nt(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 yr(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 rt(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 vr(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 _r(){try{return await R.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch(e){if(v(e))return G(),[];throw e}}var Mt=!1;var ke=()=>{};ee();Se();import{env as Tn}from"@stacksjs/env";function Jt(){return Tn.QUEUE_DRIVER||"sync"}class jt{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]){await Promise.resolve();if(pe()){he()?.dispatch(this.name||"UnknownJob",e,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let r=Jt();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=Jt();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 Pe(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 import("@stacksjs/database");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(() => fe());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})}}Se();_e();He();Te();Ce();ke();ee();var st=(e)=>`__job_progress__:${e}`,ct=(e)=>`__job_cancel__:${e}`;function kr(e){return Number.isFinite(e)?Math.max(0,Math.min(100,e)):0}async function xr(e,t,n){let{cache:r}=await import("@stacksjs/cache");await r.set(st(e),{percent:kr(t),message:n,updatedAt:Date.now()},3600)}async function Jr(e){let{cache:t}=await import("@stacksjs/cache");return await t.get(st(e))??null}async function jr(e){let{cache:t}=await import("@stacksjs/cache");await t.set(ct(e),1,3600)}async function Pr(e){let{cache:t}=await import("@stacksjs/cache");return Boolean(await t.get(ct(e)))}async function Sr(e){let{cache:t}=await import("@stacksjs/cache");await Promise.all([t.del(st(e)),t.del(ct(e))])}import{log as ae}from"@stacksjs/logging";import*as Ht from"@stacksjs/path";class Lt{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 Lt;async function ut(e){let t=e||Ht.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 Er(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 Er(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 Tr(e){return Y.get(e)}function Rr(){return Y.all()}function lt(){return Y.scheduled()}async function Cr(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 qr(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 Wt}from"@stacksjs/logging";var Ut=!1;async function zt(){if(Ut)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(),Ut=!0,!0}catch(e){return Wt.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${e instanceof Error?e.message:String(e)}`),!1}}async function Vt(e){if(!await zt())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 Kt(e,t){if(!await zt())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 Dr(e){return`%"jobName":"${e.replace(/[\\%_]/g,(n)=>`\\${n}`)}"%`}async function Gt(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",[Dr(e)]).execute(),i=Array.isArray(r)?r:r?.rows??[];return Array.isArray(i)&&i.length>0}catch(t){return Wt.debug(`[scheduler] overlap check unavailable, dispatching anyway: ${t instanceof Error?t.message:String(t)}`),!1}}qe();var tn={checkInterval:60000,preventOverlapping:!0},g={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...tn}},Yt=new Set;function $r(e,t){if(Yt.has(e))return;Yt.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 Xt=!1,Zt=new Map;function Nr(e){let t=Zt.get(e);if(!t)t=new Intl.DateTimeFormat("en-US",{timeZone:e,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),Zt.set(e,t);return t}function dt(e,t){if(t&&t!=="local"&&t!=="system")try{let n=Nr(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(!Xt)Xt=!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 Ar(e,t,n){let r=dt(new Date,n),{minute:i,hour:o,day:a,month:l,dayOfWeek:s}=r;if(t){let w=dt(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!=="*")$r(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 nn(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 rn(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=dt(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 Or(e={}){if(g.isRunning){x.warn("Scheduler is already running");return}g.config={...tn,...e},g.isRunning=!0,g.isShuttingDown=!1,await ut();let t=lt();for(let i of t){let o=i.config.rate||i.config.schedule;if(o){let a=nn(o);if(a){let l=await Vt(i.name);g.jobs.set(i.name,{job:i,lastRun:l,nextRun:rn(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",()=>ht()),process.on("SIGTERM",()=>ht());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,en().catch((a)=>x.error("Scheduler check failed:",a)).finally(()=>{n=!1});r()},o),g.checkInterval?.unref?.()};r(),await en()}async function en(){for(let[e,t]of g.jobs){let n=t.job.config.rate||t.job.config.schedule;if(!n)continue;let r=nn(n);if(!r)continue;if(Ar(r,t.lastRun,g.config.timezone)){if((g.config.preventOverlapping||t.job.config.withoutOverlapping)&&await Gt(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=rn(r,g.config.timezone),await Kt(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 We(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 ht(){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 Fr(){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 Ir(){return g.isRunning}function Qr(){return new Map(g.jobs)}async function Br(e){let t=g.jobs.get(e);if(!t)throw Error(`Scheduled job "${e}" not found`);x.info(`Manually triggering scheduled job: ${e}`),await We(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 Mr}from"@stacksjs/logging";function on(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 Hr={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function pt(e={}){let t={...Hr,...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=on(p,i);if(S==="processing")P.processing++;else if(S==="delayed")P.delayed++;else if(P.pending++,p.created_at){let gn=typeof p.created_at==="number"?p.created_at:Math.floor(new Date(p.created_at).getTime()/1000),yt=i-gn;if(!P.oldestAge||yt>P.oldestAge)P.oldestAge=yt}}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 gt=we().getMetrics(),{throughputPerMinute:fn,averageDuration:mn}=gt,wt=h+b+w+j,ue=wt>0?j/wt: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 bn=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:bn,metrics:{totalPending:h,totalProcessing:b,totalDelayed:w,totalFailed:j,throughputPerMinute:fn,averageProcessingTime:mn,errorRate:ue},alerts:n}}catch(o){Mr.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 Lr(e={}){return async(t)=>{let n=await pt(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 Ur(e={}){return(await pt(e)).status==="healthy"}import{log as C}from"@stacksjs/logging";var Wr={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function xe(e){return String(e).replace(/[&<>"']/g,(t)=>Wr[t]??t)}class ft{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
- <td style="padding: 8px; border: 1px solid #ddd;">${_e(String(n.id))}</td>
5
- <td style="padding: 8px; border: 1px solid #ddd;">${_e(n.name)}</td>
6
- <td style="padding: 8px; border: 1px solid #ddd;">${_e(n.queue)}</td>
4
+ <td style="padding: 8px; border: 1px solid #ddd;">${xe(String(n.id))}</td>
5
+ <td style="padding: 8px; border: 1px solid #ddd;">${xe(n.name)}</td>
6
+ <td style="padding: 8px; border: 1px solid #ddd;">${xe(n.queue)}</td>
7
7
  <td style="padding: 8px; border: 1px solid #ddd;">${n.attempts}/${n.maxAttempts}</td>
8
- <td style="padding: 8px; border: 1px solid #ddd;">${_e(n.failedAt.toISOString())}</td>
9
- <td style="padding: 8px; border: 1px solid #ddd;"><pre style="max-width: 300px; overflow: auto;">${_e(n.exception)}</pre></td>
8
+ <td style="padding: 8px; border: 1px solid #ddd;">${xe(n.failedAt.toISOString())}</td>
9
+ <td style="padding: 8px; border: 1px solid #ddd;"><pre style="max-width: 300px; overflow: auto;">${xe(n.exception)}</pre></td>
10
10
  </tr>
11
11
  `).join("");return`
12
12
  <h2>Failed Jobs Report</h2>
@@ -28,4 +28,4 @@ var{defineProperty:Be,getOwnPropertyNames:Xn,getOwnPropertyDescriptor:Zn}=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 ke=null;function xi(e){return ke=new yt(e),ke}function Ji(){return ke}async function ji(e){if(ke)await ke.notify(e)}B();te();qe();import{err as Pi,ok as Qn}from"@stacksjs/error-handling";import{log as b}from"@stacksjs/logging";import ce from"process";import{env as Ei}from"@stacksjs/env";var Bn=!1;function Si(){if(Bn)return;Bn=!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,Mn=new Map;function Un(e){return Z.add(e),e.finally(()=>Z.delete(e)),e}function Ti(){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 Ri(){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 Ci(e,t,n=Math.random){if(!(e>0)||t<=0)return e;return Math.round(e+e*t*n())}function qi(){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 Hn(){let e=Ti(),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 Di(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 $i(){return Ei.QUEUE_DRIVER||"sync"}async function Ni(e,t={}){try{b.info("Starting queue processor..."),Si(),ue=!0,j=`worker-${ce.pid}-${Date.now()}`;let n=t.concurrency||1,r=$i(),{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 Hi(e||"default",n),Qn(void 0);let a;if(e)a=[e];else if(a=await Wn(),a.length===0)a=["default"];return b.info(`Processing queues: ${a.join(", ")}`),await Ai(a,n),Qn(void 0)}catch(n){return ue=!1,Pi(n)}}async function Wn(){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 Ai(e,t){b.info("Listening for jobs...");let n=e,r=Date.now(),i=1e4,o=Date.now(),a=qi();await Hn();while(ue)try{let l=Date.now();if(l-r>i){try{let s=await Wn();if(s.length>0)n=s}catch{}r=l}if(l-o>a)await Hn(),o=l;for(let s of n){try{let{isCircuitOpen:c}=await Promise.resolve().then(() => (ve(),Qe));if(await c(s))continue}catch{}let u=[];try{u=await Oi(s,t)}catch(c){let d=Date.now(),w=Mn.get(s)??0;if(d-w>60000)Mn.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 Un(Fi(c))}catch{b.error(`Unexpected error processing job ${c.id}`)}}))}await _t(1000)}catch{await _t(3000)}}async function Oi(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 Fi(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 vt(t),W--,a.markIdle(j);return}}}catch{}let s=null;try{let u=JSON.parse(e.payload||"{}"),c=Bi(u);if(c===void 0)await Ln(u);else await Mi(Ln(u),c*1000,`Job ${t} exceeded ${c}s timeout`)}catch(u){s=u instanceof Error?u:Error(String(u))}if(!s)try{await vt(t),b.info(`[Queue] Job ${t} completed`),a.recordCompletion(j);try{let{recordCircuitSuccess:u}=await Promise.resolve().then(() => (ve(),Qe));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 f=!1;if(d?._retriedFromFailed===!0)try{let{moveToDeadLetter:g}=await Promise.resolve().then(() => (Te(),Bt));if(f=await g({queue:e.queue,payload:e.payload,exception:s.stack||s.message},"repeat-failure",2),f)b.info(`[Queue] Job ${t} re-failed after retry - moved to dead_letter_jobs`)}catch{f=!1}if(!f){b.info(`[Queue] Moving job ${t} to failed_jobs`);try{f=await Qi(e,s,{attempts:w,maxAttempts:c,durationMs:Date.now()-r})}catch{f=!1}}if(f)try{await vt(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(() => (Ce(),Vt)),{recordCircuitFailure:p}=await Promise.resolve().then(() => (ve(),Qe)),x=d?.jobName??"unknown";await g(x,d?.payload),await p(e.queue??"default")}catch{}try{let g=d.payload?._batchId;if(g){let{recordBatchJobFailure:p}=await Promise.resolve().then(() => (B(),U));await p(g,String(t),s)}}catch{}}else{let f=d.options?.backoff,g=30;if(Array.isArray(f)&&f.length>0){let p=Math.min(w-1,f.length-1);g=f[p]}else if(typeof f==="number"&&f>0)g=f;if(g=Number(g),!Number.isFinite(g)||g<0)g=30;g=Ci(g,Ri()),b.info(`[Queue] Job ${t} will be retried in ${g}s (attempt ${w}/${c})`);try{await Ii(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 vt(e){let{db:t}=await import("@stacksjs/database");await t.deleteFrom("jobs").where("id","=",e).execute()}async function Ii(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 Qi(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 Bi(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 Mi(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 Ln(e){let t=xe(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(),Fe));await n(t.envelope.jobName,{payload:t.envelope.payload,traceId:t.envelope.traceId})}async function Hi(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=xe(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 f=w.envelope,g=f.payload?._batchId;if(g)try{let{isBatchCancelled:p}=await Promise.resolve().then(() => (B(),U));if(await p(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:p}=await Promise.resolve().then(() => (ye(),Fe));if(await p(f.jobName,{payload:f.payload,traceId:f.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(p){if(s.recordFailure(j),await a("job:failed",{jobId:String(c.id),queueName:e,error:p instanceof Error?p:Error(String(p)),duration:Date.now()-d}),g)try{let{recordBatchJobFailure:x}=await Promise.resolve().then(() => (B(),U));await x(g,String(c.id),p instanceof Error?p:Error(String(p)))}catch{}if(b.error(`[Queue] Redis job ${c.id} failed: ${p}`),Di(p)){b.info(`[Queue] Redis job ${c.id} hit a non-retryable error - skipping retry`);return}throw p}finally{W--,s.markIdle(j)}};o.process(t,(c)=>Un(u(c))),b.info(`Listening for Redis jobs on queue "${e}" with concurrency ${t}...`);while(ue)await _t(1000);await o.close()}async function Li(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 Ui(){let{db:e}=await import("@stacksjs/database"),t=await e.selectFrom("failed_jobs").selectAll().execute();for(let n of t)await zn(Number(n.id))}async function zn(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 Wi(){return W}function zi(){return ue}function _t(e){return new Promise((t)=>setTimeout(t,e))}async function ta(){let{RedisQueue:e}=await Promise.resolve().then(() => (fe(),pe));return e}export{nt as Batch,L as DispatchedBatch,yt as FailedJobNotifier,or as JOB_ENVELOPE_VERSION,At as Job,fn as Jobs,Yt as OnQueueEvent,Oe as PendingBatch,De as QueueEvents,$e as QueueMetrics,Pe as QueueTester,de as assertEnvelopeSerializable,ti as cancelJob,wt as checkQueueHealth,Ge as claimDispatchKey,ur as clearEnvelopeWarnings,ri as clearJobState,xi as configureFailedJobNotifications,N as createEnvelope,vi as createHealthCheckHandler,Rt as createQueueTester,ft as discoverJobs,V as emitQueueEvent,Ui as executeFailedJobs,si as executeJob,qt as expectJobToFail,Ue as fake,lr as getActionRunner,Wi as getActiveJobCount,ai as getAllJobs,we as getBatchCallbacks,Ji as getFailedJobNotifier,Et as getFakeQueue,Ne as getGlobalMetrics,oi as getJob,ei as getJobProgress,ie as getQueueEvents,ta as getRedisQueue,bi as getRegisteredJobs,mt as getScheduledJobs,fi as getSchedulerStatus,ge as getWorkerTracker,xr as hasDispatchedKey,be as hashPayload,ln as isBatchCancelled,wn as isCircuitOpen,Tt as isFaked,ni as isJobCancelled,Re as isQuarantined,_i as isQueueHealthy,mi as isSchedulerRunning,zi as isWorkerRunning,pn as job,mn as jobBatch,X as jobRegistry,xn as listCircuitState,Ft as listDeadLetterJobs,zt as listQuarantined,Ee as moveToDeadLetter,ji as notifyJobFailed,Kt as onQueueEvent,xe as parseEnvelope,_n as pauseQueue,Qt as purgeDeadLetterJobs,Ut as quarantineJob,In as queuedJobState,ct as recordBatchJobCompletion,ut as recordBatchJobFailure,vn as recordCircuitFailure,yn as recordCircuitSuccess,Jr as recordDispatchedKey,Lt as recordFailureForPoison,Ye as releaseDispatchKey,We as restore,kn as resumeQueue,It as retryDeadLetterJob,zn as retryFailedJob,Ie as runJob,Ct as runTestJob,F as serializeEnvelope,dr as setActionRunner,Zr as setJobProgress,Ni as startProcessor,pi as startScheduler,Li as stopProcessor,gt as stopScheduler,ci as toJobOptions,gi as triggerJob,Wt as unquarantineJob,Gt 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 Je=null;function zr(e){return Je=new ft(e),Je}function Vr(){return Je}async function Kr(e){if(Je)await Je.notify(e)}M();ee();qe();import{err as Gr,ok as an}from"@stacksjs/error-handling";import{log as f}from"@stacksjs/logging";import se from"process";import{env as Xr}from"@stacksjs/env";var sn=!1;function Yr(){if(sn)return;sn=!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,cn=new Map;function dn(e){return X.add(e),e.finally(()=>X.delete(e)),e}function Zr(){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 ei(){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 ti(e,t,n=Math.random){if(!(e>0)||t<=0)return e;return Math.round(e+e*t*n())}function ni(){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 un(){let e=Zr(),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 ri(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 ii(){return Xr.QUEUE_DRIVER||"sync"}async function oi(e,t={}){try{f.info("Starting queue processor..."),Yr(),ce=!0,J=`worker-${se.pid}-${Date.now()}`;let n=t.concurrency||1,r=ii();await Promise.resolve().then(() => T());if(we(),E().register(J,e||"default"),r==="redis")return f.info("Using Redis queue driver (bun-queue)"),await pi(e||"default",n),an(void 0);let a;if(e)a=[e];else if(a=await hn(),a.length===0)a=["default"];return f.info(`Processing queues: ${a.join(", ")}`),await ai(a,n),an(void 0)}catch(n){return ce=!1,Gr(n)}}async function hn(){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 ai(e,t){f.info("Listening for jobs...");let n=e,r=Date.now(),i=1e4,o=Date.now(),a=ni();await un();while(ce)try{let l=Date.now();if(l-r>i){try{let s=await hn();if(s.length>0)n=s}catch{}r=l}if(l-o>a)await un(),o=l;for(let s of n){try{await Promise.resolve().then(() => ke());if(await it(s))continue}catch{}let u=[];try{u=await si(s,t)}catch(c){let d=Date.now(),m=cn.get(s)??0;if(d-m>60000)cn.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 dn(ci(c))}catch{f.error(`Unexpected error processing job ${c.id}`)}}))}await bt(1000)}catch{await bt(3000)}}async function si(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 ci(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 ve(c)){f.info(`[Queue] Skipping job ${t} - batch ${c} has been cancelled`),await mt(t),L--,a.markIdle(J);return}}}catch{}let s=null;try{let u=JSON.parse(e.payload||"{}"),c=di(u);if(c===void 0)await ln(u);else await hi(ln(u),c*1000,`Job ${t} exceeded ${c}s timeout`)}catch(u){s=u instanceof Error?u:Error(String(u))}if(!s)try{await mt(t),f.info(`[Queue] Job ${t} completed`),a.recordCompletion(J);try{await Promise.resolve().then(() => ke());await ot(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 me({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 li(e,s,{attempts:m,maxAttempts:c,durationMs:Date.now()-r})}catch{h=!1}}if(h)try{await mt(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(() => Ce());await Promise.resolve().then(() => ke());let j=d?.jobName??"unknown";await Le(j,d?.payload),await at(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=ti(b,ei()),f.info(`[Queue] Job ${t} will be retried in ${b}s (attempt ${m}/${c})`);try{await ui(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 mt(e){let{db:t}=await import("@stacksjs/database");await t.deleteFrom("jobs").where("id","=",e).execute()}async function ui(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 li(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 di(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 hi(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 ln(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(() => _e());await K(t.envelope.jobName,{payload:t.envelope.payload,traceId:t.envelope.traceId})}async function pi(e,t){await Promise.resolve().then(() => fe());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 ve(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(() => _e());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}`),ri(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)=>dn(u(c))),f.info(`Listening for Redis jobs on queue "${e}" with concurrency ${t}...`);while(ce)await bt(1000);await o.close()}async function fi(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 mi(){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 bi(){return L}function gi(){return ce}function bt(e){return new Promise((t)=>setTimeout(t,e))}async function Bo(){await Promise.resolve().then(() => fe());return Q}export{Rt as Batch,W as DispatchedBatch,ft as FailedJobNotifier,wn as JOB_ENVELOPE_VERSION,jt as Job,tt as Jobs,Hn as OnQueueEvent,ye as PendingBatch,Ke as QueueEvents,Ge as QueueMetrics,Fe as QueueTester,le as assertEnvelopeSerializable,jr as cancelJob,pt as checkQueueHealth,Be as claimDispatchKey,kn as clearEnvelopeWarnings,Sr as clearJobState,zr as configureFailedJobNotifications,A as createEnvelope,Lr as createHealthCheckHandler,jn as createQueueTester,ut as discoverJobs,_ as emitQueueEvent,mi as executeFailedJobs,Cr as executeJob,Sn as expectJobToFail,kt as fake,xn as getActionRunner,bi as getActiveJobCount,Rr as getAllJobs,$e as getBatchCallbacks,Vr as getFailedJobNotifier,he as getFakeQueue,we as getGlobalMetrics,Tr as getJob,Jr as getJobProgress,ge as getQueueEvents,Bo as getRedisQueue,Qr as getRegisteredJobs,lt as getScheduledJobs,Fr as getSchedulerStatus,E as getWorkerTracker,Dn as hasDispatchedKey,Re as hashPayload,ve as isBatchCancelled,it as isCircuitOpen,pe as isFaked,Pr as isJobCancelled,Ue as isQuarantined,Ur as isQueueHealthy,Ir as isSchedulerRunning,gi as isWorkerRunning,br as job,gr as jobBatch,Y as jobRegistry,_r as listCircuitState,Rn as listDeadLetterJobs,Fn as listQuarantined,me as moveToDeadLetter,Kr as notifyJobFailed,Bn as onQueueEvent,je as parseEnvelope,yr as pauseQueue,qn as purgeDeadLetterJobs,An as quarantineJob,on as queuedJobState,ie as recordBatchJobCompletion,oe as recordBatchJobFailure,at as recordCircuitFailure,ot as recordCircuitSuccess,$n as recordDispatchedKey,Le as recordFailureForPoison,Me as releaseDispatchKey,xt as restore,vr as resumeQueue,Cn as retryDeadLetterJob,pn as retryFailedJob,K as runJob,Pn as runTestJob,I as serializeEnvelope,Jn as setActionRunner,xr as setJobProgress,oi as startProcessor,Or as startScheduler,fi as stopProcessor,ht as stopScheduler,qr as toJobOptions,Br as triggerJob,On as unquarantineJob,Mn 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.19",
5
+ "version": "0.74.21",
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.19",
66
+ "@stacksjs/action-runner": "0.74.21",
67
67
  "@stacksjs/bun-queue": "^0.1.8",
68
- "@stacksjs/cache": "0.74.19",
69
- "@stacksjs/config": "0.74.19",
70
- "@stacksjs/database": "0.74.19",
71
- "@stacksjs/env": "0.74.19",
72
- "@stacksjs/error-handling": "0.74.19",
73
- "@stacksjs/logging": "0.74.19",
74
- "@stacksjs/path": "0.74.19"
68
+ "@stacksjs/cache": "0.74.21",
69
+ "@stacksjs/config": "0.74.21",
70
+ "@stacksjs/database": "0.74.21",
71
+ "@stacksjs/env": "0.74.21",
72
+ "@stacksjs/error-handling": "0.74.21",
73
+ "@stacksjs/logging": "0.74.21",
74
+ "@stacksjs/path": "0.74.21"
75
75
  },
76
76
  "devDependencies": {
77
77
  "better-dx": "^0.2.24"
78
78
  },
79
79
  "peerDependencies": {
80
- "@stacksjs/email": "0.74.19",
81
- "@stacksjs/router": "0.74.19"
80
+ "@stacksjs/email": "0.74.21",
81
+ "@stacksjs/router": "0.74.21"
82
82
  },
83
83
  "peerDependenciesMeta": {
84
84
  "@stacksjs/email": {