@stacksjs/queue 0.70.352 → 0.70.354

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/batch.d.ts CHANGED
@@ -1,5 +1,18 @@
1
1
  import type { Job } from './action';
2
2
  export declare function getBatchCallbacks(batchId: string): BatchOptions | undefined;
3
+ /**
4
+ * The SET payload that adds `delta` to both batch counters.
5
+ *
6
+ * Split out from {@link incrementBatchCounters} purely so the shape is
7
+ * assertable. The thing worth testing is that these are RELATIVE expressions
8
+ * evaluated by the database rather than absolute numbers this process worked
9
+ * out, and checking that through the real call would mean mocking
10
+ * `@stacksjs/database`. `mock.module` is process-global and bun runs a
11
+ * package's test files in one process, so mocking a module this widely used
12
+ * takes out every other test in the package (it dropped the queue suite from
13
+ * 299 passing to 202 when tried).
14
+ */
15
+ export declare function batchCounterIncrements(sql: (strings: TemplateStringsArray, ...values: unknown[]) => unknown, delta: number): { total_jobs: unknown, pending_jobs: unknown };
3
16
  /**
4
17
  * Encode a {@link BatchRecord} as a `stacks:batch:*` hash.
5
18
  *
@@ -3,6 +3,82 @@
3
3
  * call this — single source of truth for the on-the-wire shape.
4
4
  */
5
5
  export declare function createEnvelope(jobName: string, payload: unknown, options?: JobEnvelopeOptions, traceId?: string): JobEnvelope;
6
+ /**
7
+ * JSON round-trip contract — what a payload is worth on the other side
8
+ * (stacksjs/stacks#2282, item 6).
9
+ *
10
+ * The envelope travels as JSON and only as JSON: the database driver writes it
11
+ * into `jobs.payload` as text, and the redis driver hands it to bun-queue,
12
+ * which stringifies it on the way into redis. A payload is therefore subject
13
+ * to JSON's type system rather than JavaScript's, and the worker on the far
14
+ * side revives nothing. Precisely what that costs:
15
+ *
16
+ * - `undefined` — the property is DROPPED. `{ a: 1, b: undefined }` arrives
17
+ * as `{ a: 1 }`, so `'b' in payload` flips from true to false. Inside an
18
+ * array it becomes `null` instead: `[1, undefined]` → `[1, null]`.
19
+ * - `Date` — becomes its ISO 8601 STRING (via `Date.prototype.toJSON`) and
20
+ * stays a string; a handler calling `.getTime()` on it throws. Send
21
+ * `date.toISOString()` or an epoch number on purpose and parse it in the
22
+ * handler, so the conversion is visible at both ends.
23
+ * - `Map` / `Set` — become `{}`. Their contents are gone, silently. Send an
24
+ * array or a plain object.
25
+ * - class instances — arrive as plain objects: own enumerable properties
26
+ * only, no prototype, so methods and `instanceof` are gone. Model
27
+ * instances especially should never be a payload; send the id.
28
+ * - functions and symbol values — dropped exactly like `undefined`. Symbol
29
+ * keys are ignored outright.
30
+ * - `NaN`, `Infinity`, `-Infinity` — become `null`. `-0` becomes `0`.
31
+ * - numbers — IEEE-754 doubles. Integers past 2^53 lose precision with no
32
+ * error whatsoever, which is the trap `BigInt` exists to avoid and the
33
+ * reason large ids are safer sent as strings.
34
+ * - `BigInt` — THROWS. Guarded by `serializeEnvelope` below rather than left
35
+ * to blow up from inside `JSON.stringify`.
36
+ * - circular references — THROW, with no fix but restructuring the payload.
37
+ * - anything with a `toJSON()` — is whatever `toJSON()` returns. That is how
38
+ * `Date` becomes a string, and it applies to your own classes too.
39
+ *
40
+ * Everything else — strings, booleans, `null`, finite numbers, arrays, plain
41
+ * objects, and key order within them — survives unchanged.
42
+ *
43
+ * One trap worth naming: `QUEUE_DRIVER=sync` runs the handler in-process and
44
+ * never serializes anything, so a payload that violates any of the above works
45
+ * perfectly in development and changes shape (or throws) the moment the app
46
+ * runs against `database` or `redis`.
47
+ *
48
+ * Rule of thumb: a payload should be data you would be content to read in a
49
+ * log line. Send ids, not objects. That is also what keeps a job replayable
50
+ * from the dead-letter queue hours after the things it referred to changed.
51
+ */
52
+ /**
53
+ * Serialize an envelope for the wire, turning JSON's two hard failures into
54
+ * errors that say what to do about them.
55
+ *
56
+ * On a `BigInt` this FAILS rather than coerces, deliberately. The alternatives
57
+ * were to stringify it (`"9007199254740993"`), which is silently lossy in the
58
+ * *type* — the worker gets a string, `typeof` flips, and arithmetic in the
59
+ * handler either concatenates or throws, in another process, with nothing
60
+ * pointing back at the dispatch that caused it — or to invent a tagged wrapper
61
+ * (`{ $bigint: '…' }`), which is a private serialization format that every
62
+ * reader of the `jobs` table and every non-Stacks consumer of the redis queue
63
+ * would then have to know about. Both trade a loud, local, fixable failure for
64
+ * a quiet, remote, strange one. The caller knows whether `String()` or
65
+ * `Number()` is the right conversion for their value; the framework does not.
66
+ */
67
+ export declare function serializeEnvelope(envelope: JobEnvelope): string;
68
+ /**
69
+ * Check an envelope against the contract above without keeping the JSON.
70
+ *
71
+ * For the driver that serializes the envelope somewhere we cannot see: the
72
+ * redis driver hands the object to bun-queue, which stringifies it several
73
+ * layers down, so without this the failure surfaces there as the bare
74
+ * TypeError, naming neither the job nor the property.
75
+ *
76
+ * Callers run this BEFORE they acquire anything (stacksjs/stacks#2282 item 6).
77
+ * A guard placed after the queue was constructed still produced the better
78
+ * message, but only once a connection had been opened for a job that was never
79
+ * going to be enqueued.
80
+ */
81
+ export declare function assertEnvelopeSerializable(envelope: JobEnvelope): void;
6
82
  /**
7
83
  * Test-only: clear the warn-once state. Production callers never
8
84
  * need this.
@@ -51,6 +127,10 @@ export declare function parseEnvelope(raw: unknown): ParsedEnvelope;
51
127
  * is logged and skipped (so a rolling deploy with a newer-shape envelope
52
128
  * doesn't crash old workers; they leave the row for the new workers to
53
129
  * pick up).
130
+ *
131
+ * Wire format: JSON, with everything that implies for a payload. What
132
+ * survives a round trip, what is silently changed on the way, and what throws
133
+ * is documented under "JSON round-trip contract" above `serializeEnvelope`.
54
134
  */
55
135
  /**
56
136
  * Current envelope version. Bump when you change the shape of
package/dist/events.d.ts CHANGED
@@ -32,17 +32,50 @@ export declare function withEvents<T extends (...args: any[]) => Promise<any>>(q
32
32
  /**
33
33
  * Queue event listener decorator
34
34
  *
35
+ * Subscribes the decorated **instance method** to `event` on the global queue
36
+ * events instance once per instance, with `this` bound to that instance — creating
37
+ * N listener objects means N subscriptions, exactly as if every constructor had
38
+ * called `onQueueEvent(event, this.method.bind(this))` by hand. A class that is
39
+ * never instantiated never receives events.
40
+ *
41
+ * ## Something else must hold the listener
42
+ *
43
+ * The global emitter holds listeners **weakly** (see
44
+ * `QueueEvents.subscribeListener`), because it is process-global and would
45
+ * otherwise become the permanent owner of every listener ever constructed
46
+ * (stacksjs/stacks#2282 item 7). The listener therefore has to be reachable from
47
+ * somewhere else — a module-level binding, a service container, a registry.
48
+ * `new EmailNotifications()` as a bare statement is garbage the moment it
49
+ * finishes, and a collected listener stops receiving events.
50
+ *
51
+ * For deterministic teardown call `getQueueEvents().unsubscribeListener(instance)`;
52
+ * `getQueueEvents().off(event)` still clears an event wholesale.
53
+ *
54
+ * ## Instance methods only
55
+ *
56
+ * Static methods, fields, accessors and whole classes are rejected with a
57
+ * `TypeError` at class-definition time — see the guards below for why.
58
+ *
35
59
  * @example
36
60
  * ```typescript
37
61
  * class EmailNotifications {
62
+ * private completed = 0
63
+ *
38
64
  * @OnQueueEvent('job:completed')
39
65
  * async onJobCompleted(payload: QueueEventPayload) {
40
- * console.log('Job completed:', payload)
66
+ * this.completed++ // `this` is the EmailNotifications instance
41
67
  * }
42
68
  * }
69
+ *
70
+ * // Subscribing happens on `new`, not at class definition — and this binding is
71
+ * // what keeps the listener, and therefore its subscription, alive.
72
+ * export const emailNotifications = new EmailNotifications()
43
73
  * ```
44
74
  */
45
- export declare function OnQueueEvent(event: QueueEventType): void;
75
+ export declare function OnQueueEvent(event: QueueEventType): (
76
+ value: any,
77
+ context: DecoratorContext | string,
78
+ ) => any;
46
79
  /**
47
80
  * Get or create the global QueueMetrics instance
48
81
  */
@@ -104,6 +137,9 @@ export type QueueEventHandler = (_payload: QueueEventPayload) => void | Promise<
104
137
  */
