@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,116 @@
1
+ /**
2
+ * Create a job builder for dispatching file-based jobs
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * // Basic dispatch
7
+ * await job('SendWelcomeEmail', { userId: 1 }).dispatch()
8
+ *
9
+ * // With options
10
+ * await job('ProcessOrder', { orderId: 123 })
11
+ * .onQueue('orders')
12
+ * .tries(3)
13
+ * .backoff([10, 30, 60])
14
+ * .dispatch()
15
+ *
16
+ * // Immediate execution
17
+ * await job('SendNotification', { message: 'Hello' }).dispatchNow()
18
+ * ```
19
+ */
20
+ export declare function job(name: string, payload?: any): JobBuilder;
21
+ /**
22
+ * Create a job batch for dispatching multiple jobs together
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import SendWelcomeEmail from '~/app/Jobs/SendWelcomeEmail'
27
+ * import ProcessOrder from '~/app/Jobs/ProcessOrder'
28
+ *
29
+ * const batch = await jobBatch([
30
+ * { job: SendWelcomeEmail, payload: { email: 'user@example.com' } },
31
+ * { job: ProcessOrder, payload: { orderId: 123 } },
32
+ * ])
33
+ * .name('Onboard User')
34
+ * .allowFailures()
35
+ * .then(async (_b) => console.log('All done!'))
36
+ * .dispatch()
37
+ * ```
38
+ */
39
+ export declare function jobBatch(jobs: Array<import('./action').Job | import('./batch').BatchableJob>): import('./batch').PendingBatch;
40
+ /**
41
+ * Run a job immediately by name
42
+ *
43
+ * This loads the job from app/Jobs/{name}.ts and executes it
44
+ *
45
+ * Wraps execution in `withTraceId(...)` so log lines, db queries, and
46
+ * downstream HTTP calls emitted from inside the job carry the same
47
+ * trace id as the request (or a fresh one for cron-triggered runs).
48
+ * `options.traceId` lets dispatchers attach a parent id explicitly;
49
+ * absent that, we mint one of the form `job:<name>:<random>` so
50
+ * background work is at least correlatable to itself.
51
+ */
52
+ export declare function runJob(name: string, options?: { payload?: any; context?: any; traceId?: string }): Promise<void>;
53
+ /*.ts`; this `Jobs` facade is the *call-site* form for
54
+ * dispatching by name from anywhere in the app. It lets one-liner
55
+ * dispatches skip the builder while still allowing the chainable form
56
+ * via `Jobs.make(...)` for option-heavy cases.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // One-shot dispatch (no options)
61
+ * await Jobs.dispatch('SendWelcomeEmail', { userId: 1 })
62
+ *
63
+ * // Conditional dispatch
64
+ * await Jobs.dispatchIf(user.isNew, 'SendWelcomeEmail', { userId: user.id })
65
+ *
66
+ * // With options — fall back to the builder
67
+ * await Jobs.make('ProcessOrder', { orderId: 123 })
68
+ * .onQueue('orders')
69
+ * .tries(3)
70
+ * .dispatch()
71
+ *
72
+ * // Immediate (synchronous) execution, ignoring the queue
73
+ * await Jobs.dispatchNow('SendNotification', { message: 'Hello' })
74
+ * ```
75
+ */
76
+ export declare const Jobs: {
77
+ /** Construct a builder without dispatching — for chained option calls. */
78
+ make: (name: string, payload?: any) => JobBuilder;
79
+ /** Dispatch a job in one call. Equivalent to `job(name, payload).dispatch()`. */
80
+ async dispatch: (name: string, payload?: any) => Promise<void>;
81
+ /** Dispatch only if `condition` is truthy. */
82
+ async dispatchIf: (condition: boolean, name: string, payload?: any) => Promise<void>;
83
+ /** Dispatch unless `condition` is truthy. */
84
+ async dispatchUnless: (condition: boolean, name: string, payload?: any) => Promise<void>;
85
+ /** Run the job synchronously, bypassing the queue. */
86
+ async dispatchNow: (name: string, payload?: any) => Promise<void>;
87
+ /** Schedule the job to run after `seconds` of delay. */
88
+ dispatchAfter: (seconds: number, name: string, payload?: any) => JobBuilder
89
+ };
90
+ /**
91
+ * Options for job dispatch
92
+ */
93
+ export declare interface JobDispatchOptions {
94
+ queue?: string
95
+ delay?: number
96
+ tries?: number
97
+ timeout?: number
98
+ backoff?: number[]
99
+ context?: any
100
+ }
101
+ /**
102
+ * Fluent job builder for file-based jobs
103
+ */
104
+ declare class JobBuilder {
105
+ constructor(name: string, payload?: any);
106
+ onQueue(queue: string): this;
107
+ delay(seconds: number): this;
108
+ tries(count: number): this;
109
+ timeout(seconds: number): this;
110
+ backoff(delays: number[]): this;
111
+ withContext(context: any): this;
112
+ dispatch(): Promise<void>;
113
+ dispatchIf(condition: boolean): Promise<void>;
114
+ dispatchUnless(condition: boolean): Promise<void>;
115
+ dispatchNow(): Promise<void>;
116
+ }
@@ -0,0 +1,70 @@
1
+ import { log } from '@stacksjs/logging';
2
+ /**
3
+ * Configure failed job notifications
4
+ */
5
+ export declare function configureFailedJobNotifications(config: FailedJobNotificationConfig): FailedJobNotifier;
6
+ /**
7
+ * Get the global notifier
8
+ */
9
+ export declare function getFailedJobNotifier(): FailedJobNotifier | null;
10
+ /**
11
+ * Notify about a failed job using the global notifier
12
+ */
13
+ export declare function notifyJobFailed(job: FailedJobInfo): Promise<void>;
14
+ /**
15
+ * Failed job info
16
+ */
17
+ export declare interface FailedJobInfo {
18
+ id: string | number
19
+ name: string
20
+ queue: string
21
+ payload: any
22
+ exception: string
23
+ failedAt: Date
24
+ attempts: number
25
+ maxAttempts: number
26
+ }
27
+ /**
28
+ * Notification configuration
29
+ */
30
+ export declare interface FailedJobNotificationConfig {
31
+ channels: NotificationChannel[]
32
+ email?: {
33
+ to: string | string[]
34
+ from?: string
35
+ subject?: string
36
+ }
37
+ slack?: {
38
+ webhookUrl: string
39
+ channel?: string
40
+ username?: string
41
+ iconEmoji?: string
42
+ }
43
+ discord?: {
44
+ webhookUrl: string
45
+ username?: string
46
+ avatarUrl?: string
47
+ }
48
+ webhook?: {
49
+ url: string
50
+ headers?: Record<string, string>
51
+ secret?: string
52
+ }
53
+ rateLimit?: number
54
+ batch?: boolean
55
+ batchInterval?: number
56
+ filter?: (job: FailedJobInfo) => boolean
57
+ }
58
+ /**
59
+ * Notification channel types
60
+ */
61
+ export type NotificationChannel = 'email' | 'slack' | 'discord' | 'webhook' | 'log';
62
+ /**
63
+ * Failed job notification manager
64
+ */
65
+ export declare class FailedJobNotifier {
66
+ constructor(config: FailedJobNotificationConfig);
67
+ notify(job: FailedJobInfo): Promise<void>;
68
+ shutdown(): Promise<void>;
69
+ cleanup(): Promise<void>;
70
+ }
@@ -0,0 +1,52 @@
1
+ import { type DiscoveredJob } from './discovery';
2
+ /**
3
+ * Start the scheduler
4
+ */
5
+ export declare function startScheduler(config?: Partial<SchedulerConfig>): Promise<void>;
6
+ /**
7
+ * Stop the scheduler
8
+ */
9
+ export declare function stopScheduler(): Promise<void>;
10
+ /**
11
+ * Get scheduler status
12
+ */
13
+ export declare function getSchedulerStatus(): {
14
+ isRunning: boolean
15
+ jobCount: number
16
+ jobs: Array<{
17
+ name: string
18
+ schedule: string | undefined
19
+ lastRun: Date | null
20
+ nextRun: Date | null
21
+ isRunning: boolean
22
+ }>
23
+ };
24
+ /**
25
+ * Check if scheduler is running
26
+ */
27
+ export declare function isSchedulerRunning(): boolean;
28
+ /**
29
+ * Get registered scheduled jobs
30
+ */
31
+ export declare function getRegisteredJobs(): Map<string, ScheduledJobState>;
32
+ /**
33
+ * Manually trigger a scheduled job
34
+ */
35
+ export declare function triggerJob(name: string): Promise<void>;
36
+ /**
37
+ * Scheduler configuration
38
+ */
39
+ declare interface SchedulerConfig {
40
+ checkInterval: number
41
+ timezone?: string
42
+ preventOverlapping: boolean
43
+ }
44
+ /**
45
+ * Scheduled job state
46
+ */
47
+ declare interface ScheduledJobState {
48
+ job: DiscoveredJob
49
+ lastRun: Date | null
50
+ nextRun: Date | null
51
+ isRunning: boolean
52
+ }
@@ -0,0 +1,74 @@
1
+ import type { JobOptions } from '@stacksjs/types';
2
+ /**
3
+ * Enable fake queue for testing
4
+ */
5
+ export declare function fake(): FakeQueue;
6
+ /**
7
+ * Get the fake queue instance
8
+ */
9
+ export declare function getFakeQueue(): FakeQueue | null;
10
+ /**
11
+ * Check if queue is faked
12
+ */
13
+ export declare function isFaked(): boolean;
14
+ /**
15
+ * Restore real queue behavior
16
+ */
17
+ export declare function restore(): void;
18
+ /**
19
+ * Create a queue tester instance
20
+ */
21
+ export declare function createQueueTester(): QueueTester;
22
+ /**
23
+ * Test helper to run a job synchronously
24
+ */
25
+ export declare function runJob<T, R>(jobModule: { handle: (data: T) => Promise<R> | R }, data: T): Promise<R>;
26
+ /**
27
+ * Test helper to expect a job to fail
28
+ */
29
+ export declare function expectJobToFail<T>(jobModule: { handle: (data: T) => Promise<any> }, data: T, expectedError?: string | RegExp): Promise<Error>;
30
+ /**
31
+ * Dispatched job record for assertions
32
+ */
33
+ export declare interface DispatchedJob<T = any> {
34
+ name: string
35
+ data: T
36
+ options: Partial<JobOptions>
37
+ dispatchedAt: Date
38
+ queue: string
39
+ }
40
+ /**
41
+ * Fake queue for testing
42
+ */
43
+ declare class FakeQueue {
44
+ dispatch<T>(name: string, data: T, options?: Partial<JobOptions>): void;
45
+ push<T>(name: string, data: T, options?: Partial<JobOptions>): void;
46
+ dispatched(name?: string): DispatchedJob[];
47
+ pushed(name?: string): DispatchedJob[];
48
+ assertDispatched(name: string, callback?: (job: DispatchedJob) => boolean): void;
49
+ assertNotDispatched(name: string): void;
50
+ assertDispatchedTimes(name: string, times: number): void;
51
+ assertNothingDispatched(): void;
52
+ assertPushed(name: string, callback?: (job: DispatchedJob) => boolean): void;
53
+ assertPushedWithDelay(name: string, delay: number): void;
54
+ assertPushedOn(queue: string, name: string): void;
55
+ processJob<T>(name: string, handler: (data: T) => Promise<any>): Promise<void>;
56
+ processed(name?: string): DispatchedJob[];
57
+ failed(name?: string): Array<{ job: DispatchedJob, error: Error }>;
58
+ reset(): void;
59
+ }
60
+ /**
61
+ * Queue testing helper class
62
+ */
63
+ export declare class QueueTester {
64
+ constructor();
65
+ dispatch<T>(name: string, data: T, options?: Partial<JobOptions>): this;
66
+ push<T>(name: string, data: T, options?: Partial<JobOptions>): this;
67
+ assertDispatched(name: string, callback?: (job: DispatchedJob) => boolean): this;
68
+ assertNotDispatched(name: string): this;
69
+ assertDispatchedTimes(name: string, times: number): this;
70
+ assertNothingDispatched(): this;
71
+ dispatched(name?: string): DispatchedJob[];
72
+ reset(): this;
73
+ cleanup(): void;
74
+ }
@@ -0,0 +1,26 @@
1
+ import type { Result } from '@stacksjs/error-handling';
2
+ /**
3
+ * Start the queue processor (worker)
4
+ * This is the main function called by `buddy queue:work`
5
+ */
6
+ export declare function startProcessor(queueName?: string, options?: { concurrency?: number }): Promise<Result<undefined, Error>>;
7
+ /**
8
+ * Stop the queue processor
9
+ */
10
+ export declare function stopProcessor(): Promise<void>;
11
+ /**
12
+ * Retry all failed jobs
13
+ */
14
+ export declare function executeFailedJobs(): Promise<void>;
15
+ /**
16
+ * Retry a specific failed job
17
+ */
18
+ export declare function retryFailedJob(id: number): Promise<void>;
19
+ /**
20
+ * Get the count of currently active (processing) jobs
21
+ */
22
+ export declare function getActiveJobCount(): number;
23
+ /**
24
+ * Check if the worker is currently running
25
+ */
26
+ export declare function isWorkerRunning(): boolean;
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@stacksjs/queue",
3
3
  "type": "module",
