@stacksjs/queue 0.74.3 → 0.74.5

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.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * How a queued job runs an action it names by string.
3
+ *
4
+ * The port itself moved to `@stacksjs/action-runner` once the scheduler and
5
+ * the DNS package turned out to need the same thing for the same reason. It
6
+ * is re-exported here so `@stacksjs/queue`'s own API is unchanged.
7
+ */
8
+ export type { ActionRunner } from '@stacksjs/action-runner';
9
+ export { getActionRunner, runNamedAction, setActionRunner } from '@stacksjs/action-runner';
package/dist/index.d.ts CHANGED
@@ -21,6 +21,10 @@ export declare function getRedisQueue(): Promise<void>;
21
21
  // Stacks Job class for file-based jobs (app/Jobs/*.ts)
22
22
  // =============================================================================
23
23
  export { Job } from './action';
24
+ // How a job runs an action it names by string. `@stacksjs/actions` registers
25
+ // itself here on import; see ./action-runner for why the queue asks rather
26
+ // than imports.
27
+ export { type ActionRunner, getActionRunner, setActionRunner } from './action-runner';
24
28
  // =============================================================================
25
29
  // Stacks job helper for dispatching file-based jobs
26
30
  // =============================================================================
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var{defineProperty:Ie,getOwnPropertyNames:Gn,getOwnPropertyDescriptor:Yn}=Object,Xn=Object.prototype.hasOwnProperty;function Zn(e){return this[e]}var er=(e)=>{var t=(xt??=new WeakMap).get(e),n;if(t)return t;if(t=Ie({},"__esModule",{value:!0}),e&&typeof e==="object"||typeof e==="function"){for(var r of Gn(e))if(!Xn.call(t,r))Ie(t,r,{get:Zn.bind(e,r),enumerable:!(n=Yn(e,r))||n.enumerable})}return xt.set(e,t),t},xt;var tr=(e)=>e;function nr(e,t){this[e]=tr.bind(null,t)}var M=(e,t)=>{for(var n in t)Ie(e,n,{get:t[n],enumerable:!0,configurable:!0,set:nr.bind(t,n)})};var D=(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 ir(e,t)}}function de(e){F(e)}function ir(e,t){if(or(t)){let i=null;try{i=Qe(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(ar(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 or(e){return e instanceof TypeError&&/bigint/i.test(e.message)}function ar(e){return e instanceof TypeError&&/circular|cyclic/i.test(e.message)}function Qe(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=Qe(e[r],`${t}[${r}]`,n);if(i)return i}return null}for(let[r,i]of Object.entries(e)){let o=Qe(i,t?`${t}.${r}`:r,n);if(o)return o}return null}function Jt(e,t){if(Be.has(e))return;Be.add(e),console.warn(t)}function sr(){Be.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 Jt("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 Jt("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 rr=1,Be;var te=D(()=>{Be=new Set});var Ue={};M(Ue,{QueueTester:()=>Je,createQueueTester:()=>Et,expectJobToFail:()=>Rt,fake:()=>He,getFakeQueue:()=>Pt,isFaked:()=>St,restore:()=>Le,runJob:()=>Tt});class jt{dispatchedJobs=[];pushedJobs=[];processedJobs=[];failedJobs=[];dispatch(e,t,n={}){this.dispatchedJobs.push({name:e,data:t,options:n,dispatchedAt:new Date,queue:n.queue||"default"})}push(e,t,n={}){this.pushedJobs.push({name:e,data:t,options:n,dispatchedAt:new Date,queue:n.queue||"default"})}dispatched(e){if(e)return this.dispatchedJobs.filter((t)=>t.name===e);return[...this.dispatchedJobs]}pushed(e){if(e)return this.pushedJobs.filter((t)=>t.name===e);return[...this.pushedJobs]}assertDispatched(e,t){let n=this.dispatched(e);if(n.length===0)throw Error(`Expected job "${e}" to be dispatched, but it was not.`);if(t){if(n.filter(t).length===0)throw Error(`Expected job "${e}" to be dispatched matching the callback, but no matching jobs were found.`)}}assertNotDispatched(e){let t=this.dispatched(e);if(t.length>0)throw Error(`Expected job "${e}" to not be dispatched, but it was dispatched ${t.length} time(s).`)}assertDispatchedTimes(e,t){let n=this.dispatched(e);if(n.length!==t)throw Error(`Expected job "${e}" to be dispatched ${t} time(s), but it was dispatched ${n.length} time(s).`)}assertNothingDispatched(){if(this.dispatchedJobs.length>0){let e=[...new Set(this.dispatchedJobs.map((t)=>t.name))].join(", ");throw Error(`Expected no jobs to be dispatched, but found: ${e}`)}}assertPushed(e,t){let n=this.pushed(e);if(n.length===0)throw Error(`Expected job "${e}" to be pushed, but it was not.`);if(t){if(n.filter(t).length===0)throw Error(`Expected job "${e}" to be pushed matching the callback, but no matching jobs were found.`)}}assertPushedWithDelay(e,t){if(this.pushed(e).filter((i)=>i.options?.delay===t).length===0)throw Error(`Expected job "${e}" to be pushed with delay ${t}ms, but no matching jobs were found.`)}assertPushedOn(e,t){if(this.pushed(t).filter((r)=>r.queue===e).length===0)throw Error(`Expected job "${t}" to be pushed on queue "${e}", but it was not.`)}async processJob(e,t){let n=this.dispatchedJobs.find((r)=>r.name===e);if(!n)throw Error(`No dispatched job found with name "${e}"`);try{await t(n.data),this.processedJobs.push(n)}catch(r){throw this.failedJobs.push({job:n,error:r}),r}}processed(e){if(e)return this.processedJobs.filter((t)=>t.name===e);return[...this.processedJobs]}failed(e){if(e)return this.failedJobs.filter((t)=>t.job.name===e);return[...this.failedJobs]}reset(){this.dispatchedJobs=[],this.pushedJobs=[],this.processedJobs=[],this.failedJobs=[]}}function He(){return he=new jt,he}function Pt(){return he}function St(){return he!==null}function Le(){he=null}class Je{queue;constructor(){this.queue=He()}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(){Le()}}function Et(){return new Je}async function Tt(e,t){return await e.handle(t)}async function Rt(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:()=>mr,RedisJob:()=>qt,RedisQueue:()=>ne,StacksQueueManager:()=>Ct,batch:()=>ur,chain:()=>lr,createRedisDispatcher:()=>gr,dispatch:()=>dr,dispatchAfter:()=>hr,dispatchSync:()=>pr,getQueueManager:()=>fr,setQueueManager:()=>br});import{Queue as cr,batch as ur,chain as lr,dispatch as dr,dispatchAfter as hr,dispatchSync as pr,getQueueManager as fr,QueueManager as mr,setQueueManager as br}from"@stacksjs/bun-queue";import{log as We}from"@stacksjs/logging";class ne{queue;config;isProcessing=!1;constructor(e,t){this.config=t,this.queue=new cr(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}),We.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){We.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process(e,t),We.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 Ct{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 gr(e,t){let n=new ne(e,t);return async(r,i)=>n.add(r,i)}class qt{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 It={};M(It,{listDeadLetterJobs:()=>Ot,moveToDeadLetter:()=>Pe,purgeDeadLetterJobs:()=>Ft,retryDeadLetterJob:()=>At});import{db as re}from"@stacksjs/database";function je(){if(Nt)return;Nt=!0,console.warn("[queue/dlq] dead_letter_jobs table missing - DLQ disabled. Run migrations to enable poison-message isolation.")}async function Pe(e,t,n=1){let r=new Date().toISOString().slice(0,19).replace("T"," ");try{return await 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 je(),!1;throw i}}async function Ot(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 je(),[];throw t}}async function At(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 je(),!1;throw t}}async function Ft(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 je(),0;throw t}}var Nt=!1;var Se=()=>{};function ze(){if(Qt)return;Qt=!0,console.warn("[queue/idempotency] job_idempotency table missing - idempotency keys are accepted but NOT enforced. Run migrations to enable dedup.")}async function yr(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 ze(),!1;throw t}}async function vr(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)){ze();return}let i=r?.message??"";if(i.includes("UNIQUE constraint")||i.includes("Duplicate entry"))return;throw r}}async function Ve(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 ze(),"unenforced";let i=r?.message??"";if(i.includes("UNIQUE constraint")||i.includes("Duplicate entry"))return"duplicate";throw r}}async function Ke(e){try{let{db:t}=await import("@stacksjs/database");await t.deleteFrom("job_idempotency").where("idempotency_key","=",e).execute()}catch{}}var Qt=!1;var Ge=()=>{};var Wt={};M(Wt,{hashPayload:()=>be,isQuarantined:()=>Ee,listQuarantined:()=>Ut,quarantineJob:()=>Ht,recordFailureForPoison:()=>Mt,unquarantineJob:()=>Lt});import{createHash as _r}from"crypto";import{db as T}from"@stacksjs/database";function me(){if(Bt)return;Bt=!0,console.warn("[queue/poison] job_quarantine table missing - poison detection disabled. Run migrations to enable.")}function be(e){let t;try{t=typeof e==="string"?e:JSON.stringify(e??null)}catch{t=String(e)}return _r("sha256").update(t).digest("hex").slice(0,32)}async function Mt(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 Ee(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 Ht(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 Lt(e){try{await T.deleteFrom("job_quarantine").where("job_name","=",e).execute()}catch(t){if(_(t)){me();return}throw t}}async function Ut(){try{return await T.selectFrom("job_quarantine").whereNotNull("quarantined_at").selectAll().execute()??[]}catch(e){if(_(e))return me(),[];throw e}}var Bt=!1;var Te=()=>{};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 kr(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:xr(t.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}}async function Ye(e,t){let n=kr(e,t),{db:r}=await import("@stacksjs/database");await r.insertInto("jobs").values(n).execute()}function xr(e){let t=Date.now();return Math.floor(t/1000+e)}var Re=D(()=>{te()});var Q={};M(Q,{OnQueueEvent:()=>Kt,QueueEvents:()=>Ce,QueueMetrics:()=>qe,emitQueueEvent:()=>V,getGlobalMetrics:()=>De,getQueueEvents:()=>ie,getWorkerTracker:()=>ge,onQueueEvent:()=>zt,withEvents:()=>Vt});import{log as I}from"@stacksjs/logging";class Ce{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(!Xe)Xe=new Ce;return Xe}function zt(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 Vt(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 Kt(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 qe{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 De(){if(!Ze)Ze=new qe;return Ze}class Gt{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 Jr}var Xe=null,Ze=null,Jr;var R=D(()=>{Jr=new Gt});var U={};M(U,{Batch:()=>et,DispatchedBatch:()=>L,PendingBatch:()=>Ne,batchCounterIncrements:()=>Yt,batchRecordFromHash:()=>it,batchRecordToHash:()=>rn,getBatchCallbacks:()=>we,isBatchCancelled:()=>cn,recordBatchJobCompletion:()=>at,recordBatchJobFailure:()=>st});var{RedisClient:jr}=globalThis.Bun;import{log as k}from"@stacksjs/logging";import{env as Pr}from"@stacksjs/env";function q(){return Pr.QUEUE_DRIVER||"sync"}class Ne{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=q();await Er({id:e,name:this.options.name||"",total_jobs:t,pending_jobs:t,failed_jobs:0,failed_job_ids:"[]",options:JSON.stringify({queue:this.options.queue,allowFailures:this.options.allowFailures||!1}),cancelled_at:null,created_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:null,then_handler:this.options.thenHandler?JSON.stringify(this.options.thenHandler):null,catch_handler:this.options.catchHandler?JSON.stringify(this.options.catchHandler):null,finally_handler:this.options.finallyHandler?JSON.stringify(this.options.finallyHandler):null}),Sr(e,this.options);try{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 at(e)}catch(s){await st(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(q()==="redis")await Ir(this.id);else await Nr(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 Cr(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 qr(this.id),$e(this.id)}}class et{static create(e){return new Ne(e)}static async find(e){if(!await H(e))return null;return new L(e)}static async all(){return(await Tr()).map((t)=>new L(t.id))}static async prune(e=24){return Dr(e)}}function Sr(e,t){tt.set(e,t)}function we(e){return tt.get(e)}function $e(e){tt.delete(e)}async function Er(e){if(q()==="redis")await Ar(e);else await Xt(e)}async function H(e){if(q()==="redis")return Fr(e);return Zt(e)}async function Tr(){if(q()==="redis")return on();return en()}async function Rr(e,t){if(q()==="redis")await ot(e,t);else await nt(e,t)}async function Cr(e,t){if(t===0)return!0;if(q()==="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(Yt(r,t)).where("id","=",e).whereNull("cancelled_at").whereNull("finished_at").executeTakeFirst();return z(i)>0}function Yt(e,t){return{total_jobs:e`total_jobs + ${t}`,pending_jobs:e`pending_jobs + ${t}`}}async function qr(e){if(q()==="redis")await sn(e);else await tn(e)}async function Dr(e){if(q()==="redis")return Qr(e);return nn(e)}function $r(e){return!!(e.then_handler||e.catch_handler||e.finally_handler)}async function Xt(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(!$r(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 Zt(e){let{db:t}=await import("@stacksjs/database");return await t.selectFrom("job_batches").where("id","=",e).selectAll().executeTakeFirst()||null}async function en(){let{db:e}=await import("@stacksjs/database");return await e.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function nt(e,t){let{db:n}=await import("@stacksjs/database");await n.updateTable("job_batches").set(t).where("id","=",e).execute()}async function tn(e){let{db:t}=await import("@stacksjs/database");await t.deleteFrom("job_batches").where("id","=",e).execute()}async function Nr(e){await nt(e,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function nn(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 Or(){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 jr(await Or());return await e.connect(),e}function rn(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 Ar(e){try{let t=await G();await t.hset(`${K}${e.id}`,rn(e)),await t.sadd(rt,e.id),t.close()}catch{await Xt(e)}}function it(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 Fr(e){try{let t=await G(),n=`${K}${e}`,r=await t.hgetall(n);return t.close(),it(r)}catch{return Zt(e)}}async function on(){try{let e=await G(),t=await e.smembers(rt),n=[];for(let r of t){let i=it(await e.hgetall(`${K}${r}`));if(i)n.push(i)}return e.close(),n}catch{return en()}}async function ot(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 nt(e,t)}}async function an(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 sn(e){try{let t=await G();await t.del(`${K}${e}`),await t.srem(rt,e),t.close()}catch{await tn(e)}}async function Ir(e){let t=new Date().toISOString().slice(0,19).replace("T"," ");await ot(e,{cancelled_at:t,finished_at:t})}async function Qr(e){try{let t=await on(),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 sn(i.id),r++}return r}catch{return nn(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(),Oe));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 at(e){let t=q(),n=!1;if(t==="redis")n=await an(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){$e(e),k.info(`[Batch] Batch ${e} finished (record vanished)`);return}let a=JSON.parse(o.options||"{}"),s=!((o.failed_jobs||0)>0)||a.allowFailures;if(r){if(s)for(let c of r.thenCallbacks)try{await c(i)}catch(d){k.error(`[Batch] Error in then callback for batch ${e}:`,d)}for(let c of r.finallyCallbacks)try{await c(i)}catch(d){k.error(`[Batch] Error in finally callback for batch ${e}:`,d)}$e(e)}if(s){let c=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 st(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(q()==="redis"){l=await an(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 ot(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 Rr(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)}$e(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 cn(e){return(await H(e))?.cancelled_at!==null}var tt,K="stacks:batch:",rt="stacks:batches";var B=D(()=>{Re();tt=new Map});var Oe={};M(Oe,{Jobs:()=>hn,job:()=>dn,jobBatch:()=>pn,resolveJobFile:()=>fn,runJob:()=>Ae});import{appPath as Br,frameworkPath as Mr}from"@stacksjs/path";import{env as Hr}from"@stacksjs/env";import{enqueueAfterCommit as Lr,isInTransaction as Ur}from"@stacksjs/database";function Wr(){return Hr.QUEUE_DRIVER||"sync"}function zr(){if(un)return;un=!0,console.warn("[queue] .afterCommit() was called outside of `db.transaction(...)`. Dispatching immediately. Wrap the call in a transaction or drop .afterCommit() to silence this message.")}class O{name;payload;options={};txMode="auto";constructor(e,t){this.name=e;this.payload=t}onQueue(e){return this.options.queue=e,this}delay(e){return this.options.delay=e,this}tries(e){return this.options.tries=e,this}timeout(e){return this.options.timeout=e,this}backoff(e){return this.options.backoff=e,this}withContext(e){return this.options.context=e,this}withIdempotencyKey(e){return this.options.idempotencyKey=e,this}afterCommit(){return this.txMode="after",this}withoutCommit(){return this.txMode="immediate",this}async dispatch(){let{isFaked:e,getFakeQueue:t}=await Promise.resolve().then(() => Ue);if(e()){t()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(Ur()){let r=this.runDispatchPipeline.bind(this);if(Lr(async()=>{await r()}))return}else if(this.txMode==="after")zr()}await this.runDispatchPipeline()}async runDispatchPipeline(){let e=!1;if(this.options.idempotencyKey){let t=await Ve(this.options.idempotencyKey,this.name,this.options.queue);if(t==="duplicate")return;e=t==="claimed"}try{if(await Ee(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 Pe({queue:this.options.queue||"default",payload:F(n),exception:`quarantined: ${this.name}`},"poison-detected"))return}let t=Wr();if(t==="database")await this.dispatchToDatabase();else if(t==="redis")await this.dispatchToRedis();else if(t==="sync")await Ae(this.name,{payload:this.payload,context:this.options.context,traceId:await ln()});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 Ke(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 ln()),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 Ae(this.name,{payload:this.payload,context:this.options.context})}}function dn(e,t){return new O(e,t)}function pn(e){let{PendingBatch:t}=(B(),er(U));return new t(e)}async function fn(e){let t=[Br(`Jobs/${e}.ts`),Mr(`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 Ae(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 fn(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"){let{runAction:l}=await import("@stacksjs/actions");await l(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 ln(){try{let{getTraceId:e}=await import("@stacksjs/router");return e()}catch{return}}var un=!1,hn;var ye=D(()=>{Se();te();Ge();Te();hn={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()}}});var Fe={};M(Fe,{isCircuitOpen:()=>bn,listCircuitState:()=>_n,pauseQueue:()=>yn,recordCircuitFailure:()=>wn,recordCircuitSuccess:()=>gn,resumeQueue:()=>vn});import{db as S}from"@stacksjs/database";function Y(){if(mn)return;mn=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing - circuit breaker disabled. Run migrations to enable.")}async function ut(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 bn(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 gn(e,t={}){let n=t.windowSeconds??300,r=new Date().toISOString().slice(0,19).replace("T"," "),i=await ut(e,r);if(!i)return;try{if(ct(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 wn(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 ut(e,l);if(!s)return!1;if(s.paused_at)return!1;try{let{success_count:u,failure_count:c}=s;if(ct(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:ct(s.window_start,r)?l:s.window_start}).where("queue_name","=",e).execute(),!1}catch(u){if(_(u))return Y(),!1;throw u}}function ct(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 yn(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 ut(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 vn(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 _n(){try{return await S.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch(e){if(_(e))return Y(),[];throw e}}var mn=!1;var ve=()=>{};te();import{env as wr}from"@stacksjs/env";function Dt(){return wr.QUEUE_DRIVER||"sync"}class $t{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(() => Ue);if(t()){n()?.dispatch(this.name||"UnknownJob",e,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let r=Dt();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=Dt();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"){let{runAction:t}=await import("@stacksjs/actions");await t(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})}}ye();Ge();Se();Te();ve();te();var lt=(e)=>`__job_progress__:${e}`,dt=(e)=>`__job_cancel__:${e}`;function Vr(e){return Number.isFinite(e)?Math.max(0,Math.min(100,e)):0}async function Kr(e,t,n){let{cache:r}=await import("@stacksjs/cache");await r.set(lt(e),{percent:Vr(t),message:n,updatedAt:Date.now()},3600)}async function Gr(e){let{cache:t}=await import("@stacksjs/cache");return await t.get(lt(e))??null}async function Yr(e){let{cache:t}=await import("@stacksjs/cache");await t.set(dt(e),1,3600)}async function Xr(e){let{cache:t}=await import("@stacksjs/cache");return Boolean(await t.get(dt(e)))}async function Zr(e){let{cache:t}=await import("@stacksjs/cache");await Promise.all([t.del(lt(e)),t.del(dt(e))])}import{log as se}from"@stacksjs/logging";import*as kn from"@stacksjs/path";class xn{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 xn;async function ht(e){let t=e||kn.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 ei(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 ei(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 ti(e){return X.get(e)}function ni(){return X.all()}function pt(){return X.scheduled()}async function ri(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 ii(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 jn}from"@stacksjs/logging";var Jn=!1;async function Pn(){if(Jn)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(),Jn=!0,!0}catch(e){return jn.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${e instanceof Error?e.message:String(e)}`),!1}}async function Sn(e){if(!await Pn())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 En(e,t){if(!await Pn())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 oi(e){return`%"jobName":"${e.replace(/[\\%_]/g,(n)=>`\\${n}`)}"%`}async function Tn(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",[oi(e)]).execute(),i=Array.isArray(r)?r:r?.rows??[];return Array.isArray(i)&&i.length>0}catch(t){return jn.debug(`[scheduler] overlap check unavailable, dispatching anyway: ${t instanceof Error?t.message:String(t)}`),!1}}Re();var $n={checkInterval:60000,preventOverlapping:!0},y={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...$n}},Rn=new Set;function ai(e,t){if(Rn.has(e))return;Rn.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 Cn=!1,qn=new Map;function si(e){let t=qn.get(e);if(!t)t=new Intl.DateTimeFormat("en-US",{timeZone:e,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),qn.set(e,t);return t}function ft(e,t){if(t&&t!=="local"&&t!=="system")try{let n=si(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(!Cn)Cn=!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 ci(e,t,n){let r=ft(new Date,n),i=r.minute,o=r.hour,a=r.day,l=r.month,s=r.dayOfWeek;if(t){let p=ft(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!=="*")ai(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 A(c,i,0,59)&&A(d,o,0,23)&&A(w,a,1,31)&&A(f,l,1,12)&&A(g,s,0,6)}function A(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 On(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=ft(w,t);if(A(i,f.minute,0,59)&&A(o,f.hour,0,23)&&A(a,f.day,1,31)&&A(l,f.month,1,12)&&A(s,f.dayOfWeek,0,6))return w}return null}async function ui(e={}){if(y.isRunning){J.warn("Scheduler is already running");return}y.config={...$n,...e},y.isRunning=!0,y.isShuttingDown=!1,await ht();let t=pt();for(let i of t){let o=i.config.rate||i.config.schedule;if(o){let a=Nn(o);if(a){let l=await Sn(i.name);y.jobs.set(i.name,{job:i,lastRun:l,nextRun:On(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",()=>mt()),process.on("SIGTERM",()=>mt());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,Dn().catch((a)=>J.error("Scheduler check failed:",a)).finally(()=>{n=!1});r()},o),y.checkInterval?.unref?.()};r(),await Dn()}async function Dn(){for(let[e,t]of y.jobs){let n=t.job.config.rate||t.job.config.schedule;if(!n)continue;let r=Nn(n);if(!r)continue;if(ci(r,t.lastRun,y.config.timezone)){if((y.config.preventOverlapping||t.job.config.withoutOverlapping)&&await Tn(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=On(r,y.config.timezone),await En(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 Ye(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 mt(){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 li(){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 di(){return y.isRunning}function hi(){return new Map(y.jobs)}async function pi(e){let t=y.jobs.get(e);if(!t)throw Error(`Scheduled job "${e}" not found`);J.info(`Manually triggering scheduled job: ${e}`),await Ye(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 fi}from"@stacksjs/logging";function An(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 mi={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function bt(e={}){let t={...mi,...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=An(m,i);if(E==="processing")P.processing++;else if(E==="delayed")P.delayed++;else if(P.pending++,m.created_at){let Kn=typeof m.created_at==="number"?m.created_at:Math.floor(new Date(m.created_at).getTime()/1000),kt=i-Kn;if(!P.oldestAge||kt>P.oldestAge)P.oldestAge=kt}}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 vt=De().getMetrics(),Wn=vt.throughputPerMinute,zn=vt.averageDuration,_t=f+g+p+x,le=_t>0?x/_t: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 Vn=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:Vn,metrics:{totalPending:f,totalProcessing:g,totalDelayed:p,totalFailed:x,throughputPerMinute:Wn,averageProcessingTime:zn,errorRate:le},alerts:n}}catch(o){fi.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 bi(e={}){return async(t)=>{let n=await bt(e),r=n.status==="healthy"?200:n.status==="degraded"?207:503;return new Response(JSON.stringify(n,null,2),{status:r,headers:{"Content-Type":"application/json","Cache-Control":"no-store"}})}}async function gi(e={}){return(await bt(e)).status==="healthy"}import{log as C}from"@stacksjs/logging";var wi={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function _e(e){return String(e).replace(/[&<>"']/g,(t)=>wi[t]??t)}class gt{config;notificationCount=0;lastResetTime=Date.now();pendingBatch=[];batchTimeout=null;activeFlush=null;constructor(e){this.config=e}async notify(e){if(this.config.filter&&!this.config.filter(e))return;if(this.config.rateLimit){let t=Date.now();if(t-this.lastResetTime>3600000)this.notificationCount=0,this.lastResetTime=t;if(this.notificationCount>=this.config.rateLimit){C.debug("Rate limit reached for failed job notifications");return}}if(this.config.batch){if(this.pendingBatch.push(e),!this.batchTimeout)this.batchTimeout=setTimeout(()=>{this.batchTimeout=null,this.activeFlush=this.flushBatch().catch((t)=>C.error("Failed to flush notification batch:",t)).finally(()=>{this.activeFlush=null})},this.config.batchInterval||60000),this.batchTimeout.unref?.();return}await this.sendNotifications([e])}async flushBatch(){if(this.pendingBatch.length===0)return;let e=[...this.pendingBatch];if(this.pendingBatch=[],this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;await this.sendNotifications(e)}async shutdown(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.activeFlush)await this.activeFlush.catch(()=>{});await this.flushBatch().catch((e)=>C.error("Failed to flush notification batch on shutdown:",e))}async sendNotifications(e){let t=[];for(let n of this.config.channels)switch(n){case"email":if(this.config.email)t.push(this.sendEmail(e));break;case"slack":if(this.config.slack)t.push(this.sendSlack(e));break;case"discord":if(this.config.discord)t.push(this.sendDiscord(e));break;case"webhook":if(this.config.webhook)t.push(this.sendWebhook(e));break;case"log":this.logFailures(e);break}try{await Promise.all(t)}catch(n){C.error("Failed to send job failure notifications:",n)}finally{this.notificationCount+=e.length}}async sendEmail(e){let t=this.config.email,n=t.subject||`[Queue] ${e.length} Job(s) Failed`,r=this.formatEmailBody(e);try{let{mail:i}=await import("@stacksjs/email");await i.send({to:Array.isArray(t.to)?t.to:[t.to],from:t.from?{address:t.from}:void 0,subject:n,html:r})}catch(i){C.error("Failed to send email notification:",i)}}formatEmailBody(e){let t=e.map((n)=>`
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)=>`
3
3
  <tr>
4
4
  <td style="padding: 8px; border: 1px solid #ddd;">${_e(String(n.id))}</td>
5
5
  <td style="padding: 8px; border: 1px solid #ddd;">${_e(n.name)}</td>
@@ -28,4 +28,4 @@ var{defineProperty:Ie,getOwnPropertyNames:Gn,getOwnPropertyDescriptor:Yn}=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 yi(e){return ke=new gt(e),ke}function vi(){return ke}async function _i(e){if(ke)await ke.notify(e)}B();te();Re();import{err as ki,ok as Fn}from"@stacksjs/error-handling";import{log as b}from"@stacksjs/logging";import ce from"process";import{env as Ji}from"@stacksjs/env";var In=!1;function xi(){if(In)return;In=!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,Qn=new Map;function Hn(e){return Z.add(e),e.finally(()=>Z.delete(e)),e}function ji(){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 Pi(){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 Si(e,t,n=Math.random){if(!(e>0)||t<=0)return e;return Math.round(e+e*t*n())}function Ei(){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 Bn(){let e=ji(),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 Ti(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 Ri(){return Ji.QUEUE_DRIVER||"sync"}async function Ci(e,t={}){try{b.info("Starting queue processor..."),xi(),ue=!0,j=`worker-${ce.pid}-${Date.now()}`;let n=t.concurrency||1,r=Ri(),{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 Ii(e||"default",n),Fn(void 0);let a;if(e)a=[e];else if(a=await Ln(),a.length===0)a=["default"];return b.info(`Processing queues: ${a.join(", ")}`),await qi(a,n),Fn(void 0)}catch(n){return ue=!1,ki(n)}}async function Ln(){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 qi(e,t){b.info("Listening for jobs...");let n=e,r=Date.now(),i=1e4,o=Date.now(),a=Ei();await Bn();while(ue)try{let l=Date.now();if(l-r>i){try{let s=await Ln();if(s.length>0)n=s}catch{}r=l}if(l-o>a)await Bn(),o=l;for(let s of n){try{let{isCircuitOpen:c}=await Promise.resolve().then(() => (ve(),Fe));if(await c(s))continue}catch{}let u=[];try{u=await Di(s,t)}catch(c){let d=Date.now(),w=Qn.get(s)??0;if(d-w>60000)Qn.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 Hn($i(c))}catch{b.error(`Unexpected error processing job ${c.id}`)}}))}await yt(1000)}catch{await yt(3000)}}async function Di(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 $i(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 wt(t),W--,a.markIdle(j);return}}}catch{}let s=null;try{let u=JSON.parse(e.payload||"{}"),c=Ai(u);if(c===void 0)await Mn(u);else await Fi(Mn(u),c*1000,`Job ${t} exceeded ${c}s timeout`)}catch(u){s=u instanceof Error?u:Error(String(u))}if(!s)try{await wt(t),b.info(`[Queue] Job ${t} completed`),a.recordCompletion(j);try{let{recordCircuitSuccess:u}=await Promise.resolve().then(() => (ve(),Fe));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(() => (Se(),It));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 Oi(e,s,{attempts:w,maxAttempts:c,durationMs:Date.now()-r})}catch{f=!1}}if(f)try{await wt(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(() => (Te(),Wt)),{recordCircuitFailure:p}=await Promise.resolve().then(() => (ve(),Fe)),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=Si(g,Pi()),b.info(`[Queue] Job ${t} will be retried in ${g}s (attempt ${w}/${c})`);try{await Ni(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 wt(e){let{db:t}=await import("@stacksjs/database");await t.deleteFrom("jobs").where("id","=",e).execute()}async function Ni(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 Oi(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 Ai(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 Fi(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 Mn(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(),Oe));await n(t.envelope.jobName,{payload:t.envelope.payload,traceId:t.envelope.traceId})}async function Ii(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(),Oe));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}`),Ti(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)=>Hn(u(c))),b.info(`Listening for Redis jobs on queue "${e}" with concurrency ${t}...`);while(ue)await yt(1000);await o.close()}async function Qi(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 Bi(){let{db:e}=await import("@stacksjs/database"),t=await e.selectFrom("failed_jobs").selectAll().execute();for(let n of t)await Un(Number(n.id))}async function Un(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 Mi(){return W}function Hi(){return ue}function yt(e){return new Promise((t)=>setTimeout(t,e))}async function zo(){let{RedisQueue:e}=await Promise.resolve().then(() => (fe(),pe));return e}export{et as Batch,L as DispatchedBatch,gt as FailedJobNotifier,rr as JOB_ENVELOPE_VERSION,$t as Job,hn as Jobs,Kt as OnQueueEvent,Ne as PendingBatch,Ce as QueueEvents,qe as QueueMetrics,Je as QueueTester,de as assertEnvelopeSerializable,Yr as cancelJob,bt as checkQueueHealth,Ve as claimDispatchKey,sr as clearEnvelopeWarnings,Zr as clearJobState,yi as configureFailedJobNotifications,N as createEnvelope,bi as createHealthCheckHandler,Et as createQueueTester,ht as discoverJobs,V as emitQueueEvent,Bi as executeFailedJobs,ri as executeJob,Rt as expectJobToFail,He as fake,Mi as getActiveJobCount,ni as getAllJobs,we as getBatchCallbacks,vi as getFailedJobNotifier,Pt as getFakeQueue,De as getGlobalMetrics,ti as getJob,Gr as getJobProgress,ie as getQueueEvents,zo as getRedisQueue,hi as getRegisteredJobs,pt as getScheduledJobs,li as getSchedulerStatus,ge as getWorkerTracker,yr as hasDispatchedKey,be as hashPayload,cn as isBatchCancelled,bn as isCircuitOpen,St as isFaked,Xr as isJobCancelled,Ee as isQuarantined,gi as isQueueHealthy,di as isSchedulerRunning,Hi as isWorkerRunning,dn as job,pn as jobBatch,X as jobRegistry,_n as listCircuitState,Ot as listDeadLetterJobs,Ut as listQuarantined,Pe as moveToDeadLetter,_i as notifyJobFailed,zt as onQueueEvent,xe as parseEnvelope,yn as pauseQueue,Ft as purgeDeadLetterJobs,Ht as quarantineJob,An as queuedJobState,at as recordBatchJobCompletion,st as recordBatchJobFailure,wn as recordCircuitFailure,gn as recordCircuitSuccess,vr as recordDispatchedKey,Mt as recordFailureForPoison,Ke as releaseDispatchKey,Le as restore,vn as resumeQueue,At as retryDeadLetterJob,Un as retryFailedJob,Ae as runJob,Tt as runTestJob,F as serializeEnvelope,Kr as setJobProgress,Ci as startProcessor,ui as startScheduler,Qi as stopProcessor,mt as stopScheduler,ii as toJobOptions,pi as triggerJob,Lt as unquarantineJob,Vt 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 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};
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.3",
5
+ "version": "0.74.5",
6
6
  "description": "The Stacks Queue system powered by bun-queue.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -63,19 +63,29 @@
63
63
  "prepublishOnly": "bun run build"
64
64
  },
65
65
  "dependencies": {
66
- "@stacksjs/actions": "0.74.3",
66
+ "@stacksjs/action-runner": "0.74.5",
67
67
  "@stacksjs/bun-queue": "^0.1.8",
68
- "@stacksjs/cache": "0.74.3",
69
- "@stacksjs/config": "0.74.3",
70
- "@stacksjs/database": "0.74.3",
71
- "@stacksjs/email": "0.74.3",
72
- "@stacksjs/env": "0.74.3",
73
- "@stacksjs/error-handling": "0.74.3",
74
- "@stacksjs/logging": "0.74.3",
75
- "@stacksjs/path": "0.74.3",
76
- "@stacksjs/router": "0.74.3"
68
+ "@stacksjs/cache": "0.74.5",
69
+ "@stacksjs/config": "0.74.5",
70
+ "@stacksjs/database": "0.74.5",
71
+ "@stacksjs/env": "0.74.5",
72
+ "@stacksjs/error-handling": "0.74.5",
73
+ "@stacksjs/logging": "0.74.5",
74
+ "@stacksjs/path": "0.74.5"
77
75
  },
78
76
  "devDependencies": {
79
77
  "better-dx": "^0.2.24"
78
+ },
79
+ "peerDependencies": {
80
+ "@stacksjs/email": "0.74.5",
81
+ "@stacksjs/router": "0.74.5"
82
+ },
83
+ "peerDependenciesMeta": {
84
+ "@stacksjs/email": {
85
+ "optional": true
86
+ },
87
+ "@stacksjs/router": {
88
+ "optional": true
89
+ }
80
90
  }
81
91
  }