105
138
  export declare class QueueEvents {
106
139
  on(event: QueueEventType, handler: QueueEventHandler): () => void;
140
+ subscribeListener(listener: object, event: QueueEventType, method: QueueEventHandler): () => void;
141
+ unsubscribeListener(listener: object): number;
142
+ listenerCount(event: QueueEventType | '*'): number;
107
143
  onAny(handler: (event: QueueEventType, payload: QueueEventPayload) => void | Promise<void>): () => void;
108
144
  once(event: QueueEventType, handler: QueueEventHandler): () => void;
109
145
  emit(event: QueueEventType, payload: Omit<QueueEventPayload, 'timestamp'>): Promise<void>;
package/dist/index.d.ts CHANGED
@@ -60,12 +60,20 @@ export {
60
60
  // Single shape across database + redis drivers; back-compat parser
61
61
  // handles the pre-#1884 implicit-v0 shape and the Laravel-import
62
62
  // legacy `{ job, data }` form.
63
+ //
64
+ // `serializeEnvelope` / `assertEnvelopeSerializable` are part of the surface
65
+ // too (stacksjs/stacks#2282 item 6): the JSON round-trip contract is a rule
66
+ // application code has to keep, so it needs a way to ask whether a payload
67
+ // keeps it. Without them exported the only way to find out was to dispatch and
68
+ // see, which on the redis driver meant finding out from inside bun-queue.
63
69
  // =============================================================================
64
70
  export {
71
+ assertEnvelopeSerializable,
65
72
  clearEnvelopeWarnings,
66
73
  createEnvelope,
67
74
  JOB_ENVELOPE_VERSION,
68
75
  parseEnvelope,
76
+ serializeEnvelope,
69
77
  } from './envelope';
70
78
  // =============================================================================
71
79
  // Per-job progress + cancellation (cache-backed; safe across processes)
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  // @bun
2
- var{defineProperty:j1,getOwnPropertyNames:p2,getOwnPropertyDescriptor:g2}=Object,u2=Object.prototype.hasOwnProperty;function b2($){return this[$]}var l2=($)=>{var L=(D4??=new WeakMap).get($),V;if(L)return L;if(L=j1({},"__esModule",{value:!0}),$&&typeof $==="object"||typeof $==="function"){for(var Y of p2($))if(!u2.call(L,Y))j1(L,Y,{get:b2.bind($,Y),enumerable:!(V=g2($,Y))||V.enumerable})}return D4.set($,L),L},D4;var c2=($)=>$;function i2($,L){this[$]=c2.bind(null,L)}var j=($,L)=>{for(var V in L)j1($,V,{get:L[V],enumerable:!0,configurable:!0,set:i2.bind(L,V)})};var y=($,L)=>()=>($&&(L=$($=0)),L);var F=import.meta.require;function m($,L,V,Y){return{jobName:$,payload:L,options:V,envelopeVersion:1,dispatchedAt:new Date().toISOString(),...Y?{traceId:Y}:{}}}function A4($,L){if(m1.has($))return;m1.add($),console.warn(L)}function d2(){m1.clear()}function B1($){let L;if(typeof $==="string")try{L=JSON.parse($)}catch{return{ok:!1,reason:"malformed",detail:"not valid JSON"}}else if($&&typeof $==="object")L=$;else return{ok:!1,reason:"malformed",detail:`expected string or object, got ${typeof $}`};if(L.envelopeVersion===1){if(typeof L.jobName!=="string")return{ok:!1,reason:"missing-job-name"};return{ok:!0,envelope:{jobName:L.jobName,payload:L.payload,options:L.options??void 0,envelopeVersion:1,dispatchedAt:typeof L.dispatchedAt==="string"?L.dispatchedAt:new Date(0).toISOString(),...typeof L.traceId==="string"&&L.traceId?{traceId:L.traceId}:{}},source:"v1"}}if(typeof L.envelopeVersion==="number"&&L.envelopeVersion>1)return{ok:!1,reason:"unknown-version",detail:`envelopeVersion=${L.envelopeVersion}, this worker speaks v1`};if(typeof L.jobName==="string")return A4("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:L.jobName,payload:L.payload,options:L.options??void 0,envelopeVersion:1,dispatchedAt:typeof L.dispatchedAt==="string"?L.dispatchedAt:new Date(0).toISOString(),...typeof L.traceId==="string"&&L.traceId?{traceId:L.traceId}:{}},source:"v0-implicit"};if(typeof L.job==="string")return A4("laravel-legacy","[queue/envelope] Processing Laravel-legacy job envelope (`{ job, data }` shape). "+"Will continue to process but the queue table contains migration-era rows \u2014 "+"consider flushing once they drain."),{ok:!0,envelope:{jobName:L.job.replace(/^App\\+Jobs\\+/,"").replace(/^.*\\/,""),payload:L.data,options:void 0,envelopeVersion:1,dispatchedAt:new Date(0).toISOString()},source:"laravel-legacy"};return{ok:!1,reason:"missing-job-name"}}var n2=1,m1;var U1=y(()=>{m1=new Set});var g1={};j(g1,{runJob:()=>T4,restore:()=>p1,isFaked:()=>B4,getFakeQueue:()=>M4,fake:()=>h1,expectJobToFail:()=>S4,createQueueTester:()=>E4,QueueTester:()=>E1});class P4{dispatchedJobs=[];pushedJobs=[];processedJobs=[];failedJobs=[];dispatch($,L,V={}){this.dispatchedJobs.push({name:$,data:L,options:V,dispatchedAt:new Date,queue:V.queue||"default"})}push($,L,V={}){this.pushedJobs.push({name:$,data:L,options:V,dispatchedAt:new Date,queue:V.queue||"default"})}dispatched($){if($)return this.dispatchedJobs.filter((L)=>L.name===$);return[...this.dispatchedJobs]}pushed($){if($)return this.pushedJobs.filter((L)=>L.name===$);return[...this.pushedJobs]}assertDispatched($,L){let V=this.dispatched($);if(V.length===0)throw Error(`Expected job "${$}" to be dispatched, but it was not.`);if(L){if(V.filter(L).length===0)throw Error(`Expected job "${$}" to be dispatched matching the callback, but no matching jobs were found.`)}}assertNotDispatched($){let L=this.dispatched($);if(L.length>0)throw Error(`Expected job "${$}" to not be dispatched, but it was dispatched ${L.length} time(s).`)}assertDispatchedTimes($,L){let V=this.dispatched($);if(V.length!==L)throw Error(`Expected job "${$}" to be dispatched ${L} time(s), but it was dispatched ${V.length} time(s).`)}assertNothingDispatched(){if(this.dispatchedJobs.length>0){let $=[...new Set(this.dispatchedJobs.map((L)=>L.name))].join(", ");throw Error(`Expected no jobs to be dispatched, but found: ${$}`)}}assertPushed($,L){let V=this.pushed($);if(V.length===0)throw Error(`Expected job "${$}" to be pushed, but it was not.`);if(L){if(V.filter(L).length===0)throw Error(`Expected job "${$}" to be pushed matching the callback, but no matching jobs were found.`)}}assertPushedWithDelay($,L){if(this.pushed($).filter((G)=>G.options.delay===L).length===0)throw Error(`Expected job "${$}" to be pushed with delay ${L}ms, but no matching jobs were found.`)}assertPushedOn($,L){if(this.pushed(L).filter((Y)=>Y.queue===$).length===0)throw Error(`Expected job "${L}" to be pushed on queue "${$}", but it was not.`)}async processJob($,L){let V=this.dispatchedJobs.find((Y)=>Y.name===$);if(!V)throw Error(`No dispatched job found with name "${$}"`);try{await L(V.data),this.processedJobs.push(V)}catch(Y){throw this.failedJobs.push({job:V,error:Y}),Y}}processed($){if($)return this.processedJobs.filter((L)=>L.name===$);return[...this.processedJobs]}failed($){if($)return this.failedJobs.filter((L)=>L.job.name===$);return[...this.failedJobs]}reset(){this.dispatchedJobs=[],this.pushedJobs=[],this.processedJobs=[],this.failedJobs=[]}}function h1(){return X1=new P4,X1}function M4(){return X1}function B4(){return X1!==null}function p1(){X1=null}class E1{queue;constructor(){this.queue=h1()}dispatch($,L,V={}){return this.queue.dispatch($,L,V),this}push($,L,V={}){return this.queue.push($,L,V),this}assertDispatched($,L){return this.queue.assertDispatched($,L),this}assertNotDispatched($){return this.queue.assertNotDispatched($),this}assertDispatchedTimes($,L){return this.queue.assertDispatchedTimes($,L),this}assertNothingDispatched(){return this.queue.assertNothingDispatched(),this}dispatched($){return this.queue.dispatched($)}reset(){return this.queue.reset(),this}cleanup(){p1()}}function E4(){return new E1}async function T4($,L){return await $.handle(L)}async function S4($,L,V){try{throw await $.handle(L),Error("Expected job to fail, but it succeeded")}catch(Y){if(Y.message==="Expected job to fail, but it succeeded")throw Y;if(V){let G=Y.message;if(typeof V==="string"){if(!G.includes(V))throw Error(`Expected error to contain "${V}", got "${G}"`)}else if(!V.test(G))throw Error(`Expected error to match ${V}, got "${G}"`)}return Y}}var X1=null;var z1={};j(z1,{setQueueManager:()=>V5,getQueueManager:()=>$5,dispatchSync:()=>e2,dispatchAfter:()=>o2,dispatch:()=>a2,createRedisDispatcher:()=>Y5,chain:()=>r2,batch:()=>t2,StacksQueueManager:()=>N4,RedisQueue:()=>r,RedisJob:()=>J4,QueueManager:()=>L5});import{Queue as s2,batch as t2,chain as r2,dispatch as a2,dispatchAfter as o2,dispatchSync as e2,getQueueManager as $5,QueueManager as L5,setQueueManager as V5}from"@stacksjs/bun-queue";import{log as u1}from"@stacksjs/logging";class r{queue;config;isProcessing=!1;constructor($,L){this.config=L,this.queue=new s2($,{driver:"redis",prefix:L.prefix,redis:L.redis?{url:L.redis.url||this.buildRedisUrl(L.redis)}:void 0,defaultJobOptions:L.defaultJobOptions?{delay:L.defaultJobOptions.delay,attempts:L.defaultJobOptions.attempts,backoff:L.defaultJobOptions.backoff,removeOnComplete:L.defaultJobOptions.removeOnComplete,removeOnFail:L.defaultJobOptions.removeOnFail,priority:L.defaultJobOptions.priority,lifo:L.defaultJobOptions.lifo,timeout:L.defaultJobOptions.timeout,jobId:L.defaultJobOptions.jobId,dependsOn:L.defaultJobOptions.dependsOn,keepJobs:L.defaultJobOptions.keepJobs,deadLetter:L.defaultJobOptions.deadLetter}:void 0,limiter:L.limiter,metrics:L.metrics,stalledJobCheckInterval:L.stalledJobCheckInterval,maxStalledJobRetries:L.maxStalledJobRetries,distributedLock:L.distributedLock,defaultDeadLetterOptions:L.defaultDeadLetterOptions,horizontalScaling:L.horizontalScaling,logLevel:L.logLevel}),u1.debug(`Redis queue "${$}" initialized`)}buildRedisUrl($){let L=$.host||"localhost",V=$.port||6379,Y=$.password?`:${encodeURIComponent($.password)}@`:"",G=Number.isFinite($.db)&&$.db>=0?$.db:0;return`redis://${Y}${L}:${V}/${G}`}async add($,L){let V={delay:L?.delay?L.delay*1000:void 0,attempts:L?.maxTries,priority:L?.priority,timeout:L?.timeout?L.timeout*1000:void 0,backoff:Array.isArray(L?.backoff)?L.backoff.map((Y)=>(Number(Y)||1)*1000):L?.backoff};return this.queue.add($,V)}process($,L){if(this.isProcessing){u1.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process($,L),u1.info(`Started processing queue with concurrency ${$}`)}async getJob($){return this.queue.getJob($)}async getJobs($){return this.queue.getJobs($)}async getJobCounts(){return this.queue.getJobCounts()}async removeJob($){return this.queue.removeJob($)}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($){return this.queue.scheduleCron({cronExpression:$.cron,data:$.data,timezone:$.tz,jobId:$.name})}async unscheduleCron($){return this.queue.unscheduleCron($)}async getDeadLetterJobs(){return this.queue.getDeadLetterJobs()}async republishDeadLetterJob($){return this.queue.republishDeadLetterJob($)}async clearDeadLetterQueue(){return this.queue.clearDeadLetterQueue()}async bulkRemove($){return this.queue.bulkRemove($)}async getClusterInfo(){return this.queue.getClusterInfo()}isLeader(){return this.queue.isLeader()}getQueue(){return this.queue}on($,L){this.queue.events.on($,L)}}class N4{config;queues=new Map;defaultConnection="default";constructor($){this.config=$;for(let[L,V]of Object.entries($))if(V.driver==="redis")this.queues.set(L,new r(L,V))}queue($){let L=$||this.defaultConnection,V=this.queues.get(L);if(!V){let Y=this.config[L];if(!Y)throw Error(`Queue "${L}" not configured`);V=new r(L,Y),this.queues.set(L,V)}return V}setDefaultConnection($){this.defaultConnection=$}async closeAll(){let $=Array.from(this.queues.values()).map((L)=>L.close());await Promise.all($),this.queues.clear()}}function Y5($,L){let V=new r($,L);return async(Y,G)=>{return V.add(Y,G)}}class J4{data;options={};queue;constructor($,L,V){this.data=V;this.queue=new r($,L)}async dispatch(){await this.queue.add(this.data,this.options)}async dispatchNow(){await this.queue.add(this.data,{...this.options,immediate:!0})}delay($){return this.options.delay=$,this}afterResponse(){return this.options.afterResponse=!0,this}chain($){return this.options.chainedJobs=$,this}onQueue($){return this.options.queue=$,this}priority($){return this.options.priority=$,this}tries($){return this.options.maxTries=$,this}timeout($){return this.options.timeout=$,this}backoff($){return this.options.backoff=$,this}}var _1=()=>{};function B($){let L=$,V=L?.errno;if(typeof V==="string"&&V.toUpperCase()==="42P01")return!0;if(typeof V==="number"&&V===1146)return!0;if((typeof L?.code==="string"?L.code.toUpperCase():"")==="42P01")return!0;let G=L?.message??"";return G.includes("no such table")||G.includes("doesn't exist")||G.includes("does not exist")}var y4={};j(y4,{retryDeadLetterJob:()=>k4,purgeDeadLetterJobs:()=>I4,moveToDeadLetter:()=>S1,listDeadLetterJobs:()=>v4});import{db as a}from"@stacksjs/database";function T1(){if(q4)return;q4=!0,console.warn("[queue/dlq] dead_letter_jobs table missing \u2014 DLQ disabled. "+"Run migrations to enable poison-message isolation.")}async function S1($,L,V=1){let Y=new Date().toISOString().slice(0,19).replace("T"," ");try{return await a.insertInto("dead_letter_jobs").values({uuid:$.uuid??crypto.randomUUID(),connection:$.connection??"database",queue:$.queue??"default",payload:$.payload??"{}",exception:$.exception??"unknown",reason:L,total_failures:V,first_failed_at:$.failed_at??Y,last_failed_at:Y,dead_lettered_at:Y}).execute(),!0}catch(G){if(B(G))return T1(),!1;throw G}}async function v4($={}){try{let L=a.selectFrom("dead_letter_jobs").selectAll();if($.queue)L=L.where("queue","=",$.queue);if($.reason)L=L.where("reason","=",$.reason);if($.sinceCutoffMs){let Y=new Date($.sinceCutoffMs).toISOString().slice(0,19).replace("T"," ");L=L.where("dead_lettered_at",">=",Y)}if($.limit&&$.limit>0)L=L.limit($.limit);return await L.execute()??[]}catch(L){if(B(L))return T1(),[];throw L}}async function k4($){try{let L=await a.selectFrom("dead_letter_jobs").where("id","=",$).selectAll().executeTakeFirst();if(!L)return!1;let V=Math.floor(Date.now()/1000);return await a.insertInto("jobs").values({queue:L.queue,payload:L.payload,attempts:0,reserved_at:null,available_at:V,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),await a.deleteFrom("dead_letter_jobs").where("id","=",$).execute(),!0}catch(L){if(B(L))return T1(),!1;throw L}}async function I4($=30){try{let L=new Date(Date.now()-$*24*60*60*1000).toISOString().slice(0,19).replace("T"," "),V=await a.deleteFrom("dead_letter_jobs").where("dead_lettered_at","<",L).execute();return Number(V?.numDeletedRows??V?.[0]?.numDeletedRows??V?.affectedRows??0)}catch(L){if(B(L))return T1(),0;throw L}}var q4=!1;var N1=()=>{};function b1(){if(w4)return;w4=!0,console.warn("[queue/idempotency] job_idempotency table missing \u2014 idempotency keys are accepted but NOT enforced. "+"Run migrations to enable dedup.")}async function U5($){try{let{db:L}=await import("@stacksjs/database"),V=await L.selectFrom("job_idempotency").where("idempotency_key","=",$).select(["idempotency_key"]).executeTakeFirst();return Boolean(V)}catch(L){if(B(L))return b1(),!1;throw L}}async function X5($,L,V){try{let{db:Y}=await import("@stacksjs/database");await Y.insertInto("job_idempotency").values({idempotency_key:$,job_name:L,queue:V??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}catch(Y){if(B(Y)){b1();return}let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return;throw Y}}async function l1($,L,V){try{let{db:Y}=await import("@stacksjs/database");return await Y.insertInto("job_idempotency").values({idempotency_key:$,job_name:L,queue:V??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),"claimed"}catch(Y){if(B(Y))return b1(),"unenforced";let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return"duplicate";throw Y}}async function c1($){try{let{db:L}=await import("@stacksjs/database");await L.deleteFrom("job_idempotency").where("idempotency_key","=",$).execute()}catch{}}var w4=!1;var i1=()=>{};var g4={};j(g4,{unquarantineJob:()=>h4,recordFailureForPoison:()=>j4,quarantineJob:()=>m4,listQuarantined:()=>p4,isQuarantined:()=>J1,hashPayload:()=>Z1});import{createHash as K5}from"crypto";import{db as R}from"@stacksjs/database";function W1(){if(f4)return;f4=!0,console.warn("[queue/poison] job_quarantine table missing \u2014 poison detection disabled. "+"Run migrations to enable.")}function Z1($){let L;try{L=typeof $==="string"?$:JSON.stringify($??null)}catch{L=String($)}return K5("sha256").update(L).digest("hex").slice(0,32)}async function j4($,L,V={}){let Y=V.maxFailures??5,G=V.windowMinutes??60,U=Z1(L),X=new Date,_=X.toISOString().slice(0,19).replace("T"," ");try{let K=await R.selectFrom("job_quarantine").where("job_name","=",$).where("payload_hash","=",U).selectAll().executeTakeFirst();if(!K)return await R.insertInto("job_quarantine").values({job_name:$,payload_hash:U,failure_count:1,window_start:_,quarantined_at:null}).execute(),!1;if(K.quarantined_at)return!0;let z=Date.parse(K.window_start.replace(" ","T")+"Z"),Z=X.getTime()-z,x=G*60*1000;if(Number.isFinite(z)&&Z>x)return await R.updateTable("job_quarantine").set({failure_count:1,window_start:_}).where("id","=",K.id).execute(),!1;let A=K.failure_count+1;if(A>=Y)return await R.updateTable("job_quarantine").set({failure_count:A,quarantined_at:_}).where("id","=",K.id).execute(),!0;return await R.updateTable("job_quarantine").set({failure_count:A}).where("id","=",K.id).execute(),!1}catch(K){if(B(K))return W1(),!1;throw K}}async function J1($,L){let V=Z1(L);try{let Y=await R.selectFrom("job_quarantine").where("job_name","=",$).where("payload_hash","in",[V,"*"]).whereNotNull("quarantined_at").select(["id"]).executeTakeFirst();return Boolean(Y)}catch(Y){if(B(Y))return W1(),!1;throw Y}}async function m4($,L){let V=L===void 0?"*":Z1(L),Y=new Date().toISOString().slice(0,19).replace("T"," ");try{let G=await R.selectFrom("job_quarantine").where("job_name","=",$).where("payload_hash","=",V).select(["id"]).executeTakeFirst();if(G)await R.updateTable("job_quarantine").set({quarantined_at:Y}).where("id","=",G.id).execute();else await R.insertInto("job_quarantine").values({job_name:$,payload_hash:V,failure_count:0,window_start:Y,quarantined_at:Y}).execute()}catch(G){if(B(G)){W1();return}throw G}}async function h4($){try{await R.deleteFrom("job_quarantine").where("job_name","=",$).execute()}catch(L){if(B(L)){W1();return}throw L}}async function p4(){try{return await R.selectFrom("job_quarantine").whereNotNull("quarantined_at").selectAll().execute()??[]}catch($){if(B($))return W1(),[];throw $}}var f4=!1;var R1=()=>{};function o($){let L=$?.numUpdatedRows;if(L===null||L===void 0)return 0;if(typeof L==="object")return Number(L.changes??0);return Number(L)}async function n1($,L){let V=JSON.stringify({jobName:$,payload:L.payload||{},options:{queue:L.queue,tries:L.maxTries,timeout:L.timeout,backoff:L.backoff}}),{db:Y}=await import("@stacksjs/database");await Y.insertInto("jobs").values({queue:L.queue||"default",payload:V,attempts:0,available_at:z5(L.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}function z5($){let L=Date.now();return Math.floor(L/1000+$)}var h={};j(h,{withEvents:()=>b4,onQueueEvent:()=>u4,getWorkerTracker:()=>O1,getQueueEvents:()=>e,getGlobalMetrics:()=>v1,emitQueueEvent:()=>l,QueueMetrics:()=>q1,QueueEvents:()=>C1,OnQueueEvent:()=>l4});import{log as w}from"@stacksjs/logging";class C1{handlers=new Map;wildcardHandlers=new Set;on($,L){if(!this.handlers.has($))this.handlers.set($,new Set);return this.handlers.get($).add(L),()=>{this.handlers.get($)?.delete(L)}}onAny($){return this.wildcardHandlers.add($),()=>{this.wildcardHandlers.delete($)}}once($,L){let V=async(Y)=>{this.handlers.get($)?.delete(V),await L(Y)};return this.on($,V)}async emit($,L){let V={...L,timestamp:Date.now()};this.logEvent($,V);let Y=this.handlers.get($);if(Y)for(let G of Y)try{await G(V)}catch(U){w.error(`Error in queue event handler for ${$}:`,U)}for(let G of this.wildcardHandlers)try{await G($,V)}catch(U){w.error("Error in wildcard queue event handler:",U)}}logEvent($,L){let V=L.jobId?`[${L.jobId}]`:"",Y=L.queueName?`on ${L.queueName}`:"";switch($){case"job:added":w.debug(`Job added ${V} ${Y}`);break;case"job:processing":w.debug(`Job processing ${V} ${Y}`);break;case"job:completed":w.info(`Job completed ${V} ${Y} in ${L.duration}ms`);break;case"job:failed":w.error(`Job failed ${V} ${Y}:`,L.error);break;case"job:retrying":w.warn(`Job retrying ${V} ${Y} (attempt ${L.attemptsMade})`);break;case"job:stalled":w.warn(`Job stalled ${V} ${Y}`);break;case"queue:error":w.error(`Queue error ${Y}:`,L.error);break}}off($){this.handlers.delete($)}removeAllListeners(){this.handlers.clear(),this.wildcardHandlers.clear()}}function e(){if(!d1)d1=new C1;return d1}function u4($,L){let V=e();if($==="*")return V.onAny(L);return V.on($,L)}function l($,L){return e().emit($,L)}function b4($,L){return async(...V)=>{let Y=V[0]?.id||"unknown",G=Date.now();await l("job:processing",{jobId:Y,queueName:$,data:V[0]?.data});try{let U=await L(...V);return await l("job:completed",{jobId:Y,queueName:$,result:U,duration:Date.now()-G}),U}catch(U){throw await l("job:failed",{jobId:Y,queueName:$,error:U,duration:Date.now()-G}),U}}}function l4($){return function(L,V,Y){let G=Y.value;return e().on($,G),Y}}class q1{jobCounts={added:0,completed:0,failed:0,processing:0};completions=[];errors=[];unsubscribe=[];constructor(){this.setupListeners()}setupListeners(){let $=e();this.unsubscribe.push($.on("job:added",()=>{this.jobCounts.added++}),$.on("job:processing",()=>{this.jobCounts.processing++}),$.on("job:completed",(L)=>{this.jobCounts.completed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1);let V=L.duration||0;if(this.completions.push({timestamp:Date.now(),duration:V}),this.completions.length>1000)this.completions.shift()}),$.on("job:failed",(L)=>{if(this.jobCounts.failed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1),L.error){if(this.errors.push({error:L.error,timestamp:Date.now()}),this.errors.length>100)this.errors.shift()}}))}getThroughputPerMinute(){let $=Date.now()-60000;return this.completions.filter((V)=>V.timestamp>=$).length}getAverageProcessingTime(){let $=Date.now()-60000,L=this.completions.filter((V)=>V.timestamp>=$);if(L.length===0)return 0;return L.reduce((V,Y)=>V+Y.duration,0)/L.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(($)=>$()),this.unsubscribe=[]}}function v1(){if(!s1)s1=new q1;return s1}class c4{workers=new Map;register($,L){this.workers.set($,{id:$,status:"idle",queue:L,processedCount:0,failedCount:0,lastActivityAt:new Date().toISOString(),startedAt:new Date().toISOString()})}markActive($){let L=this.workers.get($);if(L)L.status="active",L.lastActivityAt=new Date().toISOString()}markIdle($){let L=this.workers.get($);if(L)L.status="idle",L.lastActivityAt=new Date().toISOString()}recordCompletion($){let L=this.workers.get($);if(L)L.processedCount++,L.lastActivityAt=new Date().toISOString()}recordFailure($){let L=this.workers.get($);if(L)L.failedCount++,L.lastActivityAt=new Date().toISOString()}unregister($){let L=this.workers.get($);if(L)L.status="stopped"}getAll(){return Array.from(this.workers.values())}clear(){this.workers.clear()}}function O1(){return _5}var d1=null,s1=null,_5;var v=y(()=>{_5=new c4});var u={};j(u,{recordBatchJobFailure:()=>L4,recordBatchJobCompletion:()=>$4,isBatchCancelled:()=>L2,getBatchCallbacks:()=>F1,batchRecordToHash:()=>a4,batchRecordFromHash:()=>e1,PendingBatch:()=>I1,DispatchedBatch:()=>p,Batch:()=>t1});var{RedisClient:W5}=globalThis.Bun;import{log as E}from"@stacksjs/logging";import{env as Z5}from"@stacksjs/env";function g(){return Z5.QUEUE_DRIVER||"sync"}class I1{jobs;options={thenCallbacks:[],catchCallbacks:[],finallyCallbacks:[],progressCallbacks:[]};constructor($){this.jobs=$.map((L)=>("job"in L)?L:{job:L})}name($){return this.options.name=$,this}onQueue($){return this.options.queue=$,this}allowFailures(){return this.options.allowFailures=!0,this}then($){return this.options.thenCallbacks.push($),this}catch($){return this.options.catchCallbacks.push($),this}finally($){return this.options.finallyCallbacks.push($),this}progress($){return this.options.progressCallbacks.push($),this}thenHandler($){return this.options.thenHandler=$,this}catchHandler($){return this.options.catchHandler=$,this}finallyHandler($){return this.options.finallyHandler=$,this}async dispatch(){let $=crypto.randomUUID(),L=this.jobs.length;if(L===0)throw Error("Cannot dispatch an empty batch");let V=g();await F5({id:$,name:this.options.name||"",total_jobs:L,pending_jobs:L,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}),O5($,this.options);try{let{emitQueueEvent:Y}=await Promise.resolve().then(() => (v(),h));await Y("batch:added",{jobId:$,data:{name:this.options.name,totalJobs:L}})}catch{}for(let Y=0;Y<this.jobs.length;Y++){let G=this.jobs[Y];if(!G)continue;let{job:U,payload:X}=G,_={...X,_batchId:$,_batchIndex:Y};if(this.options.queue&&!U.queue)U.queue=this.options.queue;if(V==="sync")try{await U.dispatchNow(_),await $4($)}catch(K){await L4($,`${$}:${Y}`,K)}else await U.dispatch(_)}return E.info(`[Batch] Dispatched batch "${this.options.name||$}" with ${L} jobs`),new p($)}getJobs(){return[...this.jobs]}getOptions(){return this.options}}class p{id;constructor($){this.id=$}async fresh(){return c(this.id)}async getName(){return(await this.fresh())?.name||""}async totalJobs(){return(await this.fresh())?.total_jobs||0}async pendingJobs(){return(await this.fresh())?.pending_jobs||0}async failedJobs(){return(await this.fresh())?.failed_jobs||0}async completedJobs(){let $=await this.fresh();if(!$)return 0;return $.total_jobs-$.pending_jobs}async progress(){let $=await this.fresh();if(!$||$.total_jobs===0)return 0;let L=$.total_jobs-$.pending_jobs;return Math.round(L/$.total_jobs*100)}async finished(){return(await this.fresh())?.finished_at!==null}async cancelled(){return(await this.fresh())?.cancelled_at!==null}async hasFailures(){return((await this.fresh())?.failed_jobs||0)>0}async failedJobIds(){let $=await this.fresh();if(!$)return[];try{return JSON.parse($.failed_job_ids||"[]")}catch{return[]}}async cancel(){if(g()==="redis")await E5(this.id);else await A5(this.id);E.info(`[Batch] Cancelled batch ${this.id}`);let L=F1(this.id);if(L)for(let V of L.finallyCallbacks)try{await V(this)}catch(Y){E.error(`[Batch] Error in finally callback for batch ${this.id}:`,Y)}}async add($){let L=await this.fresh();if(!L)throw Error(`Batch ${this.id} not found`);if(L.cancelled_at)throw Error(`Batch ${this.id} has been cancelled`);if(L.finished_at)throw Error(`Batch ${this.id} has already finished`);let V=$.map((X)=>("job"in X)?X:{job:X}),Y=L.total_jobs+V.length,G=L.pending_jobs+V.length;await i4(this.id,{total_jobs:Y,pending_jobs:G});let U=JSON.parse(L.options||"{}");for(let X=0;X<V.length;X++){let _=V[X];if(!_)continue;let{job:K,payload:z}=_,Z={...z,_batchId:this.id,_batchIndex:L.total_jobs+X};if(U.queue&&!K.queue)K.queue=U.queue;await K.dispatch(Z)}E.info(`[Batch] Added ${V.length} jobs to batch ${this.id}`)}async delete(){await H5(this.id),k1(this.id)}}class t1{static create($){return new I1($)}static async find($){if(!await c($))return null;return new p($)}static async all(){return(await x5()).map((L)=>new p(L.id))}static async prune($=24){return Q5($)}}function O5($,L){r1.set($,L)}function F1($){return r1.get($)}function k1($){r1.delete($)}async function F5($){if(g()==="redis")await M5($);else await n4($)}async function c($){if(g()==="redis")return B5($);return d4($)}async function x5(){if(g()==="redis")return o4();return s4()}async function i4($,L){if(g()==="redis")await e4($,L);else await a1($,L)}async function H5($){if(g()==="redis")await $2($);else await t4($)}async function Q5($){if(g()==="redis")return T5($);return r4($)}function D5($){return!!($.then_handler||$.catch_handler||$.finally_handler)}async function n4($){let{db:L}=await import("@stacksjs/database"),V={id:$.id,name:$.name,total_jobs:$.total_jobs,pending_jobs:$.pending_jobs,failed_jobs:$.failed_jobs,failed_job_ids:$.failed_job_ids,options:$.options,cancelled_at:$.cancelled_at,created_at:$.created_at,finished_at:$.finished_at};if(!D5($)){await L.insertInto("job_batches").values(V).execute();return}try{await L.insertInto("job_batches").values({...V,then_handler:$.then_handler??null,catch_handler:$.catch_handler??null,finally_handler:$.finally_handler??null}).execute()}catch(Y){E.warn(`[Batch] Could not persist terminal handlers for batch ${$.id}: ${Y?.message}. The job_batches table predates the then_handler/catch_handler/finally_handler columns; recreate it to enable handlers that survive a worker restart.`),await L.insertInto("job_batches").values(V).execute()}}async function d4($){let{db:L}=await import("@stacksjs/database");return await L.selectFrom("job_batches").where("id","=",$).selectAll().executeTakeFirst()||null}async function s4(){let{db:$}=await import("@stacksjs/database");return await $.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function a1($,L){let{db:V}=await import("@stacksjs/database");await V.updateTable("job_batches").set(L).where("id","=",$).execute()}async function t4($){let{db:L}=await import("@stacksjs/database");await L.deleteFrom("job_batches").where("id","=",$).execute()}async function A5($){await a1($,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function r4($){let{db:L}=await import("@stacksjs/database"),V=new Date(Date.now()-$*60*60*1000).toISOString().slice(0,19).replace("T"," "),Y=await L.deleteFrom("job_batches").whereNotNull("finished_at").where("finished_at","<",V).executeTakeFirst();return Number(Y?.numDeletedRows??0)}async function P5(){let{queue:$}=await import("@stacksjs/config"),L=$?.connections?.redis?.redis;if(L?.url)return L.url;let V=L?.password?`:${encodeURIComponent(L.password)}@`:"",Y=L?.db?`/${L.db}`:"";return`redis://${V}${L?.host||"localhost"}:${L?.port||6379}${Y}`}async function H1(){let $=new W5(await P5());return await $.connect(),$}function a4($){return{id:$.id,name:$.name,total_jobs:String($.total_jobs),pending_jobs:String($.pending_jobs),failed_jobs:String($.failed_jobs),failed_job_ids:$.failed_job_ids,options:$.options,cancelled_at:$.cancelled_at||"",created_at:$.created_at,finished_at:$.finished_at||"",then_handler:$.then_handler||"",catch_handler:$.catch_handler||"",finally_handler:$.finally_handler||""}}async function M5($){try{let L=await H1();await L.hset(`${x1}${$.id}`,a4($)),await L.sadd(o1,$.id),L.close()}catch{await n4($)}}function e1($){if(!$?.id)return null;return{id:$.id,name:$.name??"",total_jobs:Number($.total_jobs),pending_jobs:Number($.pending_jobs),failed_jobs:Number($.failed_jobs),failed_job_ids:$.failed_job_ids??"",options:$.options??"",cancelled_at:$.cancelled_at||null,created_at:$.created_at??"",finished_at:$.finished_at||null,then_handler:$.then_handler||null,catch_handler:$.catch_handler||null,finally_handler:$.finally_handler||null}}async function B5($){try{let L=await H1(),V=`${x1}${$}`,Y=await L.hgetall(V);return L.close(),e1(Y)}catch{return d4($)}}async function o4(){try{let $=await H1(),L=await $.smembers(o1),V=[];for(let Y of L){let G=e1(await $.hgetall(`${x1}${Y}`));if(G)V.push(G)}return $.close(),V}catch{return s4()}}async function e4($,L){try{let V=await H1(),Y=`${x1}${$}`,G={};for(let[U,X]of Object.entries(L))G[U]=X===null?"":String(X);await V.hset(Y,G),V.close()}catch{await a1($,L)}}async function $2($){try{let L=await H1();await L.del(`${x1}${$}`),await L.srem(o1,$),L.close()}catch{await t4($)}}async function E5($){let L=new Date().toISOString().slice(0,19).replace("T"," ");await e4($,{cancelled_at:L,finished_at:L})}async function T5($){try{let L=await o4(),V=Date.now()-$*60*60*1000,Y=0;for(let G of L)if(G.finished_at){if(new Date(G.finished_at).getTime()<V)await $2(G.id),Y++}return Y}catch{return r4($)}}function $1($){if(!$)return null;try{let L=JSON.parse($);if(L&&(L.kind==="job"||L.kind==="module"))return L;return E.warn(`[Batch] handler JSON has unknown kind '${L?.kind}' \u2014 skipping`),null}catch(L){return E.warn(`[Batch] failed to parse persistent handler: ${L.message}`),null}}async function L1($,L){try{if($.kind==="job"){let{Jobs:G}=await Promise.resolve().then(() => (Q1(),y1));await G.dispatch($.name,{...$.payload??{},_batchId:L});return}let V=await import($.module).catch((G)=>{return E.warn(`[Batch] persistent handler module not found: ${$.module} (${G.message})`),null});if(!V)return;let Y=V[$.export];if(typeof Y!=="function"){E.warn(`[Batch] persistent handler export '${$.export}' is not a function on ${$.module}`);return}await Y($.payload,L)}catch(V){E.error(`[Batch] persistent handler threw for batch ${L}:`,V)}}async function $4($){let{db:L,sql:V}=await import("@stacksjs/database");await L.updateTable("job_batches").set({pending_jobs:V`GREATEST(pending_jobs - 1, 0)`}).where("id","=",$).where("pending_jobs",">",0).execute();let Y=new Date().toISOString().slice(0,19).replace("T"," "),G=await L.updateTable("job_batches").set({finished_at:Y}).where("id","=",$).where("pending_jobs","=",0).whereNull("finished_at").executeTakeFirst(),U=o(G)>0,X=F1($),_=new p($);if(X)for(let K of X.progressCallbacks)try{await K(_)}catch(z){E.error(`[Batch] Error in progress callback for batch ${$}:`,z)}if(U){try{let{emitQueueEvent:O}=await Promise.resolve().then(() => (v(),h));await O("batch:completed",{jobId:$})}catch{}let K=await c($);if(!K){k1($),E.info(`[Batch] Batch ${$} finished (record vanished)`);return}let z=JSON.parse(K.options||"{}"),x=!((K.failed_jobs||0)>0)||z.allowFailures;if(X){if(x)for(let O of X.thenCallbacks)try{await O(_)}catch(W){E.error(`[Batch] Error in then callback for batch ${$}:`,W)}for(let O of X.finallyCallbacks)try{await O(_)}catch(W){E.error(`[Batch] Error in finally callback for batch ${$}:`,W)}k1($)}if(x){let O=$1(K.then_handler);if(O)await L1(O,$)}else{let O=$1(K.catch_handler);if(O)await L1(O,$)}let A=$1(K.finally_handler);if(A)await L1(A,$);E.info(`[Batch] Batch ${$} finished`)}}async function L4($,L,V){let{db:Y,sql:G}=await import("@stacksjs/database"),U=await c($);if(!U)return;let X=JSON.parse(U.options||"{}");await Y.updateTable("job_batches").set({pending_jobs:G`GREATEST(pending_jobs - 1, 0)`,failed_jobs:G`failed_jobs + 1`}).where("id","=",$).where("pending_jobs",">",0).execute();try{let W=await c($);if(W){let H=[];try{H=JSON.parse(W.failed_job_ids||"[]")}catch{H=[]}H.push(L),await i4($,{failed_job_ids:JSON.stringify(H)})}}catch{}let _=new p($),K=F1($);if(K)for(let W of K.catchCallbacks)try{await W(_,V)}catch(H){E.error(`[Batch] Error in catch callback for batch ${$}:`,H)}try{let{emitQueueEvent:W}=await Promise.resolve().then(() => (v(),h));await W("batch:failed",{jobId:$,error:V})}catch{}let z=new Date().toISOString().slice(0,19).replace("T"," "),Z=Y.updateTable("job_batches").set(X.allowFailures?{finished_at:z}:{finished_at:z,cancelled_at:z}).where("id","=",$).whereNull("finished_at");if(X.allowFailures)Z=Z.where("pending_jobs","=",0);let x=await Z.executeTakeFirst();if(o(x)===0)return;let A=!!X.allowFailures;if(K){if(A)for(let W of K.thenCallbacks)try{await W(_)}catch(H){E.error(`[Batch] Error in then callback for batch ${$}:`,H)}for(let W of K.finallyCallbacks)try{await W(_)}catch(H){E.error(`[Batch] Error in finally callback for batch ${$}:`,H)}k1($)}let O=await c($);if(O){if(A){let H=$1(O.then_handler);if(H)await L1(H,$)}else{let H=$1(O.catch_handler);if(H)await L1(H,$)}let W=$1(O.finally_handler);if(W)await L1(W,$)}E.info(`[Batch] Batch ${$} finished with failure(s)`)}async function L2($){return(await c($))?.cancelled_at!==null}var r1,x1="stacks:batch:",o1="stacks:batches";var f=y(()=>{r1=new Map});var y1={};j(y1,{runJob:()=>w1,resolveJobFile:()=>K2,jobBatch:()=>X2,job:()=>G2,Jobs:()=>U2});import{appPath as S5,frameworkPath as N5}from"@stacksjs/path";import{env as J5}from"@stacksjs/env";import{enqueueAfterCommit as R5,isInTransaction as C5}from"@stacksjs/database";function q5(){return J5.QUEUE_DRIVER||"sync"}function v5(){if(V2)return;V2=!0,console.warn("[queue] .afterCommit() was called outside of `db.transaction(...)`. Dispatching immediately. Wrap the call in a transaction or drop .afterCommit() to silence this message.")}class k{name;payload;options={};txMode="auto";constructor($,L){this.name=$;this.payload=L}onQueue($){return this.options.queue=$,this}delay($){return this.options.delay=$,this}tries($){return this.options.tries=$,this}timeout($){return this.options.timeout=$,this}backoff($){return this.options.backoff=$,this}withContext($){return this.options.context=$,this}withIdempotencyKey($){return this.options.idempotencyKey=$,this}afterCommit(){return this.txMode="after",this}withoutCommit(){return this.txMode="immediate",this}async dispatch(){let{isFaked:$,getFakeQueue:L}=await Promise.resolve().then(() => g1);if($()){L()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(C5()){let Y=this.runDispatchPipeline.bind(this);if(R5(async()=>{await Y()}))return}else if(this.txMode==="after")v5()}await this.runDispatchPipeline()}async runDispatchPipeline(){let $=!1;if(this.options.idempotencyKey){let L=await l1(this.options.idempotencyKey,this.name,this.options.queue);if(L==="duplicate")return;$=L==="claimed"}try{if(await J1(this.name,this.payload)){let V=m(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await S1({queue:this.options.queue||"default",payload:JSON.stringify(V),exception:`quarantined: ${this.name}`},"poison-detected"))return}let L=q5();if(L==="database")await this.dispatchToDatabase();else if(L==="redis")await this.dispatchToRedis();else if(L==="sync")await w1(this.name,{payload:this.payload,context:this.options.context,traceId:await Y2()});else if(L==="sqs"||L==="memory"||L==="beanstalkd")throw Error(`[queue] Driver "${L}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);else throw Error(`[queue] Unknown QUEUE_DRIVER "${L}". Allowed values: redis, database, sync.`)}catch(L){if($&&this.options.idempotencyKey)await c1(this.options.idempotencyKey);throw L}}async dispatchIf($){if($)await this.dispatch()}async dispatchUnless($){if(!$)await this.dispatch()}async dispatchToDatabase(){let $=Math.floor(Date.now()/1000),L=this.options.delay?$+this.options.delay:$,V=m(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff},await Y2()),{db:Y}=await import("@stacksjs/database");await Y.insertInto("jobs").values({queue:this.options.queue||"default",payload:JSON.stringify(V),attempts:0,reserved_at:null,available_at:L,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){let{RedisQueue:$}=await Promise.resolve().then(() => (_1(),z1)),{queue:L}=await import("@stacksjs/config"),V=L?.connections?.redis;if(!V)throw Error("Redis queue connection is not configured. Check config/queue.ts");let Y=new $(this.options.queue||"default",V),G=m(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});await Y.add(G,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await w1(this.name,{payload:this.payload,context:this.options.context})}}function G2($,L){return new k($,L)}function X2($){let{PendingBatch:L}=(f(),l2(u));return new L($)}async function K2($){let L=[S5(`Jobs/${$}.ts`),N5(`defaults/app/Jobs/${$}.ts`)];try{let V=import.meta.resolve("@stacksjs/defaults/package.json"),Y=new URL(".",V).pathname;L.push(`${Y}app/Jobs/${$}.ts`)}catch{}for(let V of L)if(await Bun.file(V).exists())return V;return null}async function w1($,L={}){let{withTraceId:V}=await import("@stacksjs/router"),Y=L.traceId??`job:${$}:${Math.random().toString(36).slice(2,10)}`;await V(Y,async()=>{let G=await K2($);if(!G)throw Error(`Job ${$} not found. Looked in app/Jobs/${$}.ts and the framework defaults (storage/framework/defaults/app/Jobs, @stacksjs/defaults).`);let X=(await import(G)).default;if(!X)throw Error(`Job ${$} does not export a default`);if(typeof X.handle==="function")await X.handle(L.payload);else if(typeof X.action==="string"){let{runAction:_}=await import("@stacksjs/actions");await _(X.action)}else if(typeof X.action==="function")await X.action();else if(typeof X==="function")await X(L.payload,L.context);else throw Error(`Job ${$} does not have a valid handler`)})}async function Y2(){try{let{getTraceId:$}=await import("@stacksjs/router");return $()}catch{return}}var V2=!1,U2;var Q1=y(()=>{N1();U1();i1();R1();U2={make($,L){return new k($,L)},async dispatch($,L){await new k($,L).dispatch()},async dispatchIf($,L,V){if($)await new k(L,V).dispatch()},async dispatchUnless($,L,V){if(!$)await new k(L,V).dispatch()},async dispatchNow($,L){await new k($,L).dispatchNow()},dispatchAfter($,L,V){return new k(L,V).delay($)},async dispatchOnce($,L,V){await new k(L,V).withIdempotencyKey($).dispatch()},async dispatchAfterCommit($,L){await new k($,L).afterCommit().dispatch()}}});var f1={};j(f1,{resumeQueue:()=>F2,recordCircuitSuccess:()=>W2,recordCircuitFailure:()=>Z2,pauseQueue:()=>O2,listCircuitState:()=>x2,isCircuitOpen:()=>_2});import{db as J}from"@stacksjs/database";function i(){if(z2)return;z2=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing \u2014 circuit breaker disabled. "+"Run migrations to enable.")}async function Y4($,L){try{let V=await J.selectFrom("queue_circuit_state").where("queue_name","=",$).selectAll().executeTakeFirst();if(V)return V;return await J.insertInto("queue_circuit_state").values({queue_name:$,success_count:0,failure_count:0,window_start:L,paused_at:null,resume_at:null}).execute(),{queue_name:$,success_count:0,failure_count:0,window_start:L,paused_at:null,resume_at:null}}catch(V){if(B(V))return i(),null;let Y=V?.message??"";if(Y.includes("UNIQUE constraint")||Y.includes("Duplicate entry"))return await J.selectFrom("queue_circuit_state").where("queue_name","=",$).selectAll().executeTakeFirst();throw V}}async function _2($){try{let L=await J.selectFrom("queue_circuit_state").where("queue_name","=",$).select(["paused_at","resume_at"]).executeTakeFirst();if(!L||!L.paused_at)return!1;if(L.resume_at){let V=Date.parse(L.resume_at.replace(" ","T")+"Z");if(Number.isFinite(V)&&Date.now()>=V)return await J.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",$).execute(),!1}return!0}catch(L){if(B(L))return i(),!1;throw L}}async function W2($,L={}){let V=L.windowSeconds??300,Y=new Date().toISOString().slice(0,19).replace("T"," "),G=await Y4($,Y);if(!G)return;try{if(V4(G.window_start,V)){await J.updateTable("queue_circuit_state").set({success_count:1,failure_count:0,window_start:Y}).where("queue_name","=",$).execute();return}await J.updateTable("queue_circuit_state").set({success_count:G.success_count+1}).where("queue_name","=",$).execute()}catch(U){if(B(U)){i();return}throw U}}async function Z2($,L={}){let V=L.failureRateThreshold??0.5,Y=L.windowSeconds??300,G=L.pauseSeconds??300,U=L.minObservations??10,X=new Date,_=X.toISOString().slice(0,19).replace("T"," "),K=await Y4($,_);if(!K)return!1;if(K.paused_at)return!1;try{let{success_count:z,failure_count:Z}=K;if(V4(K.window_start,Y))z=0,Z=0;Z+=1;let x=z+Z,A=x===0?0:Z/x;if(x>=U&&A>=V){let W=new Date(X.getTime()+G*1000).toISOString().slice(0,19).replace("T"," ");return await J.updateTable("queue_circuit_state").set({success_count:z,failure_count:Z,window_start:_,paused_at:_,resume_at:W}).where("queue_name","=",$).execute(),!0}return await J.updateTable("queue_circuit_state").set({success_count:z,failure_count:Z,window_start:V4(K.window_start,Y)?_:K.window_start}).where("queue_name","=",$).execute(),!1}catch(z){if(B(z))return i(),!1;throw z}}function V4($,L){if(!$)return!0;let V=Date.parse($.replace(" ","T")+"Z");if(!Number.isFinite(V))return!0;return Date.now()-V>L*1000}async function O2($,L=300){let V=new Date,Y=V.toISOString().slice(0,19).replace("T"," "),G=new Date(V.getTime()+L*1000).toISOString().slice(0,19).replace("T"," ");await Y4($,Y);try{await J.updateTable("queue_circuit_state").set({paused_at:Y,resume_at:G}).where("queue_name","=",$).execute()}catch(U){if(B(U)){i();return}throw U}}async function F2($){try{await J.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",$).execute()}catch(L){if(B(L)){i();return}throw L}}async function x2(){try{return await J.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch($){if(B($))return i(),[];throw $}}var z2=!1;var D1=()=>{};U1();import{env as G5}from"@stacksjs/env";function R4(){return G5.QUEUE_DRIVER||"sync"}class C4{name;description;action;handle;queue;rate;tries;timeout;backoff;backoffConfig;enabled;constructor($){this.name=$.name,this.description=$.description,this.handle=$.handle,this.queue=$.queue,this.rate=$.rate,this.action=$.action,this.tries=$.tries,this.timeout=$.timeout,this.backoff=$.backoff,this.backoffConfig=$.backoffConfig,this.enabled=$.enabled}async dispatch($){let{isFaked:L,getFakeQueue:V}=await Promise.resolve().then(() => g1);if(L()){V()?.dispatch(this.name||"UnknownJob",$,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let Y=R4();if(Y==="sync")return this.dispatchNow($);if(Y==="redis")return this.dispatchToRedis($);if(Y==="database")return this.dispatchToDatabase($);if(Y==="sqs"||Y==="memory"||Y==="beanstalkd")throw Error(`[queue] Driver "${Y}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);throw Error(`[queue] Unknown QUEUE_DRIVER "${Y}". Allowed values: redis, database, sync.`)}async dispatchIf($,L){if($)return this.dispatch(L)}async dispatchUnless($,L){if(!$)return this.dispatch(L)}async dispatchAfter($,L){let V=R4();if(V==="redis")return this.dispatchToRedis(L,{delay:$});if(V==="database")return this.dispatchToDatabase(L,{delay:$});return await new Promise((Y)=>setTimeout(Y,$*1000)),await this.dispatchNow(L)}async dispatchNow($){if(typeof this.handle==="function")await this.handle($);else if(typeof this.action==="string"){let{runAction:L}=await import("@stacksjs/actions");await L(this.action)}else if(typeof this.action==="function")await this.action();else throw Error(`Job ${this.name} does not have a valid handler`)}async dispatchToDatabase($,L){let V=Math.floor(Date.now()/1000),Y=L?.delay?V+L.delay:V,G=m(this.name??this.constructor.name,$,{queue:this.queue??"default",tries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0}),{db:U}=await import("@stacksjs/database");await U.insertInto("jobs").values({queue:this.queue||"default",payload:JSON.stringify(G),attempts:0,reserved_at:null,available_at:Y,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis($,L){let{RedisQueue:V}=await Promise.resolve().then(() => (_1(),z1)),{queue:Y}=await import("@stacksjs/config"),G=Y?.connections?.redis;if(!G)throw Error("Redis queue connection is not configured. Check config/queue.ts");let U=new V(this.queue||"default",G),X=m(this.name??this.constructor.name,$,{queue:this.queue??"default",tries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0});await U.add(X,{delay:L?.delay,maxTries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0})}}Q1();i1();N1();R1();D1();U1();var G4=($)=>`__job_progress__:${$}`,U4=($)=>`__job_cancel__:${$}`;function k5($){return Number.isFinite($)?Math.max(0,Math.min(100,$)):0}async function I5($,L,V){let{cache:Y}=await import("@stacksjs/cache");await Y.set(G4($),{percent:k5(L),message:V,updatedAt:Date.now()},3600)}async function y5($){let{cache:L}=await import("@stacksjs/cache");return await L.get(G4($))??null}async function w5($){let{cache:L}=await import("@stacksjs/cache");await L.set(U4($),1,3600)}async function f5($){let{cache:L}=await import("@stacksjs/cache");return Boolean(await L.get(U4($)))}async function j5($){let{cache:L}=await import("@stacksjs/cache");await Promise.all([L.del(G4($)),L.del(U4($))])}import{log as V1}from"@stacksjs/logging";import*as H2 from"@stacksjs/path";class Q2{jobs=new Map;initialized=!1;register($){this.jobs.set($.name,$),V1.debug(`Registered job: ${$.name} (${$.type})`)}get($){return this.jobs.get($)}all(){return Array.from(this.jobs.values())}byQueue($){return this.all().filter((L)=>L.config.queue===$)}scheduled(){return this.all().filter(($)=>$.config.rate||$.config.schedule)}has($){return this.jobs.has($)}clear(){this.jobs.clear(),this.initialized=!1}setInitialized($){this.initialized=$}isInitialized(){return this.initialized}}var n=new Q2;async function X4($){let L=$||H2.userJobsPath(),V=[];try{let Y=new Bun.Glob("**/*.{ts,js}"),G={cwd:L,onlyFiles:!0,absolute:!0};for await(let U of Y.scan(G)){if(U.includes(".test.")||U.includes(".spec.")||U.endsWith("index.ts")||U.endsWith("index.js"))continue;try{let X=await m5(U);if(X)V.push(X),n.register(X)}catch(X){V1.warn(`Failed to load job from ${U}: ${X.message}`)}}return n.setInitialized(!0),V1.info(`Discovered ${V.length} jobs from ${L}`),V}catch(Y){return V1.error(`Failed to discover jobs: ${Y.message}`),[]}}async function m5($){try{let L=await import($),V=$.split("/").pop()?.replace(/\.(ts|js)$/,"")||"UnknownJob";if(L.default&&typeof L.default==="function"){let Y=L.default;if(typeof Y.handle==="function"||typeof Y.prototype?.handle==="function"){let G=Y.config||{};return{name:G.name||V,path:$,config:{name:G.name||V,description:G.description,queue:G.queue||"default",tries:G.retries||3,timeout:G.timeout,withoutOverlapping:G.withoutOverlapping,schedule:G.schedule,retryAfter:G.retryAfter},type:"class",module:Y}}}if(L.default&&typeof L.default==="object"){let Y=L.default;if(typeof Y.handle==="function"||typeof Y.action==="string")return{name:Y.name||V,path:$,config:{name:Y.name||V,description:Y.description,queue:Y.queue||"default",tries:Y.tries||3,backoff:Y.backoff,rate:Y.rate,timeout:Y.timeout||Y.timeOut,backoffConfig:Y.backoffConfig},type:"function",module:Y}}return null}catch(L){return V1.debug(`Could not load job from ${$}: ${L.message}`),null}}function h5($){return n.get($)}function p5(){return n.all()}function K4(){return n.scheduled()}async function g5($,L){let V=n.get($);if(!V)throw Error(`Job "${$}" not found. Did you run discoverJobs()?`);try{if(V.type==="class"){if(typeof V.module.handle==="function")return await V.module.handle(L);return await new V.module().handle(L)}else{if(typeof V.module.handle==="function")return await V.module.handle(L);throw Error(`Job "${$}" does not have a handle method`)}}catch(Y){throw V1.error(`Failed to execute job "${$}": ${Y.message}`),Y}}function u5($){let L=$.config;return{name:L.name,queue:L.queue,tries:L.tries,backoff:L.backoff,timeout:L.timeout,backoffConfig:L.backoffConfig,rate:L.rate}}import{log as T}from"@stacksjs/logging";v();import{log as A2}from"@stacksjs/logging";var D2=!1;async function P2(){if(D2)return!0;try{let{db:$}=await import("@stacksjs/database");return await $.unsafe("CREATE TABLE IF NOT EXISTS scheduled_job_runs (job_name VARCHAR(255) PRIMARY KEY, last_run_at VARCHAR(64) NOT NULL)").execute(),D2=!0,!0}catch($){return A2.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${$ instanceof Error?$.message:String($)}`),!1}}async function M2($){if(!await P2())return null;try{let{db:L}=await import("@stacksjs/database"),V=await L.selectFrom("scheduled_job_runs").where("job_name","=",$).select(["last_run_at"]).executeTakeFirst();if(!V?.last_run_at)return null;let Y=new Date(V.last_run_at);return Number.isNaN(Y.getTime())?null:Y}catch{return null}}async function B2($,L){if(!await P2())return;try{let{db:V}=await import("@stacksjs/database"),Y=L.toISOString();await V.deleteFrom("scheduled_job_runs").where("job_name","=",$).execute(),await V.insertInto("scheduled_job_runs").values({job_name:$,last_run_at:Y}).execute()}catch{}}function b5($){return`%"jobName":"${$.replace(/[\\%_]/g,(V)=>`\\${V}`)}"%`}async function E2($){try{let{db:L}=await import("@stacksjs/database"),V=await L.unsafe("SELECT 1 AS present FROM jobs WHERE payload LIKE ? ESCAPE '\\' LIMIT 1",[b5($)]).execute(),Y=Array.isArray(V)?V:V?.rows??[];return Array.isArray(Y)&&Y.length>0}catch(L){return A2.debug(`[scheduler] overlap check unavailable, dispatching anyway: ${L instanceof Error?L.message:String(L)}`),!1}}var R2={checkInterval:60000,preventOverlapping:!0},P={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...R2}},T2=new Set;function l5($,L){if(T2.has($))return;T2.add($),T.warn(`[scheduler] Cron expression "${$}" specifies seconds="${L}" but the scheduler `+"ticks at minute granularity \u2014 the seconds field is being ignored. Use a 5-field expression "+"to avoid this warning, or wait for sub-minute scheduling support.")}var S2=!1,N2=new Map;function c5($){let L=N2.get($);if(!L)L=new Intl.DateTimeFormat("en-US",{timeZone:$,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),N2.set($,L);return L}function z4($,L){if(L&&L!=="local"&&L!=="system")try{let V=c5(L).formatToParts($),Y=(X)=>V.find((_)=>_.type===X)?.value??"",G={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},U=Number(Y("hour"));if(U===24)U=0;return{minute:Number(Y("minute")),hour:U,day:Number(Y("day")),month:Number(Y("month")),dayOfWeek:G[Y("weekday")]??$.getDay()}}catch{if(!S2)S2=!0,T.warn(`[scheduler] Invalid timezone "${L}"; falling back to system local time.`)}return{minute:$.getMinutes(),hour:$.getHours(),day:$.getDate(),month:$.getMonth()+1,dayOfWeek:$.getDay()}}function i5($,L,V){let Y=z4(new Date,V),G=Y.minute,U=Y.hour,X=Y.day,_=Y.month,K=Y.dayOfWeek;if(L){let H=z4(L,V);if(H.minute===G&&H.hour===U&&H.day===X)return!1}let z=$.trim().split(/\s+/);if(z.length===6){let H=z[0];if(H&&H!=="0"&&H!=="*")l5($,H);z=z.slice(1)}if(z.length<5)return T.warn(`Invalid cron expression: ${$}`),!1;let[Z,x,A,O,W]=z;return I(Z,G,0,59)&&I(x,U,0,23)&&I(A,X,1,31)&&I(O,_,1,12)&&I(W,K,0,6)}function I($,L,V,Y){if($==="*")return!0;if($.includes(","))return $.split(",").map((X)=>Number.parseInt(X.trim(),10)).includes(L);if($.includes("-")){let[U,X]=$.split("-").map((_)=>Number.parseInt(_.trim(),10));return L>=U&&L<=X}if($.includes("/")){let[U,X]=$.split("/"),_=Number.parseInt(X,10);if(U==="*")return L%_===0;if(U.includes("-")){let[K,z]=U.split("-").map((Z)=>Number.parseInt(Z.trim(),10));return L>=K&&L<=z&&(L-K)%_===0}}let G=Number.parseInt($,10);return!Number.isNaN(G)&&L===G}function C2($){let V={"@yearly":"0 0 1 1 *","@annually":"0 0 1 1 *","@monthly":"0 0 1 * *","@weekly":"0 0 * * 0","@daily":"0 0 * * *","@midnight":"0 0 * * *","@hourly":"0 * * * *"}[$.toLowerCase()];if(V)return V;let Y=$.match(/^Every\.(\w+)$/i);if(Y&&Y[1]!==void 0){let U=Y[1].toLowerCase();return{second:"* * * * *",fiveseconds:"* * * * *",tenseconds:"* * * * *",thirtyseconds:"* * * * *",minute:"* * * * *",fiveminutes:"*/5 * * * *",tenminutes:"*/10 * * * *",fifteenminutes:"*/15 * * * *",thirtyminutes:"*/30 * * * *",hour:"0 * * * *",twohours:"0 */2 * * *",sixhours:"0 */6 * * *",twelvehours:"0 */12 * * *",day:"0 0 * * *",week:"0 0 * * 0",month:"0 0 1 * *"}[U]||null}let G=$.split(/\s+/).length;if(G>=5&&G<=6)return $;return null}function q2($,L){let V=$.trim().split(/\s+/),Y=V.length===6?V.slice(1):V;if(Y.length<5)return null;let[G,U,X,_,K]=Y,z=new Date;z.setSeconds(0,0);let Z=527040;for(let x=1;x<=Z;x++){let A=new Date(z.getTime()+x*60000),O=z4(A,L);if(I(G,O.minute,0,59)&&I(U,O.hour,0,23)&&I(X,O.day,1,31)&&I(_,O.month,1,12)&&I(K,O.dayOfWeek,0,6))return A}return null}async function n5($={}){if(P.isRunning){T.warn("Scheduler is already running");return}P.config={...R2,...$},P.isRunning=!0,P.isShuttingDown=!1,await X4();let L=K4();for(let G of L){let U=G.config.rate||G.config.schedule;if(U){let X=C2(U);if(X){let _=await M2(G.name);P.jobs.set(G.name,{job:G,lastRun:_,nextRun:q2(X,P.config.timezone),isRunning:!1}),T.info(`Registered scheduled job: ${G.name} (${X})`)}else T.warn(`Invalid schedule for job ${G.name}: ${U}`)}}if(P.jobs.size===0){T.info("No scheduled jobs found");return}T.info(`Scheduler started with ${P.jobs.size} job(s)`),process.on("SIGINT",()=>_4()),process.on("SIGTERM",()=>_4());let V=!1,Y=()=>{if(P.isShuttingDown)return;let G=P.config.checkInterval,U=G-Date.now()%G;P.checkInterval=setTimeout(()=>{if(!P.isShuttingDown&&!V)V=!0,J2().catch((X)=>T.error("Scheduler check failed:",X)).finally(()=>{V=!1});Y()},U),P.checkInterval?.unref?.()};Y(),await J2()}async function J2(){for(let[$,L]of P.jobs){let V=L.job.config.rate||L.job.config.schedule;if(!V)continue;let Y=C2(V);if(!Y)continue;if(i5(Y,L.lastRun,P.config.timezone)){if((P.config.preventOverlapping||L.job.config.withoutOverlapping)&&await E2($)){T.debug(`Skipping ${$}: previous execution still running`);continue}if(L.isRunning){T.debug(`Skipping ${$}: a dispatch for it is already in flight`);continue}try{L.isRunning=!0,L.lastRun=new Date,L.nextRun=q2(Y,P.config.timezone),await B2($,L.lastRun),T.info(`Dispatching scheduled job: ${$}`),await l("job:added",{jobId:`scheduled-${$}-${Date.now()}`,queueName:L.job.config.queue||"default",jobName:$}),await n1($,{queue:L.job.config.queue||"default",payload:{},maxTries:L.job.config.tries||3,timeout:L.job.config.timeout||60}),L.isRunning=!1,T.info(`Scheduled job ${$} dispatched to queue`)}catch(U){L.isRunning=!1,T.error(`Failed to dispatch scheduled job ${$}:`,U)}}}}async function _4(){if(!P.isRunning)return;if(T.info("Stopping scheduler..."),P.isShuttingDown=!0,P.checkInterval)clearTimeout(P.checkInterval),P.checkInterval=null;P.isRunning=!1,P.jobs.clear(),T.info("Scheduler stopped")}function d5(){return{isRunning:P.isRunning,jobCount:P.jobs.size,jobs:Array.from(P.jobs.entries()).map(([$,L])=>({name:$,schedule:L.job.config.rate||L.job.config.schedule,lastRun:L.lastRun,nextRun:L.nextRun,isRunning:L.isRunning}))}}function s5(){return P.isRunning}function t5(){return new Map(P.jobs)}async function r5($){let L=P.jobs.get($);if(!L)throw Error(`Scheduled job "${$}" not found`);T.info(`Manually triggering scheduled job: ${$}`),await n1($,{queue:L.job.config.queue||"default",payload:{},maxTries:L.job.config.tries||3,timeout:L.job.config.timeout||60})}v();v();import{log as a5}from"@stacksjs/logging";var o5={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function W4($={}){let L={...o5,...$},V=[],Y=new Date,G=Math.floor(Y.getTime()/1000);try{let{db:U}=await import("@stacksjs/database"),X=await U.selectFrom("jobs").selectAll().execute(),_=await U.selectFrom("failed_jobs").selectAll().execute(),K=new Map;for(let Q of X){let M=Q.queue||"default";if(!K.has(M))K.set(M,{pending:0,processing:0,delayed:0});let N=K.get(M);if(Q.reserved_at)N.processing++;else if(Q.available_at&&Q.available_at>G)N.delayed++;else if(N.pending++,Q.created_at){let q=typeof Q.created_at==="number"?Q.created_at:Math.floor(new Date(Q.created_at).getTime()/1000),Q4=G-q;if(!N.oldestAge||Q4>N.oldestAge)N.oldestAge=Q4}}let z=new Map;for(let Q of _){let M=Q.queue||"default";z.set(M,(z.get(M)||0)+1)}let Z=[],x=new Set([...K.keys(),...z.keys()]),A=L.queues?[...x].filter((Q)=>L.queues.includes(Q)):[...x],O=0,W=0,H=0,s=0;for(let Q of A){let M=K.get(Q)||{pending:0,processing:0,delayed:0},N=z.get(Q)||0;O+=M.pending,W+=M.processing,H+=M.delayed,s+=N;let q="healthy";if(M.pending>=L.maxPendingCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${Q}" has ${M.pending} pending jobs (threshold: ${L.maxPendingCritical})`,queue:Q,timestamp:Y.toISOString()});else if(M.pending>=L.maxPendingWarning)q="degraded",V.push({level:"warning",message:`Queue "${Q}" has ${M.pending} pending jobs (threshold: ${L.maxPendingWarning})`,queue:Q,timestamp:Y.toISOString()});if(N>=L.maxFailedCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${Q}" has ${N} failed jobs (threshold: ${L.maxFailedCritical})`,queue:Q,timestamp:Y.toISOString()});else if(N>=L.maxFailedWarning){if(q==="healthy")q="degraded";V.push({level:"warning",message:`Queue "${Q}" has ${N} failed jobs (threshold: ${L.maxFailedWarning})`,queue:Q,timestamp:Y.toISOString()})}if(M.oldestAge){if(M.oldestAge>=L.maxJobAgeCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${Q}" has a job waiting for ${Math.floor(M.oldestAge/3600)} hours`,queue:Q,timestamp:Y.toISOString()});else if(M.oldestAge>=L.maxJobAgeWarning){if(q==="healthy")q="degraded";V.push({level:"warning",message:`Queue "${Q}" has a job waiting for ${Math.floor(M.oldestAge/60)} minutes`,queue:Q,timestamp:Y.toISOString()})}}Z.push({name:Q,status:q,pending:M.pending,processing:M.processing,delayed:M.delayed,failed:N,oldestJobAge:M.oldestAge})}let x4=v1().getMetrics(),j2=x4.throughputPerMinute,m2=x4.averageDuration,H4=O+W+H+s,G1=H4>0?s/H4:0,t="healthy";if(Z.some((Q)=>Q.status==="unhealthy"))t="unhealthy";else if(Z.some((Q)=>Q.status==="degraded"))t="degraded";if(G1>=L.maxErrorRateCritical)t="unhealthy",V.push({level:"critical",message:`Overall error rate is ${(G1*100).toFixed(1)}% (threshold: ${L.maxErrorRateCritical*100}%)`,timestamp:Y.toISOString()});else if(G1>=L.maxErrorRateWarning){if(t==="healthy")t="degraded";V.push({level:"warning",message:`Overall error rate is ${(G1*100).toFixed(1)}% (threshold: ${L.maxErrorRateWarning*100}%)`,timestamp:Y.toISOString()})}let h2=O1().getAll().map((Q)=>({id:Q.id,status:Q.status,queue:Q.queue,processedCount:Q.processedCount,failedCount:Q.failedCount,lastActivityAt:Q.lastActivityAt}));return{status:t,timestamp:Y.toISOString(),queues:Z,workers:h2,metrics:{totalPending:O,totalProcessing:W,totalDelayed:H,totalFailed:s,throughputPerMinute:j2,averageProcessingTime:m2,errorRate:G1},alerts:V}}catch(U){a5.error("Failed to perform queue health check:",U);let _=O1().getAll().map((K)=>({id:K.id,status:K.status,queue:K.queue,processedCount:K.processedCount,failedCount:K.failedCount,lastActivityAt:K.lastActivityAt}));return{status:"unhealthy",timestamp:Y.toISOString(),queues:[],workers:_,metrics:{totalPending:0,totalProcessing:0,totalDelayed:0,totalFailed:0,throughputPerMinute:0,averageProcessingTime:0,errorRate:0},alerts:[{level:"critical",message:`Health check failed: ${U.message}`,timestamp:Y.toISOString()}]}}}function e5($={}){return async(L)=>{let V=await W4($),Y=V.status==="healthy"?200:V.status==="degraded"?207:503;return new Response(JSON.stringify(V,null,2),{status:Y,headers:{"Content-Type":"application/json","Cache-Control":"no-store"}})}}async function $8($={}){return(await W4($)).status==="healthy"}import{log as C}from"@stacksjs/logging";var L8={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function A1($){return String($).replace(/[&<>"']/g,(L)=>L8[L]??L)}class Z4{config;notificationCount=0;lastResetTime=Date.now();pendingBatch=[];batchTimeout=null;activeFlush=null;constructor($){this.config=$}async notify($){if(this.config.filter&&!this.config.filter($))return;if(this.config.rateLimit){let L=Date.now();if(L-this.lastResetTime>3600000)this.notificationCount=0,this.lastResetTime=L;if(this.notificationCount>=this.config.rateLimit){C.debug("Rate limit reached for failed job notifications");return}}if(this.config.batch){if(this.pendingBatch.push($),!this.batchTimeout)this.batchTimeout=setTimeout(()=>{this.batchTimeout=null,this.activeFlush=this.flushBatch().catch((L)=>C.error("Failed to flush notification batch:",L)).finally(()=>{this.activeFlush=null})},this.config.batchInterval||60000),this.batchTimeout.unref?.();return}await this.sendNotifications([$])}async flushBatch(){if(this.pendingBatch.length===0)return;let $=[...this.pendingBatch];if(this.pendingBatch=[],this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;await this.sendNotifications($)}async shutdown(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.activeFlush)await this.activeFlush.catch(()=>{});await this.flushBatch().catch(($)=>C.error("Failed to flush notification batch on shutdown:",$))}async sendNotifications($){let L=[];for(let V of this.config.channels)switch(V){case"email":if(this.config.email)L.push(this.sendEmail($));break;case"slack":if(this.config.slack)L.push(this.sendSlack($));break;case"discord":if(this.config.discord)L.push(this.sendDiscord($));break;case"webhook":if(this.config.webhook)L.push(this.sendWebhook($));break;case"log":this.logFailures($);break}try{await Promise.all(L)}catch(V){C.error("Failed to send job failure notifications:",V)}finally{this.notificationCount+=$.length}}async sendEmail($){let L=this.config.email,V=L.subject||`[Queue] ${$.length} Job(s) Failed`,Y=this.formatEmailBody($);try{let{mail:G}=await import("@stacksjs/email");await G.send({to:Array.isArray(L.to)?L.to:[L.to],from:L.from,subject:V,html:Y})}catch(G){C.error("Failed to send email notification:",G)}}formatEmailBody($){let L=$.map((V)=>`
2
+ var{defineProperty:h1,getOwnPropertyNames:iL,getOwnPropertyDescriptor:cL}=Object,dL=Object.prototype.hasOwnProperty;function nL(L){return this[L]}var sL=(L)=>{var $=(B4??=new WeakMap).get(L),V;if($)return $;if($=h1({},"__esModule",{value:!0}),L&&typeof L==="object"||typeof L==="function"){for(var Y of iL(L))if(!dL.call($,Y))h1($,Y,{get:nL.bind(L,Y),enumerable:!(V=cL(L,Y))||V.enumerable})}return B4.set(L,$),$},B4;var rL=(L)=>L;function tL(L,$){this[L]=rL.bind(null,$)}var p=(L,$)=>{for(var V in $)h1(L,V,{get:$[V],enumerable:!0,configurable:!0,set:tL.bind($,V)})};var v=(L,$)=>()=>(L&&($=L(L=0)),$);var H=import.meta.require;function y(L,$,V,Y){return{jobName:L,payload:$,options:V,envelopeVersion:1,dispatchedAt:new Date().toISOString(),...Y?{traceId:Y}:{}}}function j(L){try{return JSON.stringify(L)}catch($){throw aL(L,$)}}function _1(L){j(L)}function aL(L,$){if(eL($)){let G=null;try{G=g1(L,"",new Set)}catch{}let U=G?`\`${G.path}\` is a BigInt (${G.value}n)`:"it holds a BigInt";return Error(`[queue] Job "${L.jobName}" was not dispatched: ${U}, `+"and job envelopes travel as JSON, which cannot represent one. Convert it where you dispatch \u2014 "+"`String(value)` keeps every digit, `Number(value)` is exact below 2^53 \u2014 and convert it back inside "+"the job handler. See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts.",{cause:$})}if(L0($))return Error(`[queue] Job "${L.jobName}" was not dispatched: its payload contains a circular reference, which JSON cannot represent. Send the ids of the things the payload points at rather than the `+`objects themselves \u2014 that also keeps the job replayable once those objects have changed. ${"See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts."}`,{cause:$});let Y=$ instanceof Error?$.message:String($);return Error(`[queue] Job "${L.jobName}" was not dispatched: serializing its payload threw (${Y}). `+"JSON rejects only two things on its own and this was neither, so the throw came from the payload \u2014 "+"a `toJSON()` or a property getter. See the JSON round-trip contract in @stacksjs/queue's src/envelope.ts.",{cause:$})}function eL(L){return L instanceof TypeError&&/bigint/i.test(L.message)}function L0(L){return L instanceof TypeError&&/circular|cyclic/i.test(L.message)}function g1(L,$,V){if(typeof L==="bigint")return{path:$||"envelope",value:L};if(!L||typeof L!=="object")return null;if(V.has(L))return null;if(V.add(L),Array.isArray(L)){for(let Y=0;Y<L.length;Y++){let G=g1(L[Y],`${$}[${Y}]`,V);if(G)return G}return null}for(let[Y,G]of Object.entries(L)){let U=g1(G,$?`${$}.${Y}`:Y,V);if(U)return U}return null}function T4(L,$){if(p1.has(L))return;p1.add(L),console.warn($)}function $0(){p1.clear()}function T1(L){let $;if(typeof L==="string")try{$=JSON.parse(L)}catch{return{ok:!1,reason:"malformed",detail:"not valid JSON"}}else if(L&&typeof L==="object")$=L;else return{ok:!1,reason:"malformed",detail:`expected string or object, got ${typeof L}`};if($.envelopeVersion===1){if(typeof $.jobName!=="string")return{ok:!1,reason:"missing-job-name"};return{ok:!0,envelope:{jobName:$.jobName,payload:$.payload,options:$.options??void 0,envelopeVersion:1,dispatchedAt:typeof $.dispatchedAt==="string"?$.dispatchedAt:new Date(0).toISOString(),...typeof $.traceId==="string"&&$.traceId?{traceId:$.traceId}:{}},source:"v1"}}if(typeof $.envelopeVersion==="number"&&$.envelopeVersion>1)return{ok:!1,reason:"unknown-version",detail:`envelopeVersion=${$.envelopeVersion}, this worker speaks v1`};if(typeof $.jobName==="string")return T4("v0-implicit","[queue/envelope] Processing pre-#1884 job envelope without envelopeVersion. In-flight jobs from before the upgrade will continue to work; new dispatches use the v1 shape automatically."),{ok:!0,envelope:{jobName:$.jobName,payload:$.payload,options:$.options??void 0,envelopeVersion:1,dispatchedAt:typeof $.dispatchedAt==="string"?$.dispatchedAt:new Date(0).toISOString(),...typeof $.traceId==="string"&&$.traceId?{traceId:$.traceId}:{}},source:"v0-implicit"};if(typeof $.job==="string")return T4("laravel-legacy","[queue/envelope] Processing Laravel-legacy job envelope (`{ job, data }` shape). "+"Will continue to process but the queue table contains migration-era rows \u2014 "+"consider flushing once they drain."),{ok:!0,envelope:{jobName:$.job.replace(/^App\\+Jobs\\+/,"").replace(/^.*\\/,""),payload:$.data,options:void 0,envelopeVersion:1,dispatchedAt:new Date(0).toISOString()},source:"laravel-legacy"};return{ok:!1,reason:"missing-job-name"}}var oL=1,p1;var a=v(()=>{p1=new Set});var i1={};p(i1,{runJob:()=>R4,restore:()=>l1,isFaked:()=>J4,getFakeQueue:()=>N4,fake:()=>u1,expectJobToFail:()=>C4,createQueueTester:()=>S4,QueueTester:()=>E1});class E4{dispatchedJobs=[];pushedJobs=[];processedJobs=[];failedJobs=[];dispatch(L,$,V={}){this.dispatchedJobs.push({name:L,data:$,options:V,dispatchedAt:new Date,queue:V.queue||"default"})}push(L,$,V={}){this.pushedJobs.push({name:L,data:$,options:V,dispatchedAt:new Date,queue:V.queue||"default"})}dispatched(L){if(L)return this.dispatchedJobs.filter(($)=>$.name===L);return[...this.dispatchedJobs]}pushed(L){if(L)return this.pushedJobs.filter(($)=>$.name===L);return[...this.pushedJobs]}assertDispatched(L,$){let V=this.dispatched(L);if(V.length===0)throw Error(`Expected job "${L}" to be dispatched, but it was not.`);if($){if(V.filter($).length===0)throw Error(`Expected job "${L}" to be dispatched matching the callback, but no matching jobs were found.`)}}assertNotDispatched(L){let $=this.dispatched(L);if($.length>0)throw Error(`Expected job "${L}" to not be dispatched, but it was dispatched ${$.length} time(s).`)}assertDispatchedTimes(L,$){let V=this.dispatched(L);if(V.length!==$)throw Error(`Expected job "${L}" to be dispatched ${$} time(s), but it was dispatched ${V.length} time(s).`)}assertNothingDispatched(){if(this.dispatchedJobs.length>0){let L=[...new Set(this.dispatchedJobs.map(($)=>$.name))].join(", ");throw Error(`Expected no jobs to be dispatched, but found: ${L}`)}}assertPushed(L,$){let V=this.pushed(L);if(V.length===0)throw Error(`Expected job "${L}" to be pushed, but it was not.`);if($){if(V.filter($).length===0)throw Error(`Expected job "${L}" to be pushed matching the callback, but no matching jobs were found.`)}}assertPushedWithDelay(L,$){if(this.pushed(L).filter((G)=>G.options.delay===$).length===0)throw Error(`Expected job "${L}" to be pushed with delay ${$}ms, but no matching jobs were found.`)}assertPushedOn(L,$){if(this.pushed($).filter((Y)=>Y.queue===L).length===0)throw Error(`Expected job "${$}" to be pushed on queue "${L}", but it was not.`)}async processJob(L,$){let V=this.dispatchedJobs.find((Y)=>Y.name===L);if(!V)throw Error(`No dispatched job found with name "${L}"`);try{await $(V.data),this.processedJobs.push(V)}catch(Y){throw this.failedJobs.push({job:V,error:Y}),Y}}processed(L){if(L)return this.processedJobs.filter(($)=>$.name===L);return[...this.processedJobs]}failed(L){if(L)return this.failedJobs.filter(($)=>$.job.name===L);return[...this.failedJobs]}reset(){this.dispatchedJobs=[],this.pushedJobs=[],this.processedJobs=[],this.failedJobs=[]}}function u1(){return Z1=new E4,Z1}function N4(){return Z1}function J4(){return Z1!==null}function l1(){Z1=null}class E1{queue;constructor(){this.queue=u1()}dispatch(L,$,V={}){return this.queue.dispatch(L,$,V),this}push(L,$,V={}){return this.queue.push(L,$,V),this}assertDispatched(L,$){return this.queue.assertDispatched(L,$),this}assertNotDispatched(L){return this.queue.assertNotDispatched(L),this}assertDispatchedTimes(L,$){return this.queue.assertDispatchedTimes(L,$),this}assertNothingDispatched(){return this.queue.assertNothingDispatched(),this}dispatched(L){return this.queue.dispatched(L)}reset(){return this.queue.reset(),this}cleanup(){l1()}}function S4(){return new E1}async function R4(L,$){return await L.handle($)}async function C4(L,$,V){try{throw await L.handle($),Error("Expected job to fail, but it succeeded")}catch(Y){if(Y.message==="Expected job to fail, but it succeeded")throw Y;if(V){let G=Y.message;if(typeof V==="string"){if(!G.includes(V))throw Error(`Expected error to contain "${V}", got "${G}"`)}else if(!V.test(G))throw Error(`Expected error to match ${V}, got "${G}"`)}return Y}}var Z1=null;var O1={};p(O1,{setQueueManager:()=>_0,getQueueManager:()=>W0,dispatchSync:()=>X0,dispatchAfter:()=>K0,dispatch:()=>U0,createRedisDispatcher:()=>Z0,chain:()=>G0,batch:()=>Y0,StacksQueueManager:()=>q4,RedisQueue:()=>e,RedisJob:()=>k4,QueueManager:()=>z0});import{Queue as V0,batch as Y0,chain as G0,dispatch as U0,dispatchAfter as K0,dispatchSync as X0,getQueueManager as W0,QueueManager as z0,setQueueManager as _0}from"@stacksjs/bun-queue";import{log as c1}from"@stacksjs/logging";class e{queue;config;isProcessing=!1;constructor(L,$){this.config=$,this.queue=new V0(L,{driver:"redis",prefix:$.prefix,redis:$.redis?{url:$.redis.url||this.buildRedisUrl($.redis)}:void 0,defaultJobOptions:$.defaultJobOptions?{delay:$.defaultJobOptions.delay,attempts:$.defaultJobOptions.attempts,backoff:$.defaultJobOptions.backoff,removeOnComplete:$.defaultJobOptions.removeOnComplete,removeOnFail:$.defaultJobOptions.removeOnFail,priority:$.defaultJobOptions.priority,lifo:$.defaultJobOptions.lifo,timeout:$.defaultJobOptions.timeout,jobId:$.defaultJobOptions.jobId,dependsOn:$.defaultJobOptions.dependsOn,keepJobs:$.defaultJobOptions.keepJobs,deadLetter:$.defaultJobOptions.deadLetter}:void 0,limiter:$.limiter,metrics:$.metrics,stalledJobCheckInterval:$.stalledJobCheckInterval,maxStalledJobRetries:$.maxStalledJobRetries,distributedLock:$.distributedLock,defaultDeadLetterOptions:$.defaultDeadLetterOptions,horizontalScaling:$.horizontalScaling,logLevel:$.logLevel}),c1.debug(`Redis queue "${L}" initialized`)}buildRedisUrl(L){let $=L.host||"localhost",V=L.port||6379,Y=L.password?`:${encodeURIComponent(L.password)}@`:"",G=Number.isFinite(L.db)&&L.db>=0?L.db:0;return`redis://${Y}${$}:${V}/${G}`}async add(L,$){let V={delay:$?.delay?$.delay*1000:void 0,attempts:$?.maxTries,priority:$?.priority,timeout:$?.timeout?$.timeout*1000:void 0,backoff:Array.isArray($?.backoff)?$.backoff.map((Y)=>(Number(Y)||1)*1000):$?.backoff};return this.queue.add(L,V)}process(L,$){if(this.isProcessing){c1.warn("Queue is already processing");return}this.isProcessing=!0,this.queue.process(L,$),c1.info(`Started processing queue with concurrency ${L}`)}async getJob(L){return this.queue.getJob(L)}async getJobs(L){return this.queue.getJobs(L)}async getJobCounts(){return this.queue.getJobCounts()}async removeJob(L){return this.queue.removeJob(L)}async pause(){return this.queue.pause()}async resume(){return this.queue.resume()}async empty(){return this.queue.empty()}async close(){return this.isProcessing=!1,this.queue.close()}async getMetrics(){return this.queue.getMetrics()}async ping(){return this.queue.ping()}async scheduleCron(L){return this.queue.scheduleCron({cronExpression:L.cron,data:L.data,timezone:L.tz,jobId:L.name})}async unscheduleCron(L){return this.queue.unscheduleCron(L)}async getDeadLetterJobs(){return this.queue.getDeadLetterJobs()}async republishDeadLetterJob(L){return this.queue.republishDeadLetterJob(L)}async clearDeadLetterQueue(){return this.queue.clearDeadLetterQueue()}async bulkRemove(L){return this.queue.bulkRemove(L)}async getClusterInfo(){return this.queue.getClusterInfo()}isLeader(){return this.queue.isLeader()}getQueue(){return this.queue}on(L,$){this.queue.events.on(L,$)}}class q4{config;queues=new Map;defaultConnection="default";constructor(L){this.config=L;for(let[$,V]of Object.entries(L))if(V.driver==="redis")this.queues.set($,new e($,V))}queue(L){let $=L||this.defaultConnection,V=this.queues.get($);if(!V){let Y=this.config[$];if(!Y)throw Error(`Queue "${$}" not configured`);V=new e($,Y),this.queues.set($,V)}return V}setDefaultConnection(L){this.defaultConnection=L}async closeAll(){let L=Array.from(this.queues.values()).map(($)=>$.close());await Promise.all(L),this.queues.clear()}}function Z0(L,$){let V=new e(L,$);return async(Y,G)=>{return V.add(Y,G)}}class k4{data;options={};queue;constructor(L,$,V){this.data=V;this.queue=new e(L,$)}async dispatch(){await this.queue.add(this.data,this.options)}async dispatchNow(){await this.queue.add(this.data,{...this.options,immediate:!0})}delay(L){return this.options.delay=L,this}afterResponse(){return this.options.afterResponse=!0,this}chain(L){return this.options.chainedJobs=L,this}onQueue(L){return this.options.queue=L,this}priority(L){return this.options.priority=L,this}tries(L){return this.options.maxTries=L,this}timeout(L){return this.options.timeout=L,this}backoff(L){return this.options.backoff=L,this}}var F1=()=>{};function B(L){let $=L,V=$?.errno;if(typeof V==="string"&&V.toUpperCase()==="42P01")return!0;if(typeof V==="number"&&V===1146)return!0;if((typeof $?.code==="string"?$.code.toUpperCase():"")==="42P01")return!0;let G=$?.message??"";return G.includes("no such table")||G.includes("doesn't exist")||G.includes("does not exist")}var m4={};p(m4,{retryDeadLetterJob:()=>f4,purgeDeadLetterJobs:()=>j4,moveToDeadLetter:()=>J1,listDeadLetterJobs:()=>w4});import{db as L1}from"@stacksjs/database";function N1(){if(y4)return;y4=!0,console.warn("[queue/dlq] dead_letter_jobs table missing \u2014 DLQ disabled. "+"Run migrations to enable poison-message isolation.")}async function J1(L,$,V=1){let Y=new Date().toISOString().slice(0,19).replace("T"," ");try{return await L1.insertInto("dead_letter_jobs").values({uuid:L.uuid??crypto.randomUUID(),connection:L.connection??"database",queue:L.queue??"default",payload:L.payload??"{}",exception:L.exception??"unknown",reason:$,total_failures:V,first_failed_at:L.failed_at??Y,last_failed_at:Y,dead_lettered_at:Y}).execute(),!0}catch(G){if(B(G))return N1(),!1;throw G}}async function w4(L={}){try{let $=L1.selectFrom("dead_letter_jobs").selectAll();if(L.queue)$=$.where("queue","=",L.queue);if(L.reason)$=$.where("reason","=",L.reason);if(L.sinceCutoffMs){let Y=new Date(L.sinceCutoffMs).toISOString().slice(0,19).replace("T"," ");$=$.where("dead_lettered_at",">=",Y)}if(L.limit&&L.limit>0)$=$.limit(L.limit);return await $.execute()??[]}catch($){if(B($))return N1(),[];throw $}}async function f4(L){try{let $=await L1.selectFrom("dead_letter_jobs").where("id","=",L).selectAll().executeTakeFirst();if(!$)return!1;let V=Math.floor(Date.now()/1000);return await L1.insertInto("jobs").values({queue:$.queue,payload:$.payload,attempts:0,reserved_at:null,available_at:V,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),await L1.deleteFrom("dead_letter_jobs").where("id","=",L).execute(),!0}catch($){if(B($))return N1(),!1;throw $}}async function j4(L=30){try{let $=new Date(Date.now()-L*24*60*60*1000).toISOString().slice(0,19).replace("T"," "),V=await L1.deleteFrom("dead_letter_jobs").where("dead_lettered_at","<",$).execute();return Number(V?.numDeletedRows??V?.[0]?.numDeletedRows??V?.affectedRows??0)}catch($){if(B($))return N1(),0;throw $}}var y4=!1;var S1=()=>{};function d1(){if(h4)return;h4=!0,console.warn("[queue/idempotency] job_idempotency table missing \u2014 idempotency keys are accepted but NOT enforced. "+"Run migrations to enable dedup.")}async function F0(L){try{let{db:$}=await import("@stacksjs/database"),V=await $.selectFrom("job_idempotency").where("idempotency_key","=",L).select(["idempotency_key"]).executeTakeFirst();return Boolean(V)}catch($){if(B($))return d1(),!1;throw $}}async function H0(L,$,V){try{let{db:Y}=await import("@stacksjs/database");await Y.insertInto("job_idempotency").values({idempotency_key:L,job_name:$,queue:V??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}catch(Y){if(B(Y)){d1();return}let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return;throw Y}}async function n1(L,$,V){try{let{db:Y}=await import("@stacksjs/database");return await Y.insertInto("job_idempotency").values({idempotency_key:L,job_name:$,queue:V??"default",dispatched_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute(),"claimed"}catch(Y){if(B(Y))return d1(),"unenforced";let G=Y?.message??"";if(G.includes("UNIQUE constraint")||G.includes("Duplicate entry"))return"duplicate";throw Y}}async function s1(L){try{let{db:$}=await import("@stacksjs/database");await $.deleteFrom("job_idempotency").where("idempotency_key","=",L).execute()}catch{}}var h4=!1;var r1=()=>{};var i4={};p(i4,{unquarantineJob:()=>u4,recordFailureForPoison:()=>p4,quarantineJob:()=>b4,listQuarantined:()=>l4,isQuarantined:()=>R1,hashPayload:()=>Q1});import{createHash as Q0}from"crypto";import{db as R}from"@stacksjs/database";function H1(){if(g4)return;g4=!0,console.warn("[queue/poison] job_quarantine table missing \u2014 poison detection disabled. "+"Run migrations to enable.")}function Q1(L){let $;try{$=typeof L==="string"?L:JSON.stringify(L??null)}catch{$=String(L)}return Q0("sha256").update($).digest("hex").slice(0,32)}async function p4(L,$,V={}){let Y=V.maxFailures??5,G=V.windowMinutes??60,U=Q1($),K=new Date,z=K.toISOString().slice(0,19).replace("T"," ");try{let X=await R.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","=",U).selectAll().executeTakeFirst();if(!X)return await R.insertInto("job_quarantine").values({job_name:L,payload_hash:U,failure_count:1,window_start:z,quarantined_at:null}).execute(),!1;if(X.quarantined_at)return!0;let W=Date.parse(X.window_start.replace(" ","T")+"Z"),_=K.getTime()-W,A=G*60*1000;if(Number.isFinite(W)&&_>A)return await R.updateTable("job_quarantine").set({failure_count:1,window_start:z}).where("id","=",X.id).execute(),!1;let x=X.failure_count+1;if(x>=Y)return await R.updateTable("job_quarantine").set({failure_count:x,quarantined_at:z}).where("id","=",X.id).execute(),!0;return await R.updateTable("job_quarantine").set({failure_count:x}).where("id","=",X.id).execute(),!1}catch(X){if(B(X))return H1(),!1;throw X}}async function R1(L,$){let V=Q1($);try{let Y=await R.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","in",[V,"*"]).whereNotNull("quarantined_at").select(["id"]).executeTakeFirst();return Boolean(Y)}catch(Y){if(B(Y))return H1(),!1;throw Y}}async function b4(L,$){let V=$===void 0?"*":Q1($),Y=new Date().toISOString().slice(0,19).replace("T"," ");try{let G=await R.selectFrom("job_quarantine").where("job_name","=",L).where("payload_hash","=",V).select(["id"]).executeTakeFirst();if(G)await R.updateTable("job_quarantine").set({quarantined_at:Y}).where("id","=",G.id).execute();else await R.insertInto("job_quarantine").values({job_name:L,payload_hash:V,failure_count:0,window_start:Y,quarantined_at:Y}).execute()}catch(G){if(B(G)){H1();return}throw G}}async function u4(L){try{await R.deleteFrom("job_quarantine").where("job_name","=",L).execute()}catch($){if(B($)){H1();return}throw $}}async function l4(){try{return await R.selectFrom("job_quarantine").whereNotNull("quarantined_at").selectAll().execute()??[]}catch(L){if(B(L))return H1(),[];throw L}}var g4=!1;var C1=()=>{};function c(L){let $=L?.numUpdatedRows;if($===null||$===void 0)return 0;if(typeof $==="object")return Number($.changes??0);return Number($)}function A0(L,$){let V=y(L,$.payload||{},{queue:$.queue,tries:$.maxTries,timeout:$.timeout,backoff:Array.isArray($.backoff)?$.backoff:void 0});return{queue:$.queue||"default",payload:j(V),attempts:0,available_at:D0($.delay||0),created_at:new Date().toISOString().slice(0,19).replace("T"," ")}}async function t1(L,$){let V=A0(L,$),{db:Y}=await import("@stacksjs/database");await Y.insertInto("jobs").values(V).execute()}function D0(L){let $=Date.now();return Math.floor($/1000+L)}var q1=v(()=>{a()});var b={};p(b,{withEvents:()=>d4,onQueueEvent:()=>c4,getWorkerTracker:()=>A1,getQueueEvents:()=>$1,getGlobalMetrics:()=>v1,emitQueueEvent:()=>d,QueueMetrics:()=>I1,QueueEvents:()=>k1,OnQueueEvent:()=>n4});import{log as m}from"@stacksjs/logging";class k1{handlers=new Map;wildcardHandlers=new Set;listenerSubscriptions=new WeakMap;reclaim=new FinalizationRegistry((L)=>{L()});on(L,$){if(!this.handlers.has(L))this.handlers.set(L,new Set);return this.handlers.get(L).add($),()=>{this.handlers.get(L)?.delete($)}}subscribeListener(L,$,V){let Y=new WeakRef(L),G=(X)=>{let W=Y.deref();if(W===void 0){U();return}return V.call(W,X)},U=this.on($,G),K=()=>{U();let X=Y.deref();if(X===void 0)return;let W=this.listenerSubscriptions.get(X);if(!W)return;if(W.delete(K),W.size===0)this.listenerSubscriptions.delete(X),this.reclaim.unregister(X)},z=this.listenerSubscriptions.get(L);if(!z)z=new Set,this.listenerSubscriptions.set(L,z);return z.add(K),this.reclaim.register(L,U,L),K}unsubscribeListener(L){let $=this.listenerSubscriptions.get(L);if(!$)return 0;let V=Array.from($);for(let Y of V)Y();return this.listenerSubscriptions.delete(L),this.reclaim.unregister(L),V.length}listenerCount(L){if(L==="*")return this.wildcardHandlers.size;return this.handlers.get(L)?.size??0}onAny(L){return this.wildcardHandlers.add(L),()=>{this.wildcardHandlers.delete(L)}}once(L,$){let V=async(Y)=>{this.handlers.get(L)?.delete(V),await $(Y)};return this.on(L,V)}async emit(L,$){let V={...$,timestamp:Date.now()};this.logEvent(L,V);let Y=this.handlers.get(L);if(Y)for(let G of Y)try{await G(V)}catch(U){m.error(`Error in queue event handler for ${L}:`,U)}for(let G of this.wildcardHandlers)try{await G(L,V)}catch(U){m.error("Error in wildcard queue event handler:",U)}}logEvent(L,$){let V=$.jobId?`[${$.jobId}]`:"",Y=$.queueName?`on ${$.queueName}`:"";switch(L){case"job:added":m.debug(`Job added ${V} ${Y}`);break;case"job:processing":m.debug(`Job processing ${V} ${Y}`);break;case"job:completed":m.info(`Job completed ${V} ${Y} in ${$.duration}ms`);break;case"job:failed":m.error(`Job failed ${V} ${Y}:`,$.error);break;case"job:retrying":m.warn(`Job retrying ${V} ${Y} (attempt ${$.attemptsMade})`);break;case"job:stalled":m.warn(`Job stalled ${V} ${Y}`);break;case"queue:error":m.error(`Queue error ${Y}:`,$.error);break}}off(L){this.handlers.delete(L)}removeAllListeners(){this.handlers.clear(),this.wildcardHandlers.clear(),this.listenerSubscriptions=new WeakMap}}function $1(){if(!o1)o1=new k1;return o1}function c4(L,$){let V=$1();if(L==="*")return V.onAny($);return V.on(L,$)}function d(L,$){return $1().emit(L,$)}function d4(L,$){return async(...V)=>{let Y=V[0]?.id||"unknown",G=Date.now();await d("job:processing",{jobId:Y,queueName:L,data:V[0]?.data});try{let U=await $(...V);return await d("job:completed",{jobId:Y,queueName:L,result:U,duration:Date.now()-G}),U}catch(U){throw await d("job:failed",{jobId:Y,queueName:L,error:U,duration:Date.now()-G}),U}}}function n4(L){return function($,V){if(typeof V!=="object"||V===null)throw TypeError(`@OnQueueEvent('${L}') requires standard (TC39) decorators. Legacy decorators ('experimentalDecorators: true') give a method decorator no construction-time hook, so the handler cannot be bound to an instance.`);if(V.kind!=="method")throw TypeError(`@OnQueueEvent('${L}') can only decorate a class method, but it was applied to a ${V.kind}${V.name===void 0?"":` ('${String(V.name)}')`}. Move the handler into a method, or call onQueueEvent('${L}', handler) directly.`);if(V.static)throw TypeError(`@OnQueueEvent('${L}') cannot decorate the static method '${String(V.name)}': a static method has no instance, so it would subscribe at class-definition time and stay subscribed for the life of the process, even if the class is never used. Use an instance method, or subscribe explicitly with onQueueEvent('${L}', MyClass.${String(V.name)}).`);return V.addInitializer(function(){$1().subscribeListener(this,L,$)}),$}}class I1{jobCounts={added:0,completed:0,failed:0,processing:0};completions=[];errors=[];unsubscribe=[];constructor(){this.setupListeners()}setupListeners(){let L=$1();this.unsubscribe.push(L.on("job:added",()=>{this.jobCounts.added++}),L.on("job:processing",()=>{this.jobCounts.processing++}),L.on("job:completed",($)=>{this.jobCounts.completed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1);let V=$.duration||0;if(this.completions.push({timestamp:Date.now(),duration:V}),this.completions.length>1000)this.completions.shift()}),L.on("job:failed",($)=>{if(this.jobCounts.failed++,this.jobCounts.processing=Math.max(0,this.jobCounts.processing-1),$.error){if(this.errors.push({error:$.error,timestamp:Date.now()}),this.errors.length>100)this.errors.shift()}}))}getThroughputPerMinute(){let L=Date.now()-60000;return this.completions.filter((V)=>V.timestamp>=L).length}getAverageProcessingTime(){let L=Date.now()-60000,$=this.completions.filter((V)=>V.timestamp>=L);if($.length===0)return 0;return $.reduce((V,Y)=>V+Y.duration,0)/$.length}getMetrics(){return{counts:{...this.jobCounts},averageDuration:this.getAverageProcessingTime(),recentErrors:[...this.errors],throughputPerMinute:this.getThroughputPerMinute()}}reset(){this.jobCounts={added:0,completed:0,failed:0,processing:0},this.completions=[],this.errors=[]}stop(){this.unsubscribe.forEach((L)=>L()),this.unsubscribe=[]}}function v1(){if(!a1)a1=new I1;return a1}class s4{workers=new Map;register(L,$){this.workers.set(L,{id:L,status:"idle",queue:$,processedCount:0,failedCount:0,lastActivityAt:new Date().toISOString(),startedAt:new Date().toISOString()})}markActive(L){let $=this.workers.get(L);if($)$.status="active",$.lastActivityAt=new Date().toISOString()}markIdle(L){let $=this.workers.get(L);if($)$.status="idle",$.lastActivityAt=new Date().toISOString()}recordCompletion(L){let $=this.workers.get(L);if($)$.processedCount++,$.lastActivityAt=new Date().toISOString()}recordFailure(L){let $=this.workers.get(L);if($)$.failedCount++,$.lastActivityAt=new Date().toISOString()}unregister(L){let $=this.workers.get(L);if($)$.status="stopped"}getAll(){return Array.from(this.workers.values())}clear(){this.workers.clear()}}function A1(){return x0}var o1=null,a1=null,x0;var k=v(()=>{x0=new s4});var l={};p(l,{recordBatchJobFailure:()=>U4,recordBatchJobCompletion:()=>G4,isBatchCancelled:()=>UL,getBatchCallbacks:()=>D1,batchRecordToHash:()=>$L,batchRecordFromHash:()=>Y4,batchCounterIncrements:()=>r4,PendingBatch:()=>w1,DispatchedBatch:()=>u,Batch:()=>e1});var{RedisClient:P0}=globalThis.Bun;import{log as T}from"@stacksjs/logging";import{env as M0}from"@stacksjs/env";function h(){return M0.QUEUE_DRIVER||"sync"}class w1{jobs;options={thenCallbacks:[],catchCallbacks:[],finallyCallbacks:[],progressCallbacks:[]};constructor(L){this.jobs=L.map(($)=>("job"in $)?$:{job:$})}name(L){return this.options.name=L,this}onQueue(L){return this.options.queue=L,this}allowFailures(){return this.options.allowFailures=!0,this}then(L){return this.options.thenCallbacks.push(L),this}catch(L){return this.options.catchCallbacks.push(L),this}finally(L){return this.options.finallyCallbacks.push(L),this}progress(L){return this.options.progressCallbacks.push(L),this}thenHandler(L){return this.options.thenHandler=L,this}catchHandler(L){return this.options.catchHandler=L,this}finallyHandler(L){return this.options.finallyHandler=L,this}async dispatch(){let L=crypto.randomUUID(),$=this.jobs.length;if($===0)throw Error("Cannot dispatch an empty batch");let V=h();await T0({id:L,name:this.options.name||"",total_jobs:$,pending_jobs:$,failed_jobs:0,failed_job_ids:"[]",options:JSON.stringify({queue:this.options.queue,allowFailures:this.options.allowFailures||!1}),cancelled_at:null,created_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:null,then_handler:this.options.thenHandler?JSON.stringify(this.options.thenHandler):null,catch_handler:this.options.catchHandler?JSON.stringify(this.options.catchHandler):null,finally_handler:this.options.finallyHandler?JSON.stringify(this.options.finallyHandler):null}),B0(L,this.options);try{let{emitQueueEvent:Y}=await Promise.resolve().then(() => (k(),b));await Y("batch:added",{jobId:L,data:{name:this.options.name,totalJobs:$}})}catch{}for(let Y=0;Y<this.jobs.length;Y++){let G=this.jobs[Y];if(!G)continue;let{job:U,payload:K}=G,z={...K,_batchId:L,_batchIndex:Y};if(this.options.queue&&!U.queue)U.queue=this.options.queue;if(V==="sync")try{await U.dispatchNow(z),await G4(L)}catch(X){await U4(L,`${L}:${Y}`,X)}else await U.dispatch(z)}return T.info(`[Batch] Dispatched batch "${this.options.name||L}" with ${$} jobs`),new u(L)}getJobs(){return[...this.jobs]}getOptions(){return this.options}}class u{id;constructor(L){this.id=L}async fresh(){return n(this.id)}async getName(){return(await this.fresh())?.name||""}async totalJobs(){return(await this.fresh())?.total_jobs||0}async pendingJobs(){return(await this.fresh())?.pending_jobs||0}async failedJobs(){return(await this.fresh())?.failed_jobs||0}async completedJobs(){let L=await this.fresh();if(!L)return 0;return L.total_jobs-L.pending_jobs}async progress(){let L=await this.fresh();if(!L||L.total_jobs===0)return 0;let $=L.total_jobs-L.pending_jobs;return Math.round($/L.total_jobs*100)}async finished(){return(await this.fresh())?.finished_at!==null}async cancelled(){return(await this.fresh())?.cancelled_at!==null}async hasFailures(){return((await this.fresh())?.failed_jobs||0)>0}async failedJobIds(){let L=await this.fresh();if(!L)return[];try{return JSON.parse(L.failed_job_ids||"[]")}catch{return[]}}async cancel(){if(h()==="redis")await y0(this.id);else await q0(this.id);T.info(`[Batch] Cancelled batch ${this.id}`);let $=D1(this.id);if($)for(let V of $.finallyCallbacks)try{await V(this)}catch(Y){T.error(`[Batch] Error in finally callback for batch ${this.id}:`,Y)}}async add(L){let $=await this.fresh();if(!$)throw Error(`Batch ${this.id} not found`);if($.cancelled_at)throw Error(`Batch ${this.id} has been cancelled`);if($.finished_at)throw Error(`Batch ${this.id} has already finished`);let V=L.map((U)=>("job"in U)?U:{job:U});if(!await J0(this.id,V.length))throw Error(`Batch ${this.id} was cancelled, finished or deleted before the jobs could be added`);let G=JSON.parse($.options||"{}");for(let U=0;U<V.length;U++){let K=V[U];if(!K)continue;let{job:z,payload:X}=K,W={...X,_batchId:this.id,_batchIndex:$.total_jobs+U};if(G.queue&&!z.queue)z.queue=G.queue;await z.dispatch(W)}T.info(`[Batch] Added ${V.length} jobs to batch ${this.id}`)}async delete(){await S0(this.id),y1(this.id)}}class e1{static create(L){return new w1(L)}static async find(L){if(!await n(L))return null;return new u(L)}static async all(){return(await E0()).map(($)=>new u($.id))}static async prune(L=24){return R0(L)}}function B0(L,$){L4.set(L,$)}function D1(L){return L4.get(L)}function y1(L){L4.delete(L)}async function T0(L){if(h()==="redis")await I0(L);else await t4(L)}async function n(L){if(h()==="redis")return v0(L);return o4(L)}async function E0(){if(h()==="redis")return VL();return a4()}async function N0(L,$){if(h()==="redis")await YL(L,$);else await $4(L,$)}async function J0(L,$){if($===0)return!0;if(h()==="redis")try{let U=await U1(),K=`${G1}${L}`;return await U.hincrby(K,"total_jobs",$),await U.hincrby(K,"pending_jobs",$),U.close(),!0}catch{}let{db:V,sql:Y}=await import("@stacksjs/database"),G=await V.updateTable("job_batches").set(r4(Y,$)).where("id","=",L).whereNull("cancelled_at").whereNull("finished_at").executeTakeFirst();return c(G)>0}function r4(L,$){return{total_jobs:L`total_jobs + ${$}`,pending_jobs:L`pending_jobs + ${$}`}}async function S0(L){if(h()==="redis")await GL(L);else await e4(L)}async function R0(L){if(h()==="redis")return w0(L);return LL(L)}function C0(L){return!!(L.then_handler||L.catch_handler||L.finally_handler)}async function t4(L){let{db:$}=await import("@stacksjs/database"),V={id:L.id,name:L.name,total_jobs:L.total_jobs,pending_jobs:L.pending_jobs,failed_jobs:L.failed_jobs,failed_job_ids:L.failed_job_ids,options:L.options,cancelled_at:L.cancelled_at,created_at:L.created_at,finished_at:L.finished_at};if(!C0(L)){await $.insertInto("job_batches").values(V).execute();return}try{await $.insertInto("job_batches").values({...V,then_handler:L.then_handler??null,catch_handler:L.catch_handler??null,finally_handler:L.finally_handler??null}).execute()}catch(Y){T.warn(`[Batch] Could not persist terminal handlers for batch ${L.id}: ${Y?.message}. The job_batches table predates the then_handler/catch_handler/finally_handler columns; recreate it to enable handlers that survive a worker restart.`),await $.insertInto("job_batches").values(V).execute()}}async function o4(L){let{db:$}=await import("@stacksjs/database");return await $.selectFrom("job_batches").where("id","=",L).selectAll().executeTakeFirst()||null}async function a4(){let{db:L}=await import("@stacksjs/database");return await L.selectFrom("job_batches").selectAll().orderBy("created_at","desc").execute()}async function $4(L,$){let{db:V}=await import("@stacksjs/database");await V.updateTable("job_batches").set($).where("id","=",L).execute()}async function e4(L){let{db:$}=await import("@stacksjs/database");await $.deleteFrom("job_batches").where("id","=",L).execute()}async function q0(L){await $4(L,{cancelled_at:new Date().toISOString().slice(0,19).replace("T"," "),finished_at:new Date().toISOString().slice(0,19).replace("T"," ")})}async function LL(L){let{db:$}=await import("@stacksjs/database"),V=new Date(Date.now()-L*60*60*1000).toISOString().slice(0,19).replace("T"," "),Y=await $.deleteFrom("job_batches").whereNotNull("finished_at").where("finished_at","<",V).executeTakeFirst();return Number(Y?.numDeletedRows??0)}async function k0(){let{queue:L}=await import("@stacksjs/config"),$=L?.connections?.redis?.redis;if($?.url)return $.url;let V=$?.password?`:${encodeURIComponent($.password)}@`:"",Y=$?.db?`/${$.db}`:"";return`redis://${V}${$?.host||"localhost"}:${$?.port||6379}${Y}`}async function U1(){let L=new P0(await k0());return await L.connect(),L}function $L(L){return{id:L.id,name:L.name,total_jobs:String(L.total_jobs),pending_jobs:String(L.pending_jobs),failed_jobs:String(L.failed_jobs),failed_job_ids:L.failed_job_ids,options:L.options,cancelled_at:L.cancelled_at||"",created_at:L.created_at,finished_at:L.finished_at||"",then_handler:L.then_handler||"",catch_handler:L.catch_handler||"",finally_handler:L.finally_handler||""}}async function I0(L){try{let $=await U1();await $.hset(`${G1}${L.id}`,$L(L)),await $.sadd(V4,L.id),$.close()}catch{await t4(L)}}function Y4(L){if(!L?.id)return null;return{id:L.id,name:L.name??"",total_jobs:Number(L.total_jobs),pending_jobs:Number(L.pending_jobs),failed_jobs:Number(L.failed_jobs),failed_job_ids:L.failed_job_ids??"",options:L.options??"",cancelled_at:L.cancelled_at||null,created_at:L.created_at??"",finished_at:L.finished_at||null,then_handler:L.then_handler||null,catch_handler:L.catch_handler||null,finally_handler:L.finally_handler||null}}async function v0(L){try{let $=await U1(),V=`${G1}${L}`,Y=await $.hgetall(V);return $.close(),Y4(Y)}catch{return o4(L)}}async function VL(){try{let L=await U1(),$=await L.smembers(V4),V=[];for(let Y of $){let G=Y4(await L.hgetall(`${G1}${Y}`));if(G)V.push(G)}return L.close(),V}catch{return a4()}}async function YL(L,$){try{let V=await U1(),Y=`${G1}${L}`,G={};for(let[U,K]of Object.entries($))G[U]=K===null?"":String(K);await V.hset(Y,G),V.close()}catch{await $4(L,$)}}async function GL(L){try{let $=await U1();await $.del(`${G1}${L}`),await $.srem(V4,L),$.close()}catch{await e4(L)}}async function y0(L){let $=new Date().toISOString().slice(0,19).replace("T"," ");await YL(L,{cancelled_at:$,finished_at:$})}async function w0(L){try{let $=await VL(),V=Date.now()-L*60*60*1000,Y=0;for(let G of $)if(G.finished_at){if(new Date(G.finished_at).getTime()<V)await GL(G.id),Y++}return Y}catch{return LL(L)}}function V1(L){if(!L)return null;try{let $=JSON.parse(L);if($&&($.kind==="job"||$.kind==="module"))return $;return T.warn(`[Batch] handler JSON has unknown kind '${$?.kind}' \u2014 skipping`),null}catch($){return T.warn(`[Batch] failed to parse persistent handler: ${$.message}`),null}}async function Y1(L,$){try{if(L.kind==="job"){let{Jobs:G}=await Promise.resolve().then(() => (x1(),f1));await G.dispatch(L.name,{...L.payload??{},_batchId:$});return}let V=await import(L.module).catch((G)=>{return T.warn(`[Batch] persistent handler module not found: ${L.module} (${G.message})`),null});if(!V)return;let Y=V[L.export];if(typeof Y!=="function"){T.warn(`[Batch] persistent handler export '${L.export}' is not a function on ${L.module}`);return}await Y(L.payload,$)}catch(V){T.error(`[Batch] persistent handler threw for batch ${$}:`,V)}}async function G4(L){let{db:$,sql:V}=await import("@stacksjs/database");await $.updateTable("job_batches").set({pending_jobs:V`GREATEST(pending_jobs - 1, 0)`}).where("id","=",L).where("pending_jobs",">",0).execute();let Y=new Date().toISOString().slice(0,19).replace("T"," "),G=await $.updateTable("job_batches").set({finished_at:Y}).where("id","=",L).where("pending_jobs","=",0).whereNull("finished_at").executeTakeFirst(),U=c(G)>0,K=D1(L),z=new u(L);if(K)for(let X of K.progressCallbacks)try{await X(z)}catch(W){T.error(`[Batch] Error in progress callback for batch ${L}:`,W)}if(U){try{let{emitQueueEvent:O}=await Promise.resolve().then(() => (k(),b));await O("batch:completed",{jobId:L})}catch{}let X=await n(L);if(!X){y1(L),T.info(`[Batch] Batch ${L} finished (record vanished)`);return}let W=JSON.parse(X.options||"{}"),A=!((X.failed_jobs||0)>0)||W.allowFailures;if(K){if(A)for(let O of K.thenCallbacks)try{await O(z)}catch(Z){T.error(`[Batch] Error in then callback for batch ${L}:`,Z)}for(let O of K.finallyCallbacks)try{await O(z)}catch(Z){T.error(`[Batch] Error in finally callback for batch ${L}:`,Z)}y1(L)}if(A){let O=V1(X.then_handler);if(O)await Y1(O,L)}else{let O=V1(X.catch_handler);if(O)await Y1(O,L)}let x=V1(X.finally_handler);if(x)await Y1(x,L);T.info(`[Batch] Batch ${L} finished`)}}async function U4(L,$,V){let{db:Y,sql:G}=await import("@stacksjs/database"),U=await n(L);if(!U)return;let K=JSON.parse(U.options||"{}");await Y.updateTable("job_batches").set({pending_jobs:G`GREATEST(pending_jobs - 1, 0)`,failed_jobs:G`failed_jobs + 1`}).where("id","=",L).where("pending_jobs",">",0).execute();try{let Z=await n(L);if(Z){let F=[];try{F=JSON.parse(Z.failed_job_ids||"[]")}catch{F=[]}F.push($),await N0(L,{failed_job_ids:JSON.stringify(F)})}}catch{}let z=new u(L),X=D1(L);if(X)for(let Z of X.catchCallbacks)try{await Z(z,V)}catch(F){T.error(`[Batch] Error in catch callback for batch ${L}:`,F)}try{let{emitQueueEvent:Z}=await Promise.resolve().then(() => (k(),b));await Z("batch:failed",{jobId:L,error:V})}catch{}let W=new Date().toISOString().slice(0,19).replace("T"," "),_=Y.updateTable("job_batches").set(K.allowFailures?{finished_at:W}:{finished_at:W,cancelled_at:W}).where("id","=",L).whereNull("finished_at");if(K.allowFailures)_=_.where("pending_jobs","=",0);let A=await _.executeTakeFirst();if(c(A)===0)return;let x=!!K.allowFailures;if(X){if(x)for(let Z of X.thenCallbacks)try{await Z(z)}catch(F){T.error(`[Batch] Error in then callback for batch ${L}:`,F)}for(let Z of X.finallyCallbacks)try{await Z(z)}catch(F){T.error(`[Batch] Error in finally callback for batch ${L}:`,F)}y1(L)}let O=await n(L);if(O){if(x){let F=V1(O.then_handler);if(F)await Y1(F,L)}else{let F=V1(O.catch_handler);if(F)await Y1(F,L)}let Z=V1(O.finally_handler);if(Z)await Y1(Z,L)}T.info(`[Batch] Batch ${L} finished with failure(s)`)}async function UL(L){return(await n(L))?.cancelled_at!==null}var L4,G1="stacks:batch:",V4="stacks:batches";var g=v(()=>{q1();L4=new Map});var f1={};p(f1,{runJob:()=>j1,resolveJobFile:()=>ZL,jobBatch:()=>_L,job:()=>WL,Jobs:()=>zL});import{appPath as f0,frameworkPath as j0}from"@stacksjs/path";import{env as m0}from"@stacksjs/env";import{enqueueAfterCommit as h0,isInTransaction as g0}from"@stacksjs/database";function p0(){return m0.QUEUE_DRIVER||"sync"}function b0(){if(KL)return;KL=!0,console.warn("[queue] .afterCommit() was called outside of `db.transaction(...)`. Dispatching immediately. Wrap the call in a transaction or drop .afterCommit() to silence this message.")}class w{name;payload;options={};txMode="auto";constructor(L,$){this.name=L;this.payload=$}onQueue(L){return this.options.queue=L,this}delay(L){return this.options.delay=L,this}tries(L){return this.options.tries=L,this}timeout(L){return this.options.timeout=L,this}backoff(L){return this.options.backoff=L,this}withContext(L){return this.options.context=L,this}withIdempotencyKey(L){return this.options.idempotencyKey=L,this}afterCommit(){return this.txMode="after",this}withoutCommit(){return this.txMode="immediate",this}async dispatch(){let{isFaked:L,getFakeQueue:$}=await Promise.resolve().then(() => i1);if(L()){$()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(g0()){let Y=this.runDispatchPipeline.bind(this);if(h0(async()=>{await Y()}))return}else if(this.txMode==="after")b0()}await this.runDispatchPipeline()}async runDispatchPipeline(){let L=!1;if(this.options.idempotencyKey){let $=await n1(this.options.idempotencyKey,this.name,this.options.queue);if($==="duplicate")return;L=$==="claimed"}try{if(await R1(this.name,this.payload)){let V=y(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await J1({queue:this.options.queue||"default",payload:j(V),exception:`quarantined: ${this.name}`},"poison-detected"))return}let $=p0();if($==="database")await this.dispatchToDatabase();else if($==="redis")await this.dispatchToRedis();else if($==="sync")await j1(this.name,{payload:this.payload,context:this.options.context,traceId:await XL()});else if($==="sqs"||$==="memory"||$==="beanstalkd")throw Error(`[queue] Driver "${$}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);else throw Error(`[queue] Unknown QUEUE_DRIVER "${$}". Allowed values: redis, database, sync.`)}catch($){if(L&&this.options.idempotencyKey)await s1(this.options.idempotencyKey);throw $}}async dispatchIf(L){if(L)await this.dispatch()}async dispatchUnless(L){if(!L)await this.dispatch()}async dispatchToDatabase(){let L=Math.floor(Date.now()/1000),$=this.options.delay?L+this.options.delay:L,V=y(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff},await XL()),Y=j(V),{db:G}=await import("@stacksjs/database");await G.insertInto("jobs").values({queue:this.options.queue||"default",payload:Y,attempts:0,reserved_at:null,available_at:$,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){let L=y(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});_1(L);let{RedisQueue:$}=await Promise.resolve().then(() => (F1(),O1)),{queue:V}=await import("@stacksjs/config"),Y=V?.connections?.redis;if(!Y)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new $(this.options.queue||"default",Y).add(L,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await j1(this.name,{payload:this.payload,context:this.options.context})}}function WL(L,$){return new w(L,$)}function _L(L){let{PendingBatch:$}=(g(),sL(l));return new $(L)}async function ZL(L){let $=[f0(`Jobs/${L}.ts`),j0(`defaults/app/Jobs/${L}.ts`)];try{let V=import.meta.resolve("@stacksjs/defaults/package.json"),Y=new URL(".",V).pathname;$.push(`${Y}app/Jobs/${L}.ts`)}catch{}for(let V of $)if(await Bun.file(V).exists())return V;return null}async function j1(L,$={}){let{withTraceId:V}=await import("@stacksjs/router"),Y=$.traceId??`job:${L}:${Math.random().toString(36).slice(2,10)}`;await V(Y,async()=>{let G=await ZL(L);if(!G)throw Error(`Job ${L} not found. Looked in app/Jobs/${L}.ts and the framework defaults (storage/framework/defaults/app/Jobs, @stacksjs/defaults).`);let K=(await import(G)).default;if(!K)throw Error(`Job ${L} does not export a default`);if(typeof K.handle==="function")await K.handle($.payload);else if(typeof K.action==="string"){let{runAction:z}=await import("@stacksjs/actions");await z(K.action)}else if(typeof K.action==="function")await K.action();else if(typeof K==="function")await K($.payload,$.context);else throw Error(`Job ${L} does not have a valid handler`)})}async function XL(){try{let{getTraceId:L}=await import("@stacksjs/router");return L()}catch{return}}var KL=!1,zL;var x1=v(()=>{S1();a();r1();C1();zL={make(L,$){return new w(L,$)},async dispatch(L,$){await new w(L,$).dispatch()},async dispatchIf(L,$,V){if(L)await new w($,V).dispatch()},async dispatchUnless(L,$,V){if(!L)await new w($,V).dispatch()},async dispatchNow(L,$){await new w(L,$).dispatchNow()},dispatchAfter(L,$,V){return new w($,V).delay(L)},async dispatchOnce(L,$,V){await new w($,V).withIdempotencyKey(L).dispatch()},async dispatchAfterCommit(L,$){await new w(L,$).afterCommit().dispatch()}}});var m1={};p(m1,{resumeQueue:()=>DL,recordCircuitSuccess:()=>HL,recordCircuitFailure:()=>QL,pauseQueue:()=>AL,listCircuitState:()=>xL,isCircuitOpen:()=>FL});import{db as S}from"@stacksjs/database";function s(){if(OL)return;OL=!0,console.warn("[queue/circuit-breaker] queue_circuit_state table missing \u2014 circuit breaker disabled. "+"Run migrations to enable.")}async function X4(L,$){try{let V=await S.selectFrom("queue_circuit_state").where("queue_name","=",L).selectAll().executeTakeFirst();if(V)return V;return await S.insertInto("queue_circuit_state").values({queue_name:L,success_count:0,failure_count:0,window_start:$,paused_at:null,resume_at:null}).execute(),{queue_name:L,success_count:0,failure_count:0,window_start:$,paused_at:null,resume_at:null}}catch(V){if(B(V))return s(),null;let Y=V?.message??"";if(Y.includes("UNIQUE constraint")||Y.includes("Duplicate entry"))return await S.selectFrom("queue_circuit_state").where("queue_name","=",L).selectAll().executeTakeFirst();throw V}}async function FL(L){try{let $=await S.selectFrom("queue_circuit_state").where("queue_name","=",L).select(["paused_at","resume_at"]).executeTakeFirst();if(!$||!$.paused_at)return!1;if($.resume_at){let V=Date.parse($.resume_at.replace(" ","T")+"Z");if(Number.isFinite(V)&&Date.now()>=V)return await S.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",L).execute(),!1}return!0}catch($){if(B($))return s(),!1;throw $}}async function HL(L,$={}){let V=$.windowSeconds??300,Y=new Date().toISOString().slice(0,19).replace("T"," "),G=await X4(L,Y);if(!G)return;try{if(K4(G.window_start,V)){await S.updateTable("queue_circuit_state").set({success_count:1,failure_count:0,window_start:Y}).where("queue_name","=",L).execute();return}await S.updateTable("queue_circuit_state").set({success_count:G.success_count+1}).where("queue_name","=",L).execute()}catch(U){if(B(U)){s();return}throw U}}async function QL(L,$={}){let V=$.failureRateThreshold??0.5,Y=$.windowSeconds??300,G=$.pauseSeconds??300,U=$.minObservations??10,K=new Date,z=K.toISOString().slice(0,19).replace("T"," "),X=await X4(L,z);if(!X)return!1;if(X.paused_at)return!1;try{let{success_count:W,failure_count:_}=X;if(K4(X.window_start,Y))W=0,_=0;_+=1;let A=W+_,x=A===0?0:_/A;if(A>=U&&x>=V){let Z=new Date(K.getTime()+G*1000).toISOString().slice(0,19).replace("T"," ");return await S.updateTable("queue_circuit_state").set({success_count:W,failure_count:_,window_start:z,paused_at:z,resume_at:Z}).where("queue_name","=",L).execute(),!0}return await S.updateTable("queue_circuit_state").set({success_count:W,failure_count:_,window_start:K4(X.window_start,Y)?z:X.window_start}).where("queue_name","=",L).execute(),!1}catch(W){if(B(W))return s(),!1;throw W}}function K4(L,$){if(!L)return!0;let V=Date.parse(L.replace(" ","T")+"Z");if(!Number.isFinite(V))return!0;return Date.now()-V>$*1000}async function AL(L,$=300){let V=new Date,Y=V.toISOString().slice(0,19).replace("T"," "),G=new Date(V.getTime()+$*1000).toISOString().slice(0,19).replace("T"," ");await X4(L,Y);try{await S.updateTable("queue_circuit_state").set({paused_at:Y,resume_at:G}).where("queue_name","=",L).execute()}catch(U){if(B(U)){s();return}throw U}}async function DL(L){try{await S.updateTable("queue_circuit_state").set({paused_at:null,resume_at:null,success_count:0,failure_count:0}).where("queue_name","=",L).execute()}catch($){if(B($)){s();return}throw $}}async function xL(){try{return await S.selectFrom("queue_circuit_state").selectAll().execute()??[]}catch(L){if(B(L))return s(),[];throw L}}var OL=!1;var P1=()=>{};a();import{env as O0}from"@stacksjs/env";function I4(){return O0.QUEUE_DRIVER||"sync"}class v4{name;description;action;handle;queue;rate;tries;timeout;backoff;backoffConfig;enabled;constructor(L){this.name=L.name,this.description=L.description,this.handle=L.handle,this.queue=L.queue,this.rate=L.rate,this.action=L.action,this.tries=L.tries,this.timeout=L.timeout,this.backoff=L.backoff,this.backoffConfig=L.backoffConfig,this.enabled=L.enabled}async dispatch(L){let{isFaked:$,getFakeQueue:V}=await Promise.resolve().then(() => i1);if($()){V()?.dispatch(this.name||"UnknownJob",L,{queue:this.queue,tries:this.tries,timeout:this.timeout});return}let Y=I4();if(Y==="sync")return this.dispatchNow(L);if(Y==="redis")return this.dispatchToRedis(L);if(Y==="database")return this.dispatchToDatabase(L);if(Y==="sqs"||Y==="memory"||Y==="beanstalkd")throw Error(`[queue] Driver "${Y}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);throw Error(`[queue] Unknown QUEUE_DRIVER "${Y}". Allowed values: redis, database, sync.`)}async dispatchIf(L,$){if(L)return this.dispatch($)}async dispatchUnless(L,$){if(!L)return this.dispatch($)}async dispatchAfter(L,$){let V=I4();if(V==="redis")return this.dispatchToRedis($,{delay:L});if(V==="database")return this.dispatchToDatabase($,{delay:L});return await new Promise((Y)=>setTimeout(Y,L*1000)),await this.dispatchNow($)}async dispatchNow(L){if(typeof this.handle==="function")await this.handle(L);else if(typeof this.action==="string"){let{runAction:$}=await import("@stacksjs/actions");await $(this.action)}else if(typeof this.action==="function")await this.action();else throw Error(`Job ${this.name} does not have a valid handler`)}async dispatchToDatabase(L,$){let V=Math.floor(Date.now()/1000),Y=$?.delay?V+$.delay:V,G=y(this.name??this.constructor.name,L,{queue:this.queue??"default",tries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0}),U=j(G),{db:K}=await import("@stacksjs/database");await K.insertInto("jobs").values({queue:this.queue||"default",payload:U,attempts:0,reserved_at:null,available_at:Y,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(L,$){let V=y(this.name??this.constructor.name,L,{queue:this.queue??"default",tries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0});_1(V);let{RedisQueue:Y}=await Promise.resolve().then(() => (F1(),O1)),{queue:G}=await import("@stacksjs/config"),U=G?.connections?.redis;if(!U)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new Y(this.queue||"default",U).add(V,{delay:$?.delay,maxTries:typeof this.tries==="number"?this.tries:void 0,timeout:this.timeout,backoff:Array.isArray(this.backoff)?this.backoff:void 0})}}x1();r1();S1();C1();P1();a();var W4=(L)=>`__job_progress__:${L}`,z4=(L)=>`__job_cancel__:${L}`;function u0(L){return Number.isFinite(L)?Math.max(0,Math.min(100,L)):0}async function l0(L,$,V){let{cache:Y}=await import("@stacksjs/cache");await Y.set(W4(L),{percent:u0($),message:V,updatedAt:Date.now()},3600)}async function i0(L){let{cache:$}=await import("@stacksjs/cache");return await $.get(W4(L))??null}async function c0(L){let{cache:$}=await import("@stacksjs/cache");await $.set(z4(L),1,3600)}async function d0(L){let{cache:$}=await import("@stacksjs/cache");return Boolean(await $.get(z4(L)))}async function n0(L){let{cache:$}=await import("@stacksjs/cache");await Promise.all([$.del(W4(L)),$.del(z4(L))])}import{log as K1}from"@stacksjs/logging";import*as PL from"@stacksjs/path";class ML{jobs=new Map;initialized=!1;register(L){this.jobs.set(L.name,L),K1.debug(`Registered job: ${L.name} (${L.type})`)}get(L){return this.jobs.get(L)}all(){return Array.from(this.jobs.values())}byQueue(L){return this.all().filter(($)=>$.config.queue===L)}scheduled(){return this.all().filter((L)=>L.config.rate||L.config.schedule)}has(L){return this.jobs.has(L)}clear(){this.jobs.clear(),this.initialized=!1}setInitialized(L){this.initialized=L}isInitialized(){return this.initialized}}var r=new ML;async function _4(L){let $=L||PL.userJobsPath(),V=[];try{let Y=new Bun.Glob("**/*.{ts,js}"),G={cwd:$,onlyFiles:!0,absolute:!0};for await(let U of Y.scan(G)){if(U.includes(".test.")||U.includes(".spec.")||U.endsWith("index.ts")||U.endsWith("index.js"))continue;try{let K=await s0(U);if(K)V.push(K),r.register(K)}catch(K){K1.warn(`Failed to load job from ${U}: ${K.message}`)}}return r.setInitialized(!0),K1.info(`Discovered ${V.length} jobs from ${$}`),V}catch(Y){return K1.error(`Failed to discover jobs: ${Y.message}`),[]}}async function s0(L){try{let $=await import(L),V=L.split("/").pop()?.replace(/\.(ts|js)$/,"")||"UnknownJob";if($.default&&typeof $.default==="function"){let Y=$.default;if(typeof Y.handle==="function"||typeof Y.prototype?.handle==="function"){let G=Y.config||{};return{name:G.name||V,path:L,config:{name:G.name||V,description:G.description,queue:G.queue||"default",tries:G.retries||3,timeout:G.timeout,withoutOverlapping:G.withoutOverlapping,schedule:G.schedule,retryAfter:G.retryAfter},type:"class",module:Y}}}if($.default&&typeof $.default==="object"){let Y=$.default;if(typeof Y.handle==="function"||typeof Y.action==="string")return{name:Y.name||V,path:L,config:{name:Y.name||V,description:Y.description,queue:Y.queue||"default",tries:Y.tries||3,backoff:Y.backoff,rate:Y.rate,timeout:Y.timeout||Y.timeOut,backoffConfig:Y.backoffConfig},type:"function",module:Y}}return null}catch($){return K1.debug(`Could not load job from ${L}: ${$.message}`),null}}function r0(L){return r.get(L)}function t0(){return r.all()}function Z4(){return r.scheduled()}async function o0(L,$){let V=r.get(L);if(!V)throw Error(`Job "${L}" not found. Did you run discoverJobs()?`);try{if(V.type==="class"){if(typeof V.module.handle==="function")return await V.module.handle($);return await new V.module().handle($)}else{if(typeof V.module.handle==="function")return await V.module.handle($);throw Error(`Job "${L}" does not have a handle method`)}}catch(Y){throw K1.error(`Failed to execute job "${L}": ${Y.message}`),Y}}function a0(L){let $=L.config;return{name:$.name,queue:$.queue,tries:$.tries,backoff:$.backoff,timeout:$.timeout,backoffConfig:$.backoffConfig,rate:$.rate}}import{log as E}from"@stacksjs/logging";k();import{log as TL}from"@stacksjs/logging";var BL=!1;async function EL(){if(BL)return!0;try{let{db:L}=await import("@stacksjs/database");return await L.unsafe("CREATE TABLE IF NOT EXISTS scheduled_job_runs (job_name VARCHAR(255) PRIMARY KEY, last_run_at VARCHAR(64) NOT NULL)").execute(),BL=!0,!0}catch(L){return TL.debug(`[scheduler] run-marker persistence unavailable, using in-memory lastRun: ${L instanceof Error?L.message:String(L)}`),!1}}async function NL(L){if(!await EL())return null;try{let{db:$}=await import("@stacksjs/database"),V=await $.selectFrom("scheduled_job_runs").where("job_name","=",L).select(["last_run_at"]).executeTakeFirst();if(!V?.last_run_at)return null;let Y=new Date(V.last_run_at);return Number.isNaN(Y.getTime())?null:Y}catch{return null}}async function JL(L,$){if(!await EL())return;try{let{db:V}=await import("@stacksjs/database"),Y=$.toISOString();await V.deleteFrom("scheduled_job_runs").where("job_name","=",L).execute(),await V.insertInto("scheduled_job_runs").values({job_name:L,last_run_at:Y}).execute()}catch{}}function e0(L){return`%"jobName":"${L.replace(/[\\%_]/g,(V)=>`\\${V}`)}"%`}async function SL(L){try{let{db:$}=await import("@stacksjs/database"),V=await $.unsafe("SELECT 1 AS present FROM jobs WHERE payload LIKE ? ESCAPE '\\' LIMIT 1",[e0(L)]).execute(),Y=Array.isArray(V)?V:V?.rows??[];return Array.isArray(Y)&&Y.length>0}catch($){return TL.debug(`[scheduler] overlap check unavailable, dispatching anyway: ${$ instanceof Error?$.message:String($)}`),!1}}q1();var IL={checkInterval:60000,preventOverlapping:!0},P={isRunning:!1,isShuttingDown:!1,checkInterval:null,jobs:new Map,config:{...IL}},RL=new Set;function L9(L,$){if(RL.has(L))return;RL.add(L),E.warn(`[scheduler] Cron expression "${L}" specifies seconds="${$}" but the scheduler `+"ticks at minute granularity \u2014 the seconds field is being ignored. Use a 5-field expression "+"to avoid this warning, or wait for sub-minute scheduling support.")}var CL=!1,qL=new Map;function $9(L){let $=qL.get(L);if(!$)$=new Intl.DateTimeFormat("en-US",{timeZone:L,hour12:!1,month:"numeric",day:"numeric",hour:"numeric",minute:"numeric",weekday:"short"}),qL.set(L,$);return $}function O4(L,$){if($&&$!=="local"&&$!=="system")try{let V=$9($).formatToParts(L),Y=(K)=>V.find((z)=>z.type===K)?.value??"",G={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6},U=Number(Y("hour"));if(U===24)U=0;return{minute:Number(Y("minute")),hour:U,day:Number(Y("day")),month:Number(Y("month")),dayOfWeek:G[Y("weekday")]??L.getDay()}}catch{if(!CL)CL=!0,E.warn(`[scheduler] Invalid timezone "${$}"; falling back to system local time.`)}return{minute:L.getMinutes(),hour:L.getHours(),day:L.getDate(),month:L.getMonth()+1,dayOfWeek:L.getDay()}}function V9(L,$,V){let Y=O4(new Date,V),G=Y.minute,U=Y.hour,K=Y.day,z=Y.month,X=Y.dayOfWeek;if($){let F=O4($,V);if(F.minute===G&&F.hour===U&&F.day===K)return!1}let W=L.trim().split(/\s+/);if(W.length===6){let F=W[0];if(F&&F!=="0"&&F!=="*")L9(L,F);W=W.slice(1)}if(W.length<5)return E.warn(`Invalid cron expression: ${L}`),!1;let[_,A,x,O,Z]=W;return f(_,G,0,59)&&f(A,U,0,23)&&f(x,K,1,31)&&f(O,z,1,12)&&f(Z,X,0,6)}function f(L,$,V,Y){if(L==="*")return!0;if(L.includes(","))return L.split(",").map((K)=>Number.parseInt(K.trim(),10)).includes($);if(L.includes("-")){let[U,K]=L.split("-").map((z)=>Number.parseInt(z.trim(),10));return $>=U&&$<=K}if(L.includes("/")){let[U,K]=L.split("/"),z=Number.parseInt(K,10);if(U==="*")return $%z===0;if(U.includes("-")){let[X,W]=U.split("-").map((_)=>Number.parseInt(_.trim(),10));return $>=X&&$<=W&&($-X)%z===0}}let G=Number.parseInt(L,10);return!Number.isNaN(G)&&$===G}function vL(L){let V={"@yearly":"0 0 1 1 *","@annually":"0 0 1 1 *","@monthly":"0 0 1 * *","@weekly":"0 0 * * 0","@daily":"0 0 * * *","@midnight":"0 0 * * *","@hourly":"0 * * * *"}[L.toLowerCase()];if(V)return V;let Y=L.match(/^Every\.(\w+)$/i);if(Y&&Y[1]!==void 0){let U=Y[1].toLowerCase();return{second:"* * * * *",fiveseconds:"* * * * *",tenseconds:"* * * * *",thirtyseconds:"* * * * *",minute:"* * * * *",fiveminutes:"*/5 * * * *",tenminutes:"*/10 * * * *",fifteenminutes:"*/15 * * * *",thirtyminutes:"*/30 * * * *",hour:"0 * * * *",twohours:"0 */2 * * *",sixhours:"0 */6 * * *",twelvehours:"0 */12 * * *",day:"0 0 * * *",week:"0 0 * * 0",month:"0 0 1 * *"}[U]||null}let G=L.split(/\s+/).length;if(G>=5&&G<=6)return L;return null}function yL(L,$){let V=L.trim().split(/\s+/),Y=V.length===6?V.slice(1):V;if(Y.length<5)return null;let[G,U,K,z,X]=Y,W=new Date;W.setSeconds(0,0);let _=527040;for(let A=1;A<=_;A++){let x=new Date(W.getTime()+A*60000),O=O4(x,$);if(f(G,O.minute,0,59)&&f(U,O.hour,0,23)&&f(K,O.day,1,31)&&f(z,O.month,1,12)&&f(X,O.dayOfWeek,0,6))return x}return null}async function Y9(L={}){if(P.isRunning){E.warn("Scheduler is already running");return}P.config={...IL,...L},P.isRunning=!0,P.isShuttingDown=!1,await _4();let $=Z4();for(let G of $){let U=G.config.rate||G.config.schedule;if(U){let K=vL(U);if(K){let z=await NL(G.name);P.jobs.set(G.name,{job:G,lastRun:z,nextRun:yL(K,P.config.timezone),isRunning:!1}),E.info(`Registered scheduled job: ${G.name} (${K})`)}else E.warn(`Invalid schedule for job ${G.name}: ${U}`)}}if(P.jobs.size===0){E.info("No scheduled jobs found");return}E.info(`Scheduler started with ${P.jobs.size} job(s)`),process.on("SIGINT",()=>F4()),process.on("SIGTERM",()=>F4());let V=!1,Y=()=>{if(P.isShuttingDown)return;let G=P.config.checkInterval,U=G-Date.now()%G;P.checkInterval=setTimeout(()=>{if(!P.isShuttingDown&&!V)V=!0,kL().catch((K)=>E.error("Scheduler check failed:",K)).finally(()=>{V=!1});Y()},U),P.checkInterval?.unref?.()};Y(),await kL()}async function kL(){for(let[L,$]of P.jobs){let V=$.job.config.rate||$.job.config.schedule;if(!V)continue;let Y=vL(V);if(!Y)continue;if(V9(Y,$.lastRun,P.config.timezone)){if((P.config.preventOverlapping||$.job.config.withoutOverlapping)&&await SL(L)){E.debug(`Skipping ${L}: previous execution still running`);continue}if($.isRunning){E.debug(`Skipping ${L}: a dispatch for it is already in flight`);continue}try{$.isRunning=!0,$.lastRun=new Date,$.nextRun=yL(Y,P.config.timezone),await JL(L,$.lastRun),E.info(`Dispatching scheduled job: ${L}`),await d("job:added",{jobId:`scheduled-${L}-${Date.now()}`,queueName:$.job.config.queue||"default",jobName:L}),await t1(L,{queue:$.job.config.queue||"default",payload:{},maxTries:$.job.config.tries||3,timeout:$.job.config.timeout||60}),$.isRunning=!1,E.info(`Scheduled job ${L} dispatched to queue`)}catch(U){$.isRunning=!1,E.error(`Failed to dispatch scheduled job ${L}:`,U)}}}}async function F4(){if(!P.isRunning)return;if(E.info("Stopping scheduler..."),P.isShuttingDown=!0,P.checkInterval)clearTimeout(P.checkInterval),P.checkInterval=null;P.isRunning=!1,P.jobs.clear(),E.info("Scheduler stopped")}function G9(){return{isRunning:P.isRunning,jobCount:P.jobs.size,jobs:Array.from(P.jobs.entries()).map(([L,$])=>({name:L,schedule:$.job.config.rate||$.job.config.schedule,lastRun:$.lastRun,nextRun:$.nextRun,isRunning:$.isRunning}))}}function U9(){return P.isRunning}function K9(){return new Map(P.jobs)}async function X9(L){let $=P.jobs.get(L);if(!$)throw Error(`Scheduled job "${L}" not found`);E.info(`Manually triggering scheduled job: ${L}`),await t1(L,{queue:$.job.config.queue||"default",payload:{},maxTries:$.job.config.tries||3,timeout:$.job.config.timeout||60})}k();k();import{log as W9}from"@stacksjs/logging";var z9={maxPendingWarning:1000,maxPendingCritical:5000,maxFailedWarning:10,maxFailedCritical:100,maxJobAgeWarning:3600,maxJobAgeCritical:86400,maxErrorRateWarning:0.1,maxErrorRateCritical:0.5};async function H4(L={}){let $={...z9,...L},V=[],Y=new Date,G=Math.floor(Y.getTime()/1000);try{let{db:U}=await import("@stacksjs/database"),K=await U.selectFrom("jobs").selectAll().execute(),z=await U.selectFrom("failed_jobs").selectAll().execute(),X=new Map;for(let Q of K){let M=Q.queue||"default";if(!X.has(M))X.set(M,{pending:0,processing:0,delayed:0});let J=X.get(M);if(Q.reserved_at)J.processing++;else if(Q.available_at&&Q.available_at>G)J.delayed++;else if(J.pending++,Q.created_at){let q=typeof Q.created_at==="number"?Q.created_at:Math.floor(new Date(Q.created_at).getTime()/1000),M4=G-q;if(!J.oldestAge||M4>J.oldestAge)J.oldestAge=M4}}let W=new Map;for(let Q of z){let M=Q.queue||"default";W.set(M,(W.get(M)||0)+1)}let _=[],A=new Set([...X.keys(),...W.keys()]),x=$.queues?[...A].filter((Q)=>$.queues.includes(Q)):[...A],O=0,Z=0,F=0,I=0;for(let Q of x){let M=X.get(Q)||{pending:0,processing:0,delayed:0},J=W.get(Q)||0;O+=M.pending,Z+=M.processing,F+=M.delayed,I+=J;let q="healthy";if(M.pending>=$.maxPendingCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${Q}" has ${M.pending} pending jobs (threshold: ${$.maxPendingCritical})`,queue:Q,timestamp:Y.toISOString()});else if(M.pending>=$.maxPendingWarning)q="degraded",V.push({level:"warning",message:`Queue "${Q}" has ${M.pending} pending jobs (threshold: ${$.maxPendingWarning})`,queue:Q,timestamp:Y.toISOString()});if(J>=$.maxFailedCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${Q}" has ${J} failed jobs (threshold: ${$.maxFailedCritical})`,queue:Q,timestamp:Y.toISOString()});else if(J>=$.maxFailedWarning){if(q==="healthy")q="degraded";V.push({level:"warning",message:`Queue "${Q}" has ${J} failed jobs (threshold: ${$.maxFailedWarning})`,queue:Q,timestamp:Y.toISOString()})}if(M.oldestAge){if(M.oldestAge>=$.maxJobAgeCritical)q="unhealthy",V.push({level:"critical",message:`Queue "${Q}" has a job waiting for ${Math.floor(M.oldestAge/3600)} hours`,queue:Q,timestamp:Y.toISOString()});else if(M.oldestAge>=$.maxJobAgeWarning){if(q==="healthy")q="degraded";V.push({level:"warning",message:`Queue "${Q}" has a job waiting for ${Math.floor(M.oldestAge/60)} minutes`,queue:Q,timestamp:Y.toISOString()})}}_.push({name:Q,status:q,pending:M.pending,processing:M.processing,delayed:M.delayed,failed:J,oldestJobAge:M.oldestAge})}let x4=v1().getMetrics(),bL=x4.throughputPerMinute,uL=x4.averageDuration,P4=O+Z+F+I,z1=P4>0?I/P4:0,o="healthy";if(_.some((Q)=>Q.status==="unhealthy"))o="unhealthy";else if(_.some((Q)=>Q.status==="degraded"))o="degraded";if(z1>=$.maxErrorRateCritical)o="unhealthy",V.push({level:"critical",message:`Overall error rate is ${(z1*100).toFixed(1)}% (threshold: ${$.maxErrorRateCritical*100}%)`,timestamp:Y.toISOString()});else if(z1>=$.maxErrorRateWarning){if(o==="healthy")o="degraded";V.push({level:"warning",message:`Overall error rate is ${(z1*100).toFixed(1)}% (threshold: ${$.maxErrorRateWarning*100}%)`,timestamp:Y.toISOString()})}let lL=A1().getAll().map((Q)=>({id:Q.id,status:Q.status,queue:Q.queue,processedCount:Q.processedCount,failedCount:Q.failedCount,lastActivityAt:Q.lastActivityAt}));return{status:o,timestamp:Y.toISOString(),queues:_,workers:lL,metrics:{totalPending:O,totalProcessing:Z,totalDelayed:F,totalFailed:I,throughputPerMinute:bL,averageProcessingTime:uL,errorRate:z1},alerts:V}}catch(U){W9.error("Failed to perform queue health check:",U);let z=A1().getAll().map((X)=>({id:X.id,status:X.status,queue:X.queue,processedCount:X.processedCount,failedCount:X.failedCount,lastActivityAt:X.lastActivityAt}));return{status:"unhealthy",timestamp:Y.toISOString(),queues:[],workers:z,metrics:{totalPending:0,totalProcessing:0,totalDelayed:0,totalFailed:0,throughputPerMinute:0,averageProcessingTime:0,errorRate:0},alerts:[{level:"critical",message:`Health check failed: ${U.message}`,timestamp:Y.toISOString()}]}}}function _9(L={}){return async($)=>{let V=await H4(L),Y=V.status==="healthy"?200:V.status==="degraded"?207:503;return new Response(JSON.stringify(V,null,2),{status:Y,headers:{"Content-Type":"application/json","Cache-Control":"no-store"}})}}async function Z9(L={}){return(await H4(L)).status==="healthy"}import{log as C}from"@stacksjs/logging";var O9={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function M1(L){return String(L).replace(/[&<>"']/g,($)=>O9[$]??$)}class Q4{config;notificationCount=0;lastResetTime=Date.now();pendingBatch=[];batchTimeout=null;activeFlush=null;constructor(L){this.config=L}async notify(L){if(this.config.filter&&!this.config.filter(L))return;if(this.config.rateLimit){let $=Date.now();if($-this.lastResetTime>3600000)this.notificationCount=0,this.lastResetTime=$;if(this.notificationCount>=this.config.rateLimit){C.debug("Rate limit reached for failed job notifications");return}}if(this.config.batch){if(this.pendingBatch.push(L),!this.batchTimeout)this.batchTimeout=setTimeout(()=>{this.batchTimeout=null,this.activeFlush=this.flushBatch().catch(($)=>C.error("Failed to flush notification batch:",$)).finally(()=>{this.activeFlush=null})},this.config.batchInterval||60000),this.batchTimeout.unref?.();return}await this.sendNotifications([L])}async flushBatch(){if(this.pendingBatch.length===0)return;let L=[...this.pendingBatch];if(this.pendingBatch=[],this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;await this.sendNotifications(L)}async shutdown(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.activeFlush)await this.activeFlush.catch(()=>{});await this.flushBatch().catch((L)=>C.error("Failed to flush notification batch on shutdown:",L))}async sendNotifications(L){let $=[];for(let V of this.config.channels)switch(V){case"email":if(this.config.email)$.push(this.sendEmail(L));break;case"slack":if(this.config.slack)$.push(this.sendSlack(L));break;case"discord":if(this.config.discord)$.push(this.sendDiscord(L));break;case"webhook":if(this.config.webhook)$.push(this.sendWebhook(L));break;case"log":this.logFailures(L);break}try{await Promise.all($)}catch(V){C.error("Failed to send job failure notifications:",V)}finally{this.notificationCount+=L.length}}async sendEmail(L){let $=this.config.email,V=$.subject||`[Queue] ${L.length} Job(s) Failed`,Y=this.formatEmailBody(L);try{let{mail:G}=await import("@stacksjs/email");await G.send({to:Array.isArray($.to)?$.to:[$.to],from:$.from,subject:V,html:Y})}catch(G){C.error("Failed to send email notification:",G)}}formatEmailBody(L){let $=L.map((V)=>`
3
3
  <tr>
4
- <td style="padding: 8px; border: 1px solid #ddd;">${A1(String(V.id))}</td>
5
- <td style="padding: 8px; border: 1px solid #ddd;">${A1(V.name)}</td>
6
- <td style="padding: 8px; border: 1px solid #ddd;">${A1(V.queue)}</td>
4
+ <td style="padding: 8px; border: 1px solid #ddd;">${M1(String(V.id))}</td>
5
+ <td style="padding: 8px; border: 1px solid #ddd;">${M1(V.name)}</td>
6
+ <td style="padding: 8px; border: 1px solid #ddd;">${M1(V.queue)}</td>
7
7
  <td style="padding: 8px; border: 1px solid #ddd;">${V.attempts}/${V.maxAttempts}</td>
8
- <td style="padding: 8px; border: 1px solid #ddd;">${A1(V.failedAt.toISOString())}</td>
9
- <td style="padding: 8px; border: 1px solid #ddd;"><pre style="max-width: 300px; overflow: auto;">${A1(V.exception)}</pre></td>
8
+ <td style="padding: 8px; border: 1px solid #ddd;">${M1(V.failedAt.toISOString())}</td>
9
+ <td style="padding: 8px; border: 1px solid #ddd;"><pre style="max-width: 300px; overflow: auto;">${M1(V.exception)}</pre></td>
10
10
  </tr>
11
11
  `).join("");return`
12
12
  <h2>Failed Jobs Report</h2>
13
- <p>${$.length} job(s) have failed.</p>
13
+ <p>${L.length} job(s) have failed.</p>
14
14
  <table style="border-collapse: collapse; width: 100%;">
15
15
  <thead>
16
16
  <tr style="background: #f5f5f5;">
@@ -23,9 +23,9 @@ var{defineProperty:j1,getOwnPropertyNames:p2,getOwnPropertyDescriptor:g2}=Object
23
23
  </tr>
24
24
  </thead>
25
25
  <tbody>
26
- ${L}
26
+ ${$}
27
27
  </tbody>
28
28
  </table>
29
- `}async sendSlack($){let L=this.config.slack,V=[{type:"header",text:{type:"plain_text",text:`\uD83D\uDEA8 ${$.length} Job(s) Failed`,emoji:!0}},...$.slice(0,10).map((Y)=>({type:"section",text:{type:"mrkdwn",text:`*${Y.name}*
29
+ `}async sendSlack(L){let $=this.config.slack,V=[{type:"header",text:{type:"plain_text",text:`\uD83D\uDEA8 ${L.length} Job(s) Failed`,emoji:!0}},...L.slice(0,10).map((Y)=>({type:"section",text:{type:"mrkdwn",text:`*${Y.name}*
30
30
  Queue: ${Y.queue} | Attempts: ${Y.attempts}/${Y.maxAttempts}
31
- \`\`\`${Y.exception.slice(0,200)}\`\`\``}}))];if($.length>10)V.push({type:"section",text:{type:"mrkdwn",text:`_...and ${$.length-10} more failed jobs_`}});try{let Y=await fetch(L.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:L.channel,username:L.username||"Queue Monitor",icon_emoji:L.iconEmoji||":warning:",blocks:V})});if(!Y.ok)C.error(`Slack notification failed with status ${Y.status}`)}catch(Y){C.error("Failed to send Slack notification:",Y)}}async sendDiscord($){let L=this.config.discord,V=$.slice(0,10).map((Y)=>({title:`\u274C ${Y.name}`,color:15158332,fields:[{name:"Queue",value:Y.queue,inline:!0},{name:"Attempts",value:`${Y.attempts}/${Y.maxAttempts}`,inline:!0},{name:"Failed At",value:Y.failedAt.toISOString(),inline:!0},{name:"Exception",value:`\`\`\`${Y.exception.slice(0,500)}\`\`\``}],timestamp:Y.failedAt.toISOString()}));try{let Y=await fetch(L.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:L.username||"Queue Monitor",avatar_url:L.avatarUrl,content:`\uD83D\uDEA8 **${$.length} Job(s) Failed**`,embeds:V})});if(!Y.ok)C.error(`Discord notification failed with status ${Y.status}`)}catch(Y){C.error("Failed to send Discord notification:",Y)}}async sendWebhook($){let L=this.config.webhook,V={event:"jobs.failed",timestamp:new Date().toISOString(),count:$.length,jobs:$.map((G)=>({id:G.id,name:G.name,queue:G.queue,attempts:G.attempts,maxAttempts:G.maxAttempts,exception:G.exception,failedAt:G.failedAt.toISOString()}))},Y={"Content-Type":"application/json",...L.headers};if(L.secret){let G=JSON.stringify(V),U=await this.generateSignature(G,L.secret);Y["X-Signature"]=U}try{let G=await fetch(L.url,{method:"POST",headers:Y,body:JSON.stringify(V)});if(!G.ok)C.error(`Webhook notification failed with status ${G.status}`)}catch(G){C.error("Failed to send webhook notification:",G)}}async generateSignature($,L){let V=new TextEncoder,Y=await crypto.subtle.importKey("raw",V.encode(L),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),G=await crypto.subtle.sign("HMAC",Y,V.encode($));return Array.from(new Uint8Array(G)).map((U)=>U.toString(16).padStart(2,"0")).join("")}logFailures($){for(let L of $)C.error(`[Queue] Job "${L.name}" failed on queue "${L.queue}" after ${L.attempts} attempts: ${L.exception}`)}async cleanup(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.pendingBatch.length>0)await this.flushBatch()}}var P1=null;function V8($){return P1=new Z4($),P1}function Y8(){return P1}async function G8($){if(P1)await P1.notify($)}f();U1();import{err as U8,ok as v2}from"@stacksjs/error-handling";import{log as D}from"@stacksjs/logging";import M1 from"process";import{env as X8}from"@stacksjs/env";M1.on("unhandledRejection",($,L)=>{D.error(`Unhandled Rejection: ${$}`)});M1.on("uncaughtException",($)=>{D.error(`Uncaught Exception: ${$.message}`)});var b=0,Y1=!1,S="",d=new Set,k2=new Map;function K8($){return d.add($),$.finally(()=>d.delete($)),$}function z8(){let $=M1.env.STACKS_QUEUE_RESERVATION_TTL_SEC,L=$===void 0?Number.NaN:Number.parseInt($,10);return Number.isFinite(L)&&L>0?L:3600}function _8(){let $=M1.env.STACKS_QUEUE_SWEEP_INTERVAL_SEC,L=$===void 0?Number.NaN:Number.parseInt($,10);return(Number.isFinite(L)&&L>0?L:60)*1000}async function I2(){let $=z8(),L=Math.floor(Date.now()/1000)-$,V=Math.floor(Date.now()/1000);try{let{db:Y}=await import("@stacksjs/database"),G=await Y.updateTable("jobs").set({reserved_at:null,available_at:V}).where("reserved_at","<=",L).executeTakeFirst(),U=o(G);if(U>0)D.warn(`[queue] Requeued ${U} job(s) whose reservation exceeded the ${$}s TTL \u2014 likely victims of a worker crash. Set `+"STACKS_QUEUE_RESERVATION_TTL_SEC to tune.");return U}catch(Y){return D.error("[queue] Reservation sweep failed",{reason:Y instanceof Error?Y.message:String(Y)}),0}}function W8($){if(!$||typeof $!=="object")return!1;let L=$.status??$.statusCode;if(typeof L==="number"&&L>=400&&L<500)return!0;let V=$.name;if(typeof V==="string"&&(V==="ValidationError"||V==="ModelNotFoundError"))return!0;return!1}function Z8(){return X8.QUEUE_DRIVER||"sync"}async function O8($,L={}){try{D.info("Starting queue processor..."),Y1=!0,S=`worker-${M1.pid}-${Date.now()}`;let V=L.concurrency||1,Y=Z8(),{getWorkerTracker:G,getGlobalMetrics:U}=await Promise.resolve().then(() => (v(),h));if(U(),G().register(S,$||"default"),Y==="redis")return D.info("Using Redis queue driver (bun-queue)"),await M8($||"default",V),v2(void 0);let X;if($)X=[$];else if(X=await w2(),X.length===0)X=["default"];return D.info(`Processing queues: ${X.join(", ")}`),await F8(X,V),v2(void 0)}catch(V){return Y1=!1,U8(V)}}async function w2(){try{let{db:$}=await import("@stacksjs/database"),V=(await $.selectFrom("jobs").select("queue").distinct().execute()).map((Y)=>Y.queue).filter((Y)=>Boolean(Y));return V.length>0?V:["default"]}catch{return["default"]}}async function F8($,L){D.info("Listening for jobs...");let V=$,Y=Date.now(),G=1e4,U=Date.now(),X=_8();await I2();while(Y1)try{let _=Date.now();if(_-Y>G){try{let K=await w2();if(K.length>0)V=K}catch{}Y=_}if(_-U>X)await I2(),U=_;for(let K of V){try{let{isCircuitOpen:Z}=await Promise.resolve().then(() => (D1(),f1));if(await Z(K))continue}catch{}let z=[];try{z=await x8(K,L)}catch(Z){let x=Date.now(),A=k2.get(K)??0;if(x-A>60000)k2.set(K,x),D.error(`[queue] Could not reserve jobs on "${K}" \u2014 retrying each cycle:`,Z);continue}await Promise.all(z.map(async(Z)=>{try{D.info(`Processing job ${Z.id} from queue "${K}"`),await K8(H8(Z))}catch{D.error(`Unexpected error processing job ${Z.id}`)}}))}await F4(1000)}catch{await F4(3000)}}async function x8($,L){let V=Math.floor(Date.now()/1000),{db:Y}=await import("@stacksjs/database"),G=[];for(let U=0;U<L;U++){let X=await Y.selectFrom("jobs").where("queue","=",$).whereNull("reserved_at").where("available_at","<=",V).orderBy("id","asc").limit(1).selectAll().executeTakeFirst();if(!X)break;let _=await Y.updateTable("jobs").set({reserved_at:V,attempts:(X.attempts||0)+1}).where("id","=",X.id).whereNull("reserved_at").executeTakeFirst();if(o(_)>0)G.push(X)}return G}async function H8($){let L=$.id,V=$.queue||"default";b++;let Y=Date.now(),{emitQueueEvent:G,getWorkerTracker:U}=await Promise.resolve().then(() => (v(),h)),X=U();X.markActive(S);let _;try{_=JSON.parse($.payload||"{}").jobName}catch{}await G("job:processing",{jobId:String(L),queueName:V,jobName:_});try{let Z=JSON.parse($.payload||"{}").payload?._batchId;if(Z){let{isBatchCancelled:x}=await Promise.resolve().then(() => (f(),u));if(await x(Z)){D.info(`[Queue] Skipping job ${L} - batch ${Z} has been cancelled`),await O4(L),b--,X.markIdle(S);return}}}catch{}let K=null;try{let z=JSON.parse($.payload||"{}"),Z=A8(z);if(Z===void 0)await y2(z);else await P8(y2(z),Z*1000,`Job ${L} exceeded ${Z}s timeout`)}catch(z){K=z instanceof Error?z:Error(String(z))}if(!K)try{await O4(L),D.info(`[Queue] Job ${L} completed`),X.recordCompletion(S);try{let{recordCircuitSuccess:z}=await Promise.resolve().then(() => (D1(),f1));await z($.queue??"default")}catch{}await G("job:completed",{jobId:String(L),queueName:V,duration:Date.now()-Y});try{let Z=JSON.parse($.payload||"{}").payload?._batchId;if(Z){let{recordBatchJobCompletion:x}=await Promise.resolve().then(() => (f(),u));await x(Z)}}catch{}}catch{D.info(`[Queue] Failed to delete completed job ${L}`)}else{let z=K.message;D.info(`[Queue] Job ${L} failed: ${z}`),X.recordFailure(S),await G("job:failed",{jobId:String(L),queueName:V,error:K,duration:Date.now()-Y,attemptsMade:($.attempts||0)+1});let Z=1,x={};try{x=JSON.parse($.payload||"{}"),Z=x.options?.tries||1}catch{}let A=($.attempts||0)+1;if(A>=Z){D.info(`[Queue] Job ${L} exceeded max attempts (${A}/${Z})`);let O=!1;if(x?._retriedFromFailed===!0)try{let{moveToDeadLetter:W}=await Promise.resolve().then(() => (N1(),y4));if(O=await W({queue:$.queue,payload:$.payload,exception:K.stack||K.message},"repeat-failure",2),O)D.info(`[Queue] Job ${L} re-failed after retry \u2014 moved to dead_letter_jobs`)}catch{O=!1}if(!O){D.info(`[Queue] Moving job ${L} to failed_jobs`);try{O=await D8($,K,{attempts:A,maxAttempts:Z,durationMs:Date.now()-Y})}catch{O=!1}}if(O)try{await O4(L)}catch{D.info(`[Queue] Failed to delete failed job ${L}`)}else D.error(`[Queue] Job ${L} exhausted its retries but could NOT be persisted to failed_jobs \u2014 leaving it in the queue to avoid data loss (the reservation sweep will retry it). Check that the failed_jobs table exists and is writable.`);try{let{recordFailureForPoison:W}=await Promise.resolve().then(() => (R1(),g4)),{recordCircuitFailure:H}=await Promise.resolve().then(() => (D1(),f1)),s=x?.jobName??"unknown";await W(s,x?.payload),await H($.queue??"default")}catch{}try{let W=x.payload?._batchId;if(W){let{recordBatchJobFailure:H}=await Promise.resolve().then(() => (f(),u));await H(W,String(L),K)}}catch{}}else{let O=x.options?.backoff,W=30;if(Array.isArray(O)&&O.length>0){let H=Math.min(A-1,O.length-1);W=O[H]}else if(typeof O==="number"&&O>0)W=O;if(W=Number(W),!Number.isFinite(W)||W<0)W=30;D.info(`[Queue] Job ${L} will be retried in ${W}s (attempt ${A}/${Z})`);try{await Q8(L,W),D.info(`[Queue] Job ${L} released for retry`)}catch{D.info(`[Queue] Failed to release job ${L} for retry`)}}}b--,X.markIdle(S)}async function O4($){let{db:L}=await import("@stacksjs/database");await L.deleteFrom("jobs").where("id","=",$).execute()}async function Q8($,L=30){let V=Math.floor(Date.now()/1000)+L;D.debug(`Releasing job ${$} for retry at ${V}`);try{let{db:Y}=await import("@stacksjs/database");await Y.updateTable("jobs").set({reserved_at:null,available_at:V}).where("id","=",$).execute(),D.debug(`Job ${$} released successfully`)}catch{D.error(`Failed to release job ${$}`)}}async function D8($,L,V){try{let Y=new Date().toISOString().slice(0,19).replace("T"," "),G=crypto.randomUUID(),U=L.stack||L.message,{db:X}=await import("@stacksjs/database");return await X.insertInto("failed_jobs").values({uuid:G,connection:"database",queue:$.queue,payload:$.payload,exception:U,attempts:V.attempts,max_attempts:V.maxAttempts,duration_ms:V.durationMs,failed_at:Y}).execute(),!0}catch(Y){return D.error("Failed to log failed job:",Y),!1}}function A8($){if(!$||typeof $!=="object")return;let V=$.options?.timeout;if(typeof V!=="number"||!Number.isFinite(V)||V<=0)return;return V}async function P8($,L,V){let Y,G=new Promise((U,X)=>{Y=setTimeout(()=>X(Error(V)),L)});try{return await Promise.race([$,G])}finally{if(Y!==void 0)clearTimeout(Y)}}async function y2($){let L=B1($);if(!L.ok)throw Error(`[queue] Cannot deserialize job envelope: ${L.reason}`+(L.detail?` (${L.detail})`:""));let{runJob:V}=await Promise.resolve().then(() => (Q1(),y1));await V(L.envelope.jobName,{payload:L.envelope.payload,traceId:L.envelope.traceId})}async function M8($,L){let{RedisQueue:V}=await Promise.resolve().then(() => (_1(),z1)),{queue:Y}=await import("@stacksjs/config"),G=Y?.connections?.redis;if(!G)throw Error("Redis queue connection is not configured. Check config/queue.ts");let U=new V($,G),{emitQueueEvent:X,getWorkerTracker:_}=await Promise.resolve().then(() => (v(),h)),K=_();U.process(L,async(z)=>{b++,K.markActive(S);let Z=Date.now(),x=B1(z.data);if(!x.ok){b--,K.markIdle(S),D.error(`[Queue] Skipping Redis job ${z.id} - unparseable envelope: ${x.reason}${x.detail?` (${x.detail})`:""}`);return}let A=x.envelope,O=A.payload?._batchId;if(O)try{let{isBatchCancelled:W}=await Promise.resolve().then(() => (f(),u));if(await W(O)){D.info(`[Queue] Skipping Redis job ${z.id} - batch ${O} has been cancelled`),b--,K.markIdle(S);return}}catch{}await X("job:processing",{jobId:String(z.id),queueName:$});try{let{runJob:W}=await Promise.resolve().then(() => (Q1(),y1));if(await W(A.jobName,{payload:A.payload,traceId:A.traceId}),K.recordCompletion(S),await X("job:completed",{jobId:String(z.id),queueName:$,duration:Date.now()-Z}),O)try{let{recordBatchJobCompletion:H}=await Promise.resolve().then(() => (f(),u));await H(O)}catch{}D.info(`[Queue] Redis job ${z.id} completed`)}catch(W){if(K.recordFailure(S),await X("job:failed",{jobId:String(z.id),queueName:$,error:W instanceof Error?W:Error(String(W)),duration:Date.now()-Z}),O)try{let{recordBatchJobFailure:H}=await Promise.resolve().then(() => (f(),u));await H(O,String(z.id),W instanceof Error?W:Error(String(W)))}catch{}if(D.error(`[Queue] Redis job ${z.id} failed: ${W}`),W8(W)){D.info(`[Queue] Redis job ${z.id} hit a non-retryable error \u2014 skipping retry`);return}throw W}finally{b--,K.markIdle(S)}}),D.info(`Listening for Redis jobs on queue "${$}" with concurrency ${L}...`);while(Y1)await F4(1000);await U.close()}async function B8($={}){let L=$.graceMs??1e4;if(Y1=!1,d.size>0&&L>0){D.info(`[queue] Draining ${d.size} in-flight job(s) (grace ${L}ms)`);let V=Promise.allSettled([...d]),Y=new Promise((U)=>setTimeout(()=>U("timeout"),L));if(await Promise.race([V.then(()=>"drained"),Y])==="timeout"&&d.size>0)D.warn(`[queue] Drain timed out with ${d.size} job(s) still active. Their reservations will be reclaimed by the next worker's sweep (Q-2).`)}if(S){let{getWorkerTracker:V}=await Promise.resolve().then(() => (v(),h));V().unregister(S)}D.info("Queue processor stopped")}async function E8(){let{db:$}=await import("@stacksjs/database"),L=await $.selectFrom("failed_jobs").selectAll().execute();for(let V of L)await f2(Number(V.id))}async function f2($){let L=Math.floor(Date.now()/1000),V=new Date().toISOString().slice(0,19).replace("T"," "),{db:Y}=await import("@stacksjs/database"),U=(await Y.selectFrom("failed_jobs").where("id","=",$).selectAll().execute())[0];if(!U)throw Error(`Failed job ${$} not found`);let X=U.payload;try{let _=JSON.parse(U.payload||"{}");_._retriedFromFailed=!0,X=JSON.stringify(_)}catch{}await Y.insertInto("jobs").values({queue:U.queue,payload:X,attempts:0,reserved_at:null,available_at:L,created_at:V}).execute(),await Y.deleteFrom("failed_jobs").where("id","=",$).execute(),D.info(`Failed job ${$} has been re-queued`)}function T8(){return b}function S8(){return Y1}function F4($){return new Promise((L)=>setTimeout(L,$))}async function J9(){let{RedisQueue:$}=await Promise.resolve().then(() => (_1(),z1));return $}export{b4 as withEvents,h4 as unquarantineJob,r5 as triggerJob,u5 as toJobOptions,_4 as stopScheduler,B8 as stopProcessor,n5 as startScheduler,O8 as startProcessor,I5 as setJobProgress,T4 as runTestJob,w1 as runJob,f2 as retryFailedJob,k4 as retryDeadLetterJob,F2 as resumeQueue,p1 as restore,c1 as releaseDispatchKey,j4 as recordFailureForPoison,X5 as recordDispatchedKey,W2 as recordCircuitSuccess,Z2 as recordCircuitFailure,L4 as recordBatchJobFailure,$4 as recordBatchJobCompletion,m4 as quarantineJob,I4 as purgeDeadLetterJobs,O2 as pauseQueue,B1 as parseEnvelope,u4 as onQueueEvent,G8 as notifyJobFailed,S1 as moveToDeadLetter,p4 as listQuarantined,v4 as listDeadLetterJobs,x2 as listCircuitState,n as jobRegistry,X2 as jobBatch,G2 as job,S8 as isWorkerRunning,s5 as isSchedulerRunning,$8 as isQueueHealthy,J1 as isQuarantined,f5 as isJobCancelled,B4 as isFaked,_2 as isCircuitOpen,L2 as isBatchCancelled,Z1 as hashPayload,U5 as hasDispatchedKey,O1 as getWorkerTracker,d5 as getSchedulerStatus,K4 as getScheduledJobs,t5 as getRegisteredJobs,J9 as getRedisQueue,e as getQueueEvents,y5 as getJobProgress,h5 as getJob,v1 as getGlobalMetrics,M4 as getFakeQueue,Y8 as getFailedJobNotifier,F1 as getBatchCallbacks,p5 as getAllJobs,T8 as getActiveJobCount,h1 as fake,S4 as expectJobToFail,g5 as executeJob,E8 as executeFailedJobs,l as emitQueueEvent,X4 as discoverJobs,E4 as createQueueTester,e5 as createHealthCheckHandler,m as createEnvelope,V8 as configureFailedJobNotifications,j5 as clearJobState,d2 as clearEnvelopeWarnings,l1 as claimDispatchKey,W4 as checkQueueHealth,w5 as cancelJob,E1 as QueueTester,q1 as QueueMetrics,C1 as QueueEvents,I1 as PendingBatch,l4 as OnQueueEvent,U2 as Jobs,C4 as Job,n2 as JOB_ENVELOPE_VERSION,Z4 as FailedJobNotifier,p as DispatchedBatch,t1 as Batch};
31
+ \`\`\`${Y.exception.slice(0,200)}\`\`\``}}))];if(L.length>10)V.push({type:"section",text:{type:"mrkdwn",text:`_...and ${L.length-10} more failed jobs_`}});try{let Y=await fetch($.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({channel:$.channel,username:$.username||"Queue Monitor",icon_emoji:$.iconEmoji||":warning:",blocks:V})});if(!Y.ok)C.error(`Slack notification failed with status ${Y.status}`)}catch(Y){C.error("Failed to send Slack notification:",Y)}}async sendDiscord(L){let $=this.config.discord,V=L.slice(0,10).map((Y)=>({title:`\u274C ${Y.name}`,color:15158332,fields:[{name:"Queue",value:Y.queue,inline:!0},{name:"Attempts",value:`${Y.attempts}/${Y.maxAttempts}`,inline:!0},{name:"Failed At",value:Y.failedAt.toISOString(),inline:!0},{name:"Exception",value:`\`\`\`${Y.exception.slice(0,500)}\`\`\``}],timestamp:Y.failedAt.toISOString()}));try{let Y=await fetch($.webhookUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:$.username||"Queue Monitor",avatar_url:$.avatarUrl,content:`\uD83D\uDEA8 **${L.length} Job(s) Failed**`,embeds:V})});if(!Y.ok)C.error(`Discord notification failed with status ${Y.status}`)}catch(Y){C.error("Failed to send Discord notification:",Y)}}async sendWebhook(L){let $=this.config.webhook,V={event:"jobs.failed",timestamp:new Date().toISOString(),count:L.length,jobs:L.map((G)=>({id:G.id,name:G.name,queue:G.queue,attempts:G.attempts,maxAttempts:G.maxAttempts,exception:G.exception,failedAt:G.failedAt.toISOString()}))},Y={"Content-Type":"application/json",...$.headers};if($.secret){let G=JSON.stringify(V),U=await this.generateSignature(G,$.secret);Y["X-Signature"]=U}try{let G=await fetch($.url,{method:"POST",headers:Y,body:JSON.stringify(V)});if(!G.ok)C.error(`Webhook notification failed with status ${G.status}`)}catch(G){C.error("Failed to send webhook notification:",G)}}async generateSignature(L,$){let V=new TextEncoder,Y=await crypto.subtle.importKey("raw",V.encode($),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),G=await crypto.subtle.sign("HMAC",Y,V.encode(L));return Array.from(new Uint8Array(G)).map((U)=>U.toString(16).padStart(2,"0")).join("")}logFailures(L){for(let $ of L)C.error(`[Queue] Job "${$.name}" failed on queue "${$.queue}" after ${$.attempts} attempts: ${$.exception}`)}async cleanup(){if(this.batchTimeout)clearTimeout(this.batchTimeout),this.batchTimeout=null;if(this.pendingBatch.length>0)await this.flushBatch()}}var B1=null;function F9(L){return B1=new Q4(L),B1}function H9(){return B1}async function Q9(L){if(B1)await B1.notify(L)}g();a();q1();import{err as A9,ok as wL}from"@stacksjs/error-handling";import{log as D}from"@stacksjs/logging";import W1 from"process";import{env as D9}from"@stacksjs/env";W1.on("unhandledRejection",(L,$)=>{D.error(`Unhandled Rejection: ${L}`)});W1.on("uncaughtException",(L)=>{D.error(`Uncaught Exception: ${L.message}`)});var i=0,X1=!1,N="",t=new Set,fL=new Map;function hL(L){return t.add(L),L.finally(()=>t.delete(L)),L}function x9(){let L=W1.env.STACKS_QUEUE_RESERVATION_TTL_SEC,$=L===void 0?Number.NaN:Number.parseInt(L,10);return Number.isFinite($)&&$>0?$:3600}function P9(){let L=W1.env.STACKS_QUEUE_RETRY_JITTER;if(L===void 0)return 0.2;let $=Number(L);if(!Number.isFinite($)||$<0)return 0.2;return Math.min($,1)}function M9(L,$,V=Math.random){if(!(L>0)||$<=0)return L;return Math.round(L+L*$*V())}function B9(){let L=W1.env.STACKS_QUEUE_SWEEP_INTERVAL_SEC,$=L===void 0?Number.NaN:Number.parseInt(L,10);return(Number.isFinite($)&&$>0?$:60)*1000}async function jL(){let L=x9(),$=Math.floor(Date.now()/1000)-L,V=Math.floor(Date.now()/1000);try{let{db:Y}=await import("@stacksjs/database"),G=await Y.updateTable("jobs").set({reserved_at:null,available_at:V}).where("reserved_at","<=",$).executeTakeFirst(),U=c(G);if(U>0)D.warn(`[queue] Requeued ${U} job(s) whose reservation exceeded the ${L}s TTL \u2014 likely victims of a worker crash. Set `+"STACKS_QUEUE_RESERVATION_TTL_SEC to tune.");return U}catch(Y){return D.error("[queue] Reservation sweep failed",{reason:Y instanceof Error?Y.message:String(Y)}),0}}function T9(L){if(!L||typeof L!=="object")return!1;let $=L.status??L.statusCode;if(typeof $==="number"&&$>=400&&$<500)return!0;let V=L.name;if(typeof V==="string"&&(V==="ValidationError"||V==="ModelNotFoundError"))return!0;return!1}function E9(){return D9.QUEUE_DRIVER||"sync"}async function N9(L,$={}){try{D.info("Starting queue processor..."),X1=!0,N=`worker-${W1.pid}-${Date.now()}`;let V=$.concurrency||1,Y=E9(),{getWorkerTracker:G,getGlobalMetrics:U}=await Promise.resolve().then(() => (k(),b));if(U(),G().register(N,L||"default"),Y==="redis")return D.info("Using Redis queue driver (bun-queue)"),await v9(L||"default",V),wL(void 0);let K;if(L)K=[L];else if(K=await gL(),K.length===0)K=["default"];return D.info(`Processing queues: ${K.join(", ")}`),await J9(K,V),wL(void 0)}catch(V){return X1=!1,A9(V)}}async function gL(){try{let{db:L}=await import("@stacksjs/database"),V=(await L.selectFrom("jobs").select("queue").distinct().execute()).map((Y)=>Y.queue).filter((Y)=>Boolean(Y));return V.length>0?V:["default"]}catch{return["default"]}}async function J9(L,$){D.info("Listening for jobs...");let V=L,Y=Date.now(),G=1e4,U=Date.now(),K=B9();await jL();while(X1)try{let z=Date.now();if(z-Y>G){try{let X=await gL();if(X.length>0)V=X}catch{}Y=z}if(z-U>K)await jL(),U=z;for(let X of V){try{let{isCircuitOpen:_}=await Promise.resolve().then(() => (P1(),m1));if(await _(X))continue}catch{}let W=[];try{W=await S9(X,$)}catch(_){let A=Date.now(),x=fL.get(X)??0;if(A-x>60000)fL.set(X,A),D.error(`[queue] Could not reserve jobs on "${X}" \u2014 retrying each cycle:`,_);continue}await Promise.all(W.map(async(_)=>{try{D.info(`Processing job ${_.id} from queue "${X}"`),await hL(R9(_))}catch{D.error(`Unexpected error processing job ${_.id}`)}}))}await D4(1000)}catch{await D4(3000)}}async function S9(L,$){let V=Math.floor(Date.now()/1000),{db:Y}=await import("@stacksjs/database"),G=[];for(let U=0;U<$;U++){let K=await Y.selectFrom("jobs").where("queue","=",L).whereNull("reserved_at").where("available_at","<=",V).orderBy("id","asc").limit(1).selectAll().executeTakeFirst();if(!K)break;let z=await Y.updateTable("jobs").set({reserved_at:V,attempts:(K.attempts||0)+1}).where("id","=",K.id).whereNull("reserved_at").executeTakeFirst();if(c(z)>0)G.push(K)}return G}async function R9(L){let $=L.id,V=L.queue||"default";i++;let Y=Date.now(),{emitQueueEvent:G,getWorkerTracker:U}=await Promise.resolve().then(() => (k(),b)),K=U();K.markActive(N);let z;try{z=JSON.parse(L.payload||"{}").jobName}catch{}await G("job:processing",{jobId:String($),queueName:V,jobName:z});try{let _=JSON.parse(L.payload||"{}").payload?._batchId;if(_){let{isBatchCancelled:A}=await Promise.resolve().then(() => (g(),l));if(await A(_)){D.info(`[Queue] Skipping job ${$} - batch ${_} has been cancelled`),await A4($),i--,K.markIdle(N);return}}}catch{}let X=null;try{let W=JSON.parse(L.payload||"{}"),_=k9(W);if(_===void 0)await mL(W);else await I9(mL(W),_*1000,`Job ${$} exceeded ${_}s timeout`)}catch(W){X=W instanceof Error?W:Error(String(W))}if(!X)try{await A4($),D.info(`[Queue] Job ${$} completed`),K.recordCompletion(N);try{let{recordCircuitSuccess:W}=await Promise.resolve().then(() => (P1(),m1));await W(L.queue??"default")}catch{}await G("job:completed",{jobId:String($),queueName:V,duration:Date.now()-Y});try{let _=JSON.parse(L.payload||"{}").payload?._batchId;if(_){let{recordBatchJobCompletion:A}=await Promise.resolve().then(() => (g(),l));await A(_)}}catch{}}catch{D.info(`[Queue] Failed to delete completed job ${$}`)}else{let W=X.message;D.info(`[Queue] Job ${$} failed: ${W}`),K.recordFailure(N),await G("job:failed",{jobId:String($),queueName:V,error:X,duration:Date.now()-Y,attemptsMade:(L.attempts||0)+1});let _=1,A={};try{A=JSON.parse(L.payload||"{}"),_=A.options?.tries||1}catch{}let x=(L.attempts||0)+1;if(x>=_){D.info(`[Queue] Job ${$} exceeded max attempts (${x}/${_})`);let O=!1;if(A?._retriedFromFailed===!0)try{let{moveToDeadLetter:Z}=await Promise.resolve().then(() => (S1(),m4));if(O=await Z({queue:L.queue,payload:L.payload,exception:X.stack||X.message},"repeat-failure",2),O)D.info(`[Queue] Job ${$} re-failed after retry \u2014 moved to dead_letter_jobs`)}catch{O=!1}if(!O){D.info(`[Queue] Moving job ${$} to failed_jobs`);try{O=await q9(L,X,{attempts:x,maxAttempts:_,durationMs:Date.now()-Y})}catch{O=!1}}if(O)try{await A4($)}catch{D.info(`[Queue] Failed to delete failed job ${$}`)}else D.error(`[Queue] Job ${$} exhausted its retries but could NOT be persisted to failed_jobs \u2014 leaving it in the queue to avoid data loss (the reservation sweep will retry it). Check that the failed_jobs table exists and is writable.`);try{let{recordFailureForPoison:Z}=await Promise.resolve().then(() => (C1(),i4)),{recordCircuitFailure:F}=await Promise.resolve().then(() => (P1(),m1)),I=A?.jobName??"unknown";await Z(I,A?.payload),await F(L.queue??"default")}catch{}try{let Z=A.payload?._batchId;if(Z){let{recordBatchJobFailure:F}=await Promise.resolve().then(() => (g(),l));await F(Z,String($),X)}}catch{}}else{let O=A.options?.backoff,Z=30;if(Array.isArray(O)&&O.length>0){let F=Math.min(x-1,O.length-1);Z=O[F]}else if(typeof O==="number"&&O>0)Z=O;if(Z=Number(Z),!Number.isFinite(Z)||Z<0)Z=30;Z=M9(Z,P9()),D.info(`[Queue] Job ${$} will be retried in ${Z}s (attempt ${x}/${_})`);try{await C9($,Z),D.info(`[Queue] Job ${$} released for retry`)}catch{D.info(`[Queue] Failed to release job ${$} for retry`)}}}i--,K.markIdle(N)}async function A4(L){let{db:$}=await import("@stacksjs/database");await $.deleteFrom("jobs").where("id","=",L).execute()}async function C9(L,$=30){let V=Math.floor(Date.now()/1000)+$;D.debug(`Releasing job ${L} for retry at ${V}`);try{let{db:Y}=await import("@stacksjs/database");await Y.updateTable("jobs").set({reserved_at:null,available_at:V}).where("id","=",L).execute(),D.debug(`Job ${L} released successfully`)}catch{D.error(`Failed to release job ${L}`)}}async function q9(L,$,V){try{let Y=new Date().toISOString().slice(0,19).replace("T"," "),G=crypto.randomUUID(),U=$.stack||$.message,{db:K}=await import("@stacksjs/database");return await K.insertInto("failed_jobs").values({uuid:G,connection:"database",queue:L.queue,payload:L.payload,exception:U,attempts:V.attempts,max_attempts:V.maxAttempts,duration_ms:V.durationMs,failed_at:Y}).execute(),!0}catch(Y){return D.error("Failed to log failed job:",Y),!1}}function k9(L){if(!L||typeof L!=="object")return;let V=L.options?.timeout;if(typeof V!=="number"||!Number.isFinite(V)||V<=0)return;return V}async function I9(L,$,V){let Y,G=new Promise((U,K)=>{Y=setTimeout(()=>K(Error(V)),$)});try{return await Promise.race([L,G])}finally{if(Y!==void 0)clearTimeout(Y)}}async function mL(L){let $=T1(L);if(!$.ok)throw Error(`[queue] Cannot deserialize job envelope: ${$.reason}`+($.detail?` (${$.detail})`:""));let{runJob:V}=await Promise.resolve().then(() => (x1(),f1));await V($.envelope.jobName,{payload:$.envelope.payload,traceId:$.envelope.traceId})}async function v9(L,$){let{RedisQueue:V}=await Promise.resolve().then(() => (F1(),O1)),{queue:Y}=await import("@stacksjs/config"),G=Y?.connections?.redis;if(!G)throw Error("Redis queue connection is not configured. Check config/queue.ts");let U=new V(L,G),{emitQueueEvent:K,getWorkerTracker:z}=await Promise.resolve().then(() => (k(),b)),X=z(),W=async(_)=>{i++,X.markActive(N);let A=Date.now(),x=T1(_.data);if(!x.ok){i--,X.markIdle(N),D.error(`[Queue] Skipping Redis job ${_.id} - unparseable envelope: ${x.reason}${x.detail?` (${x.detail})`:""}`);return}let O=x.envelope,Z=O.payload?._batchId;if(Z)try{let{isBatchCancelled:F}=await Promise.resolve().then(() => (g(),l));if(await F(Z)){D.info(`[Queue] Skipping Redis job ${_.id} - batch ${Z} has been cancelled`),i--,X.markIdle(N);return}}catch{}await K("job:processing",{jobId:String(_.id),queueName:L});try{let{runJob:F}=await Promise.resolve().then(() => (x1(),f1));if(await F(O.jobName,{payload:O.payload,traceId:O.traceId}),X.recordCompletion(N),await K("job:completed",{jobId:String(_.id),queueName:L,duration:Date.now()-A}),Z)try{let{recordBatchJobCompletion:I}=await Promise.resolve().then(() => (g(),l));await I(Z)}catch{}D.info(`[Queue] Redis job ${_.id} completed`)}catch(F){if(X.recordFailure(N),await K("job:failed",{jobId:String(_.id),queueName:L,error:F instanceof Error?F:Error(String(F)),duration:Date.now()-A}),Z)try{let{recordBatchJobFailure:I}=await Promise.resolve().then(() => (g(),l));await I(Z,String(_.id),F instanceof Error?F:Error(String(F)))}catch{}if(D.error(`[Queue] Redis job ${_.id} failed: ${F}`),T9(F)){D.info(`[Queue] Redis job ${_.id} hit a non-retryable error \u2014 skipping retry`);return}throw F}finally{i--,X.markIdle(N)}};U.process($,(_)=>hL(W(_))),D.info(`Listening for Redis jobs on queue "${L}" with concurrency ${$}...`);while(X1)await D4(1000);await U.close()}async function y9(L={}){let $=L.graceMs??1e4;if(X1=!1,t.size>0&&$>0){D.info(`[queue] Draining ${t.size} in-flight job(s) (grace ${$}ms)`);let V=Promise.allSettled([...t]),Y=new Promise((U)=>setTimeout(()=>U("timeout"),$));if(await Promise.race([V.then(()=>"drained"),Y])==="timeout"&&t.size>0)D.warn(`[queue] Drain timed out with ${t.size} job(s) still active. Their reservations will be reclaimed by the next worker's sweep (Q-2).`)}if(N){let{getWorkerTracker:V}=await Promise.resolve().then(() => (k(),b));V().unregister(N)}D.info("Queue processor stopped")}async function w9(){let{db:L}=await import("@stacksjs/database"),$=await L.selectFrom("failed_jobs").selectAll().execute();for(let V of $)await pL(Number(V.id))}async function pL(L){let $=Math.floor(Date.now()/1000),V=new Date().toISOString().slice(0,19).replace("T"," "),{db:Y}=await import("@stacksjs/database"),U=(await Y.selectFrom("failed_jobs").where("id","=",L).selectAll().execute())[0];if(!U)throw Error(`Failed job ${L} not found`);let K=U.payload;try{let z=JSON.parse(U.payload||"{}");z._retriedFromFailed=!0,K=JSON.stringify(z)}catch{}await Y.insertInto("jobs").values({queue:U.queue,payload:K,attempts:0,reserved_at:null,available_at:$,created_at:V}).execute(),await Y.deleteFrom("failed_jobs").where("id","=",L).execute(),D.info(`Failed job ${L} has been re-queued`)}function f9(){return i}function j9(){return X1}function D4(L){return new Promise(($)=>setTimeout($,L))}async function g$(){let{RedisQueue:L}=await Promise.resolve().then(() => (F1(),O1));return L}export{d4 as withEvents,u4 as unquarantineJob,X9 as triggerJob,a0 as toJobOptions,F4 as stopScheduler,y9 as stopProcessor,Y9 as startScheduler,N9 as startProcessor,l0 as setJobProgress,j as serializeEnvelope,R4 as runTestJob,j1 as runJob,pL as retryFailedJob,f4 as retryDeadLetterJob,DL as resumeQueue,l1 as restore,s1 as releaseDispatchKey,p4 as recordFailureForPoison,H0 as recordDispatchedKey,HL as recordCircuitSuccess,QL as recordCircuitFailure,U4 as recordBatchJobFailure,G4 as recordBatchJobCompletion,b4 as quarantineJob,j4 as purgeDeadLetterJobs,AL as pauseQueue,T1 as parseEnvelope,c4 as onQueueEvent,Q9 as notifyJobFailed,J1 as moveToDeadLetter,l4 as listQuarantined,w4 as listDeadLetterJobs,xL as listCircuitState,r as jobRegistry,_L as jobBatch,WL as job,j9 as isWorkerRunning,U9 as isSchedulerRunning,Z9 as isQueueHealthy,R1 as isQuarantined,d0 as isJobCancelled,J4 as isFaked,FL as isCircuitOpen,UL as isBatchCancelled,Q1 as hashPayload,F0 as hasDispatchedKey,A1 as getWorkerTracker,G9 as getSchedulerStatus,Z4 as getScheduledJobs,K9 as getRegisteredJobs,g$ as getRedisQueue,$1 as getQueueEvents,i0 as getJobProgress,r0 as getJob,v1 as getGlobalMetrics,N4 as getFakeQueue,H9 as getFailedJobNotifier,D1 as getBatchCallbacks,t0 as getAllJobs,f9 as getActiveJobCount,u1 as fake,C4 as expectJobToFail,o0 as executeJob,w9 as executeFailedJobs,d as emitQueueEvent,_4 as discoverJobs,S4 as createQueueTester,_9 as createHealthCheckHandler,y as createEnvelope,F9 as configureFailedJobNotifications,n0 as clearJobState,$0 as clearEnvelopeWarnings,n1 as claimDispatchKey,H4 as checkQueueHealth,c0 as cancelJob,_1 as assertEnvelopeSerializable,E1 as QueueTester,I1 as QueueMetrics,k1 as QueueEvents,w1 as PendingBatch,n4 as OnQueueEvent,zL as Jobs,v4 as Job,oL as JOB_ENVELOPE_VERSION,Q4 as FailedJobNotifier,u as DispatchedBatch,e1 as Batch};
package/dist/utils.d.ts CHANGED
@@ -10,4 +10,40 @@ import type { QueueOption } from '@stacksjs/types';
10
10
  * 2026-07-04, 1,600+ jobs stuck reserved).