4
- "version": "0.70.23",
5
- "description": "The Stacks Queue system.",
4
+ "version": "0.70.25",
5
+ "description": "The Stacks Queue system powered by bun-queue.",
6
6
  "author": "Chris Breuer",
7
- "contributors": ["Chris Breuer <chris@stacksjs.org>"],
7
+ "contributors": [
8
+ "Chris Breuer <chris@stacksjs.com>"
9
+ ],
8
10
  "license": "MIT",
9
11
  "funding": "https://github.com/sponsors/chrisbbreuer",
10
12
  "homepage": "https://github.com/stacksjs/stacks/tree/main/storage/framework/core/queue#readme",
@@ -18,9 +20,10 @@
18
20
  },
19
21
  "keywords": [
20
22
  "queue",
21
- "bee-queue",
23
+ "bun-queue",
22
24
  "redis",
23
- "upstash",
25
+ "job",
26
+ "worker",
24
27
  "stacks",
25
28
  "framework",
26
29
  "typescript",
@@ -28,21 +31,30 @@
28
31
  ],
29
32
  "exports": {
30
33
  ".": {
34
+ "bun": "./src/index.ts",
35
+ "types": "./dist/index.d.ts",
31
36
  "import": "./dist/index.js"
32
37
  },
33
38
  "./*": {
39
+ "bun": "./src/*",
34
40
  "import": "./dist/*"
35
41
  }
36
42
  },
