@stacksjs/queue 0.70.23 → 0.70.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ import type { JobOptions } from '@stacksjs/types';
2
+ /*.ts) and dispatch.
3
+ * Inspired by Laravel's dispatchable jobs.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * // app/Jobs/SendWelcomeEmail.ts
8
+ * export default new Job({
9
+ * name: 'SendWelcomeEmail',
10
+ * queue: 'emails',
11
+ * tries: 3,
12
+ * backoff: [10, 30, 60],
13
+ *
14
+ * async handle(payload: { email: string }) {
15
+ * await sendEmail(payload.email)
16
+ * },
17
+ * })
18
+ *
19
+ * // Dispatching:
20
+ * import SendWelcomeEmail from '~/app/Jobs/SendWelcomeEmail'
21
+ *
22
+ * await SendWelcomeEmail.dispatch({ email: 'user@example.com' })
23
+ * await SendWelcomeEmail.dispatchIf(user.isNew, { email: user.email })
24
+ * await SendWelcomeEmail.dispatchAfter(60, { email: user.email })
25
+ * await SendWelcomeEmail.dispatchNow({ email: user.email })
26
+ * ```
27
+ */
28
+ export declare class Job {
29
+ name: JobOptions['name'];
30
+ description: JobOptions['description'];
31
+ action?: JobOptions['action'];
32
+ handle?: JobOptions['handle'];
33
+ queue?: string;
34
+ rate: JobOptions['rate'];
35
+ tries: JobOptions['tries'];
36
+ timeout?: number;
37
+ backoff: JobOptions['backoff'];
38
+ backoffConfig: JobOptions['backoffConfig'];
39
+ enabled: JobOptions['enabled'];
40
+ constructor(options: JobOptions & { queue?: string; timeout?: number });
41
+ dispatch(payload?: any): Promise<void>;
42
+ dispatchIf(condition: boolean, payload?: any): Promise<void>;
43
+ dispatchUnless(condition: boolean, payload?: any): Promise<void>;
44
+ dispatchAfter(delaySeconds: number, payload?: any): Promise<void>;
45
+ dispatchNow(payload?: any): Promise<void>;
46
+ }
@@ -0,0 +1,115 @@
1
+ import type { Job } from './action';
2
+ export declare function getBatchCallbacks(batchId: string): BatchOptions | undefined;
3
+ /**
4
+ * Record that a job in a batch completed successfully.
5
+ * Called by the worker after a batch job finishes.
6
+ *
7
+ * Uses an atomic decrement on the database so two concurrent workers
8
+ * finishing batch jobs at the same instant don't both read pending=N
9
+ * and both write pending=N-1 — that race used to leave the counter
10
+ * stuck above zero and the batch's `then`/`finally` callbacks never
11
+ * fired. Falls back to read-modify-write (with a debug log) when the
12
+ * driver doesn't expose atomic SQL.
13
+ */
14
+ export declare function recordBatchJobCompletion(batchId: string): Promise<void>;
15
+ /**
16
+ * Record that a job in a batch failed.
17
+ * Called by the worker when a batch job fails its final attempt.
18
+ */
19
+ export declare function recordBatchJobFailure(batchId: string, jobId: string, error: Error): Promise<void>;
20
+ /**
21
+ * Check if a batch has been cancelled (used by worker to skip jobs)
22
+ */
23
+ export declare function isBatchCancelled(batchId: string): Promise<boolean>;
24
+ /**
25
+ * A pending job within a batch (job + optional payload)
26
+ */
27
+ export declare interface BatchableJob {
28
+ job: Job
29
+ payload?: any
30
+ }
31
+ /**
32
+ * Batch record stored in the database
33
+ */
34
+ export declare interface BatchRecord {
35
+ id: string
36
+ name: string
37
+ total_jobs: number
38
+ pending_jobs: number
39
+ failed_jobs: number
40
+ failed_job_ids: string
41
+ options: string
42
+ cancelled_at: string | null
43
+ created_at: string
44
+ finished_at: string | null
45
+ }
46
+ /**
47
+ * Batch options for callbacks and behavior
48
+ */
49
+ export declare interface BatchOptions {
50
+ name?: string
51
+ queue?: string
52
+ allowFailures?: boolean
53
+ thenCallbacks: Array<(batch: PendingBatch | DispatchedBatch) => Promise<void> | void>
54
+ catchCallbacks: Array<(batch: PendingBatch | DispatchedBatch, error: Error) => Promise<void> | void>
55
+ finallyCallbacks: Array<(batch: PendingBatch | DispatchedBatch) => Promise<void> | void>
56
+ progressCallbacks: Array<(batch: PendingBatch | DispatchedBatch) => Promise<void> | void>
57
+ }
58
+ /**
59
+ * Batch status
60
+ */
61
+ export type BatchStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';
62
+ /**
63
+ * PendingBatch - a batch that has been configured but not yet dispatched
64
+ */
65
+ export declare class PendingBatch {
66
+ constructor(jobs: Array<Job | BatchableJob>);
67
+ name(name: string): this;
68
+ onQueue(queue: string): this;
69
+ allowFailures(): this;
70
+ then(callback: (batch: PendingBatch | DispatchedBatch) => Promise<void> | void): this;
71
+ catch(callback: (batch: PendingBatch | DispatchedBatch, error: Error) => Promise<void> | void): this;
72
+ finally(callback: (batch: PendingBatch | DispatchedBatch) => Promise<void> | void): this;
73
+ progress(callback: (batch: PendingBatch | DispatchedBatch) => Promise<void> | void): this;
74
+ dispatch(): Promise<DispatchedBatch>;
75
+ getJobs(): BatchableJob[];
76
+ getOptions(): BatchOptions;
77
+ }
78
+ /**
79
+ * DispatchedBatch - a batch that has been dispatched and can be inspected
80
+ */
81
+ export declare class DispatchedBatch {
82
+ readonly id: string;
83
+ constructor(id: string);
84
+ fresh(): Promise<BatchRecord | null>;
85
+ getName(): Promise<string>;
86
+ totalJobs(): Promise<number>;
87
+ pendingJobs(): Promise<number>;
88
+ failedJobs(): Promise<number>;
89
+ completedJobs(): Promise<number>;
90
+ progress(): Promise<number>;
91
+ finished(): Promise<boolean>;
92
+ cancelled(): Promise<boolean>;
93
+ hasFailures(): Promise<boolean>;
94
+ failedJobIds(): Promise<string[]>;
95
+ cancel(): Promise<void>;
96
+ add(jobs: Array<Job | BatchableJob>): Promise<void>;
97
+ delete(): Promise<void>;
98
+ }
99
+ /**
100
+ * Create a new pending batch
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * const batch = Batch.create([job1, job2, job3])
105
+ * .name('My Batch')
106
+ * .then(async (_batch) => console.log('Done!'))
107
+ * .dispatch()
108
+ * ```
109
+ */
110
+ export declare class Batch {
111
+ static create(jobs: Array<Job | BatchableJob>): PendingBatch;
112
+ static find(id: string): Promise<DispatchedBatch | null>;
113
+ static all(): Promise<DispatchedBatch[]>;
114
+ static prune(olderThanHours?: number): Promise<number>;
115
+ }
@@ -0,0 +1,79 @@
1
+ import type { JobOptions } from '@stacksjs/types';
2
+ /**
3
+ * Discover jobs from the app/Jobs directory
4
+ */
5
+ export declare function discoverJobs(jobsPath?: string): Promise<DiscoveredJob[]>;
6
+ /**
7
+ * Get a job by name from the registry
8
+ */
9
+ export declare function getJob(name: string): DiscoveredJob | undefined;
10
+ /**
11
+ * Get all discovered jobs
12
+ */
13
+ export declare function getAllJobs(): DiscoveredJob[];
14
+ /**
15
+ * Get scheduled jobs
16
+ */
17
+ export declare function getScheduledJobs(): DiscoveredJob[];
18
+ /**
19
+ * Execute a job by name
20
+ */
21
+ export declare function executeJob<T = any>(name: string, payload?: any): Promise<T>;
22
+ /**
23
+ * Convert discovered job config to bun-queue JobOptions
24
+ */
25
+ export declare function toJobOptions(job: DiscoveredJob): JobOptions;
26
+ // Global job registry
27
+ export declare const jobRegistry: JobRegistry;
28
+ /**
29
+ * Discovered job metadata
30
+ */
31
+ export declare interface DiscoveredJob {
32
+ name: string
33
+ path: string
34
+ config: JobConfig
35
+ type: 'class' | 'function'
36
+ module: any
37
+ }
38
+ /**
39
+ * Job configuration from discovery
40
+ */
41
+ export declare interface JobConfig {
42
+ name?: string
43
+ description?: string
44
+ queue?: string
45
+ tries?: number
46
+ backoff?: number | number[]
47
+ rate?: string
48
+ timeout?: number
49
+ withoutOverlapping?: boolean
50
+ retries?: number
51
+ retryAfter?: number[]
52
+ schedule?: string
53
+ backoffConfig?: {
54
+ strategy?: 'fixed' | 'exponential' | 'linear'
55
+ initialDelay?: number
56
+ factor?: number
57
+ maxDelay?: number
58
+ jitter?: {
59
+ enabled?: boolean
60
+ factor?: number
61
+ minDelay?: number
62
+ maxDelay?: number
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Job registry for managing discovered jobs
68
+ */
69
+ declare class JobRegistry {
70
+ register(job: DiscoveredJob): void;
71
+ get(name: string): DiscoveredJob | undefined;
72
+ all(): DiscoveredJob[];
73
+ byQueue(queue: string): DiscoveredJob[];
74
+ scheduled(): DiscoveredJob[];
75
+ has(name: string): boolean;
76
+ clear(): void;
77
+ setInitialized(value: boolean): void;
78
+ isInitialized(): boolean;
79
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Get the global queue events instance
3
+ */
4
+ export declare function getQueueEvents(): QueueEvents;
5
+ /**
6
+ * Subscribe to a queue event
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * // Listen for job completions
11
+ * onQueueEvent('job:completed', (payload) => {
12
+ * console.log(`Job ${payload.jobId} completed!`)
13
+ * })
14
+ *
15
+ * // Listen for all events
16
+ * onQueueEvent('*', (event, payload) => {
17
+ * console.log(`Event: ${event}`, payload)
18
+ * })
19
+ * ```
20
+ */
21
+ export declare function onQueueEvent(event: QueueEventType | '*', handler: QueueEventHandler | ((_event: QueueEventType, _payload: QueueEventPayload) => void | Promise<void>)): () => void;
22
+ /**
23
+ * Emit a queue event
24
+ */
25
+ export declare function emitQueueEvent(event: QueueEventType, payload: Omit<QueueEventPayload, 'timestamp'>): Promise<void>;
26
+ /**
27
+ * Event-aware job wrapper
28
+ *
29
+ * Wraps a job handler to automatically emit events
30
+ */
31
+ export declare function withEvents<T extends (...args: any[]) => Promise<any>>(queueName: string, handler: T): T;
32
+ /**
33
+ * Queue event listener decorator
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * class EmailNotifications {
38
+ * @OnQueueEvent('job:completed')
39
+ * async onJobCompleted(payload: QueueEventPayload) {
40
+ * console.log('Job completed:', payload)
41
+ * }
42
+ * }
43
+ * ```
44
+ */
45
+ export declare function OnQueueEvent(event: QueueEventType): void;
46
+ /**
47
+ * Get or create the global QueueMetrics instance
48
+ */
49
+ export declare function getGlobalMetrics(): QueueMetrics;
50
+ export declare function getWorkerTracker(): WorkerTracker;
51
+ /**
52
+ * Queue event payload interface
53
+ */
54
+ export declare interface QueueEventPayload {
55
+ jobId?: string
56
+ queueName?: string
57
+ jobName?: string
58
+ data?: any
59
+ result?: any
60
+ error?: Error
61
+ progress?: number
62
+ timestamp: number
63
+ attemptsMade?: number
64
+ duration?: number
65
+ }
66
+ /**
67
+ * Worker status tracker
68
+ *
69
+ * Tracks registered workers and their activity for health checks.
70
+ */
71
+ export declare interface TrackedWorker {
72
+ id: string
73
+ status: 'active' | 'idle' | 'stopped'
74
+ queue: string
75
+ processedCount: number
76
+ failedCount: number
77
+ lastActivityAt: string
78
+ startedAt: string
79
+ }
80
+ /**
81
+ * Queue event types
82
+ */
83
+ export type QueueEventType = | 'job:added'
84
+ | 'job:processing'
85
+ | 'job:completed'
86
+ | 'job:failed'
87
+ | 'job:retrying'
88
+ | 'job:stalled'
89
+ | 'job:progress'
90
+ | 'queue:paused'
91
+ | 'queue:resumed'
92
+ | 'queue:error'
93
+ | 'worker:started'
94
+ | 'worker:stopped'
95
+ | 'batch:added'
96
+ | 'batch:completed'
97
+ | 'batch:failed';
98
+ /**
99
+ * Queue event handler type
100
+ */
101
+ export type QueueEventHandler = (_payload: QueueEventPayload) => void | Promise<void>;
102
+ /**
103
+ * Queue events emitter
104
+ */
105
+ export declare class QueueEvents {
106
+ on(event: QueueEventType, handler: QueueEventHandler): () => void;
107
+ onAny(handler: (event: QueueEventType, payload: QueueEventPayload) => void | Promise<void>): () => void;
108
+ once(event: QueueEventType, handler: QueueEventHandler): () => void;
109
+ emit(event: QueueEventType, payload: Omit<QueueEventPayload, 'timestamp'>): Promise<void>;
110
+ off(event: QueueEventType): void;
111
+ removeAllListeners(): void;
112
+ }
113
+ /**
114
+ * Queue metrics based on events
115
+ */
116
+ export declare class QueueMetrics {
117
+ constructor();
118
+ getThroughputPerMinute(): number;
119
+ getAverageProcessingTime(): number;
120
+ getMetrics(): {
121
+ counts: { added: number; completed: number; failed: number; processing: number }
122
+ averageDuration: number
123
+ recentErrors: Array<{ error: Error; timestamp: number }>
124
+ throughputPerMinute: number
125
+ };
126
+ reset(): void;
127
+ stop(): void;
128
+ }
129
+ declare class WorkerTracker {
130
+ register(id: string, queue: string): void;
131
+ markActive(id: string): void;
132
+ markIdle(id: string): void;
133
+ recordCompletion(id: string): void;
134
+ recordFailure(id: string): void;
135
+ unregister(id: string): void;
136
+ getAll(): TrackedWorker[];
137
+ clear(): void;
138
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Perform queue health check
3
+ */
4
+ export declare function checkQueueHealth(config?: HealthCheckConfig): Promise<QueueHealthResult>;
5
+ /**
6
+ * Create a health check HTTP handler
7
+ */
8
+ export declare function createHealthCheckHandler(config?: HealthCheckConfig): (req: Request) => Promise<Response>;
9
+ /**
10
+ * Quick health check - returns just the status
11
+ */
12
+ export declare function isQueueHealthy(config?: HealthCheckConfig): Promise<boolean>;
13
+ /**
14
+ * Queue health check result
15
+ */
16
+ export declare interface QueueHealthResult {
17
+ status: HealthStatus
18
+ timestamp: string
19
+ queues: QueueStatus[]
20
+ workers: WorkerStatus[]
21
+ metrics: QueueMetrics
22
+ alerts: HealthAlert[]
23
+ }
24
+ /**
25
+ * Individual queue status
26
+ */
27
+ export declare interface QueueStatus {
28
+ name: string
29
+ status: HealthStatus
30
+ pending: number
31
+ processing: number
32
+ delayed: number
33
+ failed: number
34
+ oldestJobAge?: number
35
+ }
36
+ /**
37
+ * Worker status
38
+ */
39
+ export declare interface WorkerStatus {
40
+ id: string
41
+ status: 'active' | 'idle' | 'stopped'
42
+ queue: string
43
+ processedCount: number
44
+ failedCount: number
45
+ lastActivityAt?: string
46
+ }
47
+ /**
48
+ * Queue metrics summary
49
+ */
50
+ export declare interface QueueMetrics {
51
+ totalPending: number
52
+ totalProcessing: number
53
+ totalDelayed: number
54
+ totalFailed: number
55
+ throughputPerMinute: number
56
+ averageProcessingTime: number
57
+ errorRate: number
58
+ }
59
+ /**
60
+ * Health alert
61
+ */
62
+ export declare interface HealthAlert {
63
+ level: 'warning' | 'critical'
64
+ message: string
65
+ queue?: string
66
+ timestamp: string
67
+ }
68
+ /**
69
+ * Health check configuration
70
+ */
71
+ export declare interface HealthCheckConfig {
72
+ maxPendingWarning?: number
73
+ maxPendingCritical?: number
74
+ maxFailedWarning?: number
75
+ maxFailedCritical?: number
76
+ maxJobAgeWarning?: number
77
+ maxJobAgeCritical?: number
78
+ maxErrorRateWarning?: number
79
+ maxErrorRateCritical?: number
80
+ queues?: string[]
81
+ }
82
+ /**
83
+ * Health status
84
+ */
85
+ export type HealthStatus = 'healthy' | 'degraded' | 'unhealthy';
@@ -0,0 +1,137 @@
1
+ // =============================================================================
2
+ // Redis queue driver
3
+ // =============================================================================
4
+ // Redis driver is lazily loaded to avoid requiring bun-queue when not using Redis.
5
+ // Use: const { RedisQueue } = await import('@stacksjs/queue/drivers/redis')
6
+ // Or access via the queue manager which dynamically imports the driver.
7
+ export declare function getRedisQueue(): Promise<void>;
8
+ /**
9
+ * @stacksjs/queue
10
+ *
11
+ * A thin wrapper around bun-queue that integrates with Stacks conventions.
12
+ * Core Stacks queue exports are always available. For advanced bun-queue
13
+ * features (Queue, Worker, dispatch, etc.), import from '@stacksjs/queue/bun-queue'.
14
+ */
15
+ // =============================================================================
16
+ // Stacks Job class for file-based jobs (app/Jobs/*.ts)
17
+ // =============================================================================
18
+ export { Job } from './action';
19
+ // =============================================================================
20
+ // Stacks job helper for dispatching file-based jobs
21
+ // =============================================================================
22
+ export { Jobs, job, jobBatch, runJob } from './job';
23
+ // =============================================================================
24
+ // Per-job progress + cancellation (cache-backed; safe across processes)
25
+ // =============================================================================
26
+ export { setJobProgress, getJobProgress, cancelJob, isJobCancelled, clearJobState } from './job-progress';
27
+ // =============================================================================
28
+ // Job discovery (for app/Jobs directory)
29
+ // =============================================================================
30
+ export {
31
+ discoverJobs,
32
+ executeJob,
33
+ getAllJobs,
34
+ getJob,
35
+ getScheduledJobs,
36
+ jobRegistry,
37
+ toJobOptions,
38
+ type DiscoveredJob,
39
+ type JobConfig,
40
+ } from './discovery';
41
+ // =============================================================================
42
+ // Stacks scheduler (integrates with job discovery)
43
+ // =============================================================================
44
+ export {
45
+ getRegisteredJobs,
46
+ getSchedulerStatus,
47
+ isSchedulerRunning,
48
+ startScheduler,
49
+ stopScheduler,
50
+ triggerJob,
51
+ } from './scheduler';
52
+ // =============================================================================
53
+ // Stacks queue events (integrates with Stacks logging)
54
+ // =============================================================================
55
+ export {
56
+ emitQueueEvent,
57
+ getGlobalMetrics,
58
+ getQueueEvents,
59
+ getWorkerTracker,
60
+ onQueueEvent,
61
+ OnQueueEvent,
62
+ QueueEvents,
63
+ QueueMetrics,
64
+ withEvents,
65
+ type QueueEventHandler,
66
+ type QueueEventPayload,
67
+ type QueueEventType,
68
+ type TrackedWorker,
69
+ } from './events';
70
+ // =============================================================================
71
+ // Health checks
72
+ // =============================================================================
73
+ export {
74
+ checkQueueHealth,
75
+ createHealthCheckHandler,
76
+ isQueueHealthy,
77
+ type HealthAlert,
78
+ type HealthCheckConfig,
79
+ type HealthStatus,
80
+ type QueueHealthResult,
81
+ type QueueMetrics as HealthQueueMetrics,
82
+ type QueueStatus,
83
+ type WorkerStatus,
84
+ } from './health';
85
+ // =============================================================================
86
+ // Failed job notifications
87
+ // =============================================================================
88
+ export {
89
+ configureFailedJobNotifications,
90
+ FailedJobNotifier,
91
+ getFailedJobNotifier,
92
+ notifyJobFailed,
93
+ type FailedJobInfo,
94
+ type FailedJobNotificationConfig,
95
+ type NotificationChannel,
96
+ } from './notifications';
97
+ // =============================================================================
98
+ // Testing utilities
99
+ // =============================================================================
100
+ export {
101
+ createQueueTester,
102
+ expectJobToFail,
103
+ fake,
104
+ getFakeQueue,
105
+ isFaked,
106
+ QueueTester,
107
+ restore,
108
+ runJob as runTestJob,
109
+ type DispatchedJob,
110
+ } from './testing';
111
+ // =============================================================================
112
+ // Job batches
113
+ // =============================================================================
114
+ export {
115
+ Batch,
116
+ DispatchedBatch,
117
+ PendingBatch,
118
+ getBatchCallbacks,
119
+ isBatchCancelled,
120
+ recordBatchJobCompletion,
121
+ recordBatchJobFailure,
122
+ type BatchableJob,
123
+ type BatchOptions,
124
+ type BatchRecord,
125
+ type BatchStatus,
126
+ } from './batch';
127
+ // =============================================================================
128
+ // Worker functions (for queue:work command)
129
+ // =============================================================================
130
+ export {
131
+ executeFailedJobs,
132
+ getActiveJobCount,
133
+ isWorkerRunning,
134
+ retryFailedJob,
135
+ startProcessor,
136
+ stopProcessor,
137
+ } from './worker';
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Update the progress of the running job. Workers call this from
3
+ * inside their handler; the UI polls `getJobProgress(id)` to render
4
+ * a progress bar.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * await job.progress(0.5, 'Halfway there')
9
+ * // … later
10
+ * await job.progress(1.0, 'Done')
11
+ * ```
12
+ */
13
+ export declare function setJobProgress(jobId: string, percent: number, message?: string): Promise<void>;
14
+ /**
15
+ * Read the current progress of a job, or `null` if no progress has
16
+ * been recorded yet (job hasn't started, or the entry expired).
17
+ */
18
+ export declare function getJobProgress(jobId: string): Promise<JobProgress | null>;
19
+ /**
20
+ * Mark a job as cancelled. The next time the worker calls
21
+ * `isJobCancelled(id)` it will see the flag and can stop cleanly. The
22
+ * worker is responsible for checking — the framework can't preempt a
23
+ * running handler safely.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * // From an admin endpoint:
28
+ * await cancelJob(req.params.id)
29
+ *
30
+ * // Inside the worker:
31
+ * if (await isJobCancelled(job.id)) return
32
+ * ```
33
+ */
34
+ export declare function cancelJob(jobId: string): Promise<void>;
35
+ /**
36
+ * Check whether the named job has been cancelled. Workers should call
37
+ * this between unit-of-work iterations so cancellations are honored
38
+ * promptly without preempting in-flight work.
39
+ */
40
+ export declare function isJobCancelled(jobId: string): Promise<boolean>;
41
+ /**
42
+ * Clear progress + cancellation state for a finished job. Workers
43
+ * should call this on completion (success or failure) so admin UIs
44
+ * showing "in-flight jobs" don't keep finished entries forever.
45
+ *
46
+ * No-op if no entries exist for the id — safe to call always.
47
+ */
48
+ export declare function clearJobState(jobId: string): Promise<void>;
49
+ declare interface JobProgress {
50
+ percent: number
51
+ message?: string
52
+ updatedAt: number
53
+ }