11
11
  */
12
12
  export declare function updatedRowCount(result: unknown): number;
13
+ /**
14
+ * Build the row `storeJob` writes, without writing it.
15
+ *
16
+ * Split out from `storeJob` so the shape that reaches the `jobs` table is
17
+ * reachable in a test without a database — the wiring below it is one insert.
18
+ *
19
+ * Routed through `createEnvelope` rather than merely guarded
20
+ * (stacksjs/stacks#2282 item 6). The object this used to build by hand was,
21
+ * field for field, the PRE-#1884 v0 shape: `{ jobName, payload, options }`,
22
+ * with no `envelopeVersion` and no `dispatchedAt`. Every scheduled dispatch —
23
+ * this is the writer behind `scheduler.ts`'s tick and `triggerJob` — therefore
24
+ * landed in the table looking like a job queued before the upgrade. The
25
+ * worker's `parseEnvelope` took its `v0-implicit` branch for them, warning
26
+ * that the migration window was closing about rows the CURRENT build had
27
+ * written a moment earlier, and stamping each one `dispatchedAt: 1970-01-01`
28
+ * because there was nothing to read — the one field "this job sat in the queue
29
+ * for X" is measured from. Guarding the hand-built object would have bought
30
+ * the better error message and left the shape drifted; there is no second wire
31
+ * format here worth preserving.
32
+ *
33
+ * One coupling to keep in mind before touching this: `hasUnfinishedRun` finds a
34
+ * job's queue row with `payload LIKE '%"jobName":"<name>"%'`, because `jobs`
35
+ * has no name column. `createEnvelope` emits `jobName` first, so the pattern
36
+ * matches exactly as it did — and it is wrapped in `%` on both sides, so it
37
+ * would match wherever the key sat. That property is covered behaviourally in
38
+ * `tests/envelope-json-contract.test.ts` rather than by grepping this file.
39
+ */
40
+ export declare function buildScheduledJobRow(name: string, options: QueueOption): ScheduledJobRow;
13
41
  export declare function storeJob(name: string, options: QueueOption): Promise<void>;