37
43
  "module": "dist/index.js",
38
44
  "types": "dist/index.d.ts",
39
- "files": ["README.md", "dist"],
45
+ "files": [
46
+ "README.md",
47
+ "dist"
48
+ ],
40
49
  "scripts": {
41
50
  "build": "bun build.ts",
42
51
  "typecheck": "bun tsc --noEmit",
43
52
  "prepublishOnly": "bun run build"
44
53
  },
54
+ "dependencies": {
55
+ "@stacksjs/bun-queue": "^0.1.0"
56
+ },
45
57
  "devDependencies": {
46
- "@stacksjs/development": "0.70.22"
58
+ "better-dx": "^0.2.12"
47
59
  }
48
60
  }
package/dist/action.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import type { JobOptions } from '@stacksjs/types';
2
-
3
- export declare class Job {
4
- name: JobOptions['name']
5
- description: JobOptions['description']
6
- action?: JobOptions['action']
7
- handle?: JobOptions['handle']
8
- rate: JobOptions['rate']
9
- tries: JobOptions['tries']
10
- backoff: JobOptions['backoff']
11
- backoffConfig: JobOptions['backoffConfig']
12
- enabled: JobOptions['enabled']
13
-
14
- constructor({ name, description, handle, rate, tries, backoff, backoffConfig, action, enabled }: JobOptions) {
15
- this.name = name
16
- this.description = description
17
- this.handle = handle
18
- this.rate = rate
19
- this.action = action
20
- this.tries = tries
21
- this.backoff = backoff
22
- this.backoffConfig = backoffConfig
23
- this.enabled = enabled
24
- }
25
- }
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from './action'
2
- export * from './job'
3
- export * from './process'
package/dist/job.d.ts DELETED
@@ -1,199 +0,0 @@
1
- import type { QueueOption } from '@stacksjs/types';
2
-
3
- declare const queueDriver: 'database';
4
- declare interface JobConfig {
5
- handle?: (payload?: any) => Promise<void>
6
- action?: string | (() => Promise<void>)
7
- }
8
- declare interface Dispatchable {
9
- dispatch: () => Promise<void>
10
- dispatchNow: () => Promise<void>
11
- delay: (seconds: number) => this
12
- afterResponse: () => this
13
- chain: (jobs: Dispatchable[]) => this
14
- onQueue: (queue: string) => this
15
- }
16
- export declare function runJob(name: string, options: QueueOption): Promise<void>;
17
- export declare class Queue implements Dispatchable {
18
- protected options: QueueOption = {}
19
-
20
- constructor(
21
- protected name: string,
22
- protected payload?: any,
23
- ) { }
24
-
25
- async dispatch(): Promise<void> {
26
- const queueName = this.options.queue || 'default'
27
-
28
- const jobPayload = this.createJobPayload(queueName)
29
-
30
- if (this.isQueuedDriver()) {
31
- await this.storeQueuedJob(jobPayload)
32
- return
33
- }
34
-
35
- if (this.options.afterResponse) {
36
- this.deferAfterResponse()
37
- return
38
- }
39
-
40
- if (this.options.delay) {
41
- this.deferWithDelay()
42
- return
43
- }
44
-
45
- await this.runJobImmediately(jobPayload)
46
- }
47
-
48
- private isQueuedDriver(): boolean {
49
- return ['database', 'redis'].includes(queueDriver)
50
- }
51
-
52
- private createJobPayload(queueName: string): QueueOption {
53
- return {
54
- queue: queueName,
55
- payload: this.payload,
56
- context: this.options.context,
57
- maxTries: this.options.maxTries,
58
- timeout: this.options.timeout,
59
- backoff: this.options.backoff,
60
- delay: this.options.delay,
61
- }
62
- }
63
-
64
- private async storeQueuedJob(jobPayload: any): Promise<void> {
65
- await storeJob(this.name, jobPayload)
66
- }
67
-
68
- private deferAfterResponse(): void {
69
- process.on('beforeExit', async () => {
70
- await this.dispatchNow()
71
- })
72
- }
73
-
74
- private deferWithDelay(): void {
75
- setTimeout(async () => {
76
- await this.dispatchNow()
77
- }, this.options.delay || 0)
78
- }
79
-
80
- private async runJobImmediately(jobPayload: any): Promise<void> {
81
- try {
82
- await runJob(this.name, jobPayload)
83
- await this.runChainedJobs()
84
- }
85
- catch (error) {
86
- log.error(`Failed to dispatch job ${this.name}:`, error)
87
- throw error
88
- }
89
- }
90
-
91
- private async runChainedJobs(): Promise<void> {
92
- if (!this.options.chainedJobs?.length)
93
- return
94
-
95
- for (const job of this.options.chainedJobs) {
96
- await job.dispatch()
97
- }
98
- }
99
-
100
- async dispatchNow(): Promise<void> {
101
- try {
102
- await runJob(this.name, {
103
- payload: this.payload,
104
- context: this.options.context,
105
- immediate: true,
106
- })
107
-
108
- if (this.options.chainedJobs?.length) {
109
- for (const job of this.options.chainedJobs) {
110
- await job.dispatchNow()
111
- }
112
- }
113
- }
114
- catch (error) {
115
- log.error(`Failed to execute job ${this.name}:`, error)
116
- throw error
117
- }
118
- }
119
-
120
- delay(seconds: number): this {
121
- this.options.delay = seconds
122
- return this
123
- }
124
-
125
- onQueue(queue: string): this {
126
- this.options.queue = queue
127
- return this
128
- }
129
-
130
- chain(jobs: Dispatchable[]): this {
131
- this.options.chainedJobs = jobs
132
- return this
133
- }
134
-
135
- afterResponse(): this {
136
- this.options.afterResponse = true
137
- return this
138
- }
139
-
140
- tries(count: number): this {
141
- this.options.maxTries = count
142
- return this
143
- }
144
-
145
- timeout(seconds: number): this {
146
- this.options.timeout = seconds
147
- return this
148
- }
149
-
150
- backoff(attempts: number[]): this {
151
- this.options.backoff = attempts
152
- return this
153
- }
154
-
155
- withContext(context: any): this {
156
- this.options.context = context
157
- return this
158
- }
159
- }
160
- export declare class JobFactory {
161
- static make(name: string, payload?: any): Queue {
162
- return new Queue(name, payload)
163
- }
164
-
165
- static dispatch(name: string, payload?: any): Queue {
166
- const job = new Queue(name, payload)
167
- job.dispatch()
168
- return job
169
- }
170
-
171
- static async dispatchNow(name: string, payload?: any): Promise<void> {
172
- await new Queue(name, payload).dispatchNow()
173
- }
174
-
175
- static later(delay: number, name: string, payload?: any): Queue {
176
- return new Queue(name, payload).delay(delay)
177
- }
178
-
179
- static chain(jobs: Queue[]): Queue {
180
- const firstJob = jobs[0]
181
- return firstJob.chain(jobs.slice(1))
182
- }
183
- }
184
- export declare function job(name: string, payload?: any): Queue;
185
- export declare function processOrder(payload, context): void;
186
-
187
- export default {
188
- async handle() {
189
- }
190
- };
191
- export default {
192
- action: 'SendWelcomeEmail'
193
- };
194
- export default {
195
- action: async () => {
196
- }
197
- };
198
- export default async function(payload, context) {
199
- };
package/dist/process.d.ts DELETED
@@ -1,24 +0,0 @@
1
- import type { JitterConfig, JobOptions } from '@stacksjs/types';
2
- import type { JobModel } from '../../../orm/src/models/Job';
3
-
4
- declare interface QueuePayload {
5
- path: string
6
- name: string
7
- maxTries: number
8
- timeOut: number | null
9
- timeOutAt: Date | null
10
- params: any
11
- classPayload: string
12
- }
13
- export declare function processJobs(queue: string | undefined): Promise<Ok<string, never>>;
14
- export declare function executeFailedJobs(): Promise<void>;
15
- export declare function retryFailedJob(id: number): Promise<void>;
16
- declare function executeJobs(queue: string | undefined): Promise<void>;
17
- declare function enforceMaxDelay(maxDelay: number | undefined, delay: number): number;
18
- declare function addDelay(timestamp: number | undefined, currentAttempts: number, classPayload: JobOptions): number;
19
- declare function meilisecondsToSeconds(meiliseconds: number): number;
20
- declare function applyJitter(delay: number, jitterConfig: JitterConfig): number;
21
- declare function storeFailedJob(job: JobModel, exception: string): void;
22
- declare function now(): string;
23
- declare function updateJobAttempts(job: JobModel, currentAttempts: number, delay: number | null): Promise<void>;
24
- declare function timestampNow(): number;
package/dist/utils.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import type { QueueOption } from '@stacksjs/types';
2
-
3
- export declare function storeJob(name: string, options: QueueOption): Promise<void>;
4
- declare function generateUnixTimestamp(secondsToAdd: number): number;