42
+ /** The `jobs` row a scheduled dispatch becomes. */
43
+ export declare interface ScheduledJobRow {
44
+ queue: string
45
+ payload: string
46
+ attempts: number
47
+ available_at: number
48
+ created_at: string
49
+ }
package/dist/worker.d.ts CHANGED
@@ -1,4 +1,27 @@
1
1
  import type { Result } from '@stacksjs/error-handling';
2
+ /**
3
+ * Spread a retry across a window so a downstream outage does not resynchronise
4
+ * every failed job onto the same second.
5
+ *
6
+ * Without this, a provider going down for a minute failed N jobs at once, and
7
+ * `backoff[0]` sent all N back at once, and they failed together again: the
8
+ * herd never breaks up, and each wave hits the recovering dependency harder
9
+ * than the last (stacksjs/stacks#2282 item 2).
10
+ *
11
+ * The spread is **additive only**. Classic full jitter picks uniformly from
12
+ * `[0, delay]`, which retries sooner than asked, and `backoff` here is
13
+ * frequently a rate-limit accommodation ("wait 60s, the API says so") rather
14
+ * than a politeness hint. Retrying at 12s because a coin came up short would
15
+ * be a behaviour change with a real failure mode, so the window opens after
16
+ * the configured delay, never before it.
17
+ *
18
+ * A delay of zero stays zero: someone who asked for an immediate retry gets
19
+ * one.
20
+ *
21
+ * Exported for testing with a supplied `random`, since asserting on a spread
22
+ * needs a deterministic source.
23
+ */
24
+ export declare function applyRetryJitter(delaySeconds: number, ratio: number, random?: () => number): number;
2
25
  /**
3
26
  * Start the queue processor (worker)
4
27
  * This is the main function called by `buddy queue:work`
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/queue",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.352",
5
+ "version": "0.70.354",
6
6
  "description": "The Stacks Queue system powered by bun-queue.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [