@warlock.js/queue 5.30.0 → 5.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +4 -2
  3. package/cjs/index.cjs +32 -2
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/config.d.mts +4 -6
  6. package/esm/config.d.mts.map +1 -1
  7. package/esm/dashboard-boot.d.mts +2 -4
  8. package/esm/dashboard-boot.d.mts.map +1 -1
  9. package/esm/dashboard.d.mts +8 -7
  10. package/esm/dashboard.d.mts.map +1 -1
  11. package/esm/dashboard.mjs +1 -1
  12. package/esm/dashboard.mjs.map +1 -1
  13. package/esm/define-job.d.mts +2 -2
  14. package/esm/define-job.d.mts.map +1 -1
  15. package/esm/define-job.mjs +15 -1
  16. package/esm/define-job.mjs.map +1 -1
  17. package/esm/dispatch-interceptor.d.mts +33 -0
  18. package/esm/dispatch-interceptor.d.mts.map +1 -0
  19. package/esm/dispatch-interceptor.mjs +14 -0
  20. package/esm/dispatch-interceptor.mjs.map +1 -0
  21. package/esm/duration.d.mts +1 -3
  22. package/esm/duration.d.mts.map +1 -1
  23. package/esm/errors.d.mts +6 -7
  24. package/esm/errors.d.mts.map +1 -1
  25. package/esm/failed-jobs.d.mts +8 -7
  26. package/esm/failed-jobs.d.mts.map +1 -1
  27. package/esm/failed-jobs.mjs +2 -1
  28. package/esm/failed-jobs.mjs.map +1 -1
  29. package/esm/index.d.mts +3 -1
  30. package/esm/index.mjs +3 -1
  31. package/esm/job-registry.d.mts +10 -0
  32. package/esm/job-registry.d.mts.map +1 -0
  33. package/esm/process-job.mjs.map +1 -1
  34. package/esm/queue-connector.d.mts +3 -5
  35. package/esm/queue-connector.d.mts.map +1 -1
  36. package/esm/queue-connector.mjs.map +1 -1
  37. package/esm/queue-context.d.mts +8 -7
  38. package/esm/queue-context.d.mts.map +1 -1
  39. package/esm/queue-dashboard-unguarded.error.d.mts +1 -2
  40. package/esm/queue-dashboard-unguarded.error.d.mts.map +1 -1
  41. package/esm/queue-dashboard-unguarded.error.mjs.map +1 -1
  42. package/esm/queue-manager.d.mts +5 -7
  43. package/esm/queue-manager.d.mts.map +1 -1
  44. package/esm/types.d.mts +76 -46
  45. package/esm/types.d.mts.map +1 -1
  46. package/llms-full.txt +36 -7
  47. package/llms.txt +1 -1
  48. package/package.json +3 -3
  49. package/skills/define-jobs/SKILL.md +36 -6
  50. package/skills/index.md +4 -1
  51. package/skills/queue-notifications/SKILL.md +0 -1
package/esm/types.d.mts CHANGED
@@ -1,46 +1,52 @@
1
1
  import { ConnectionOptions } from "bullmq";
2
2
  import { Middleware } from "@warlock.js/core";
3
-
4
3
  //#region ../../packages/queue/src/types.d.ts
5
4
  /** Units accepted in a {@link Duration} string. */
6
- type DurationUnit = "ms" | "s" | "m" | "h" | "d";
5
+ export type DurationUnit = "ms" | "s" | "m" | "h" | "d";
7
6
  /**
8
7
  * A duration: milliseconds as a number, or a string such as `"500ms"`,
9
8
  * `"30s"`, `"10m"`, `"2h"`, `"1d"`.
10
9
  */
11
- type Duration = number | `${number}${DurationUnit}`;
10
+ export type Duration = number | `${number}${DurationUnit}`;
12
11
  /**
13
12
  * How a failed attempt waits before the next one. A number is a fixed delay in
14
13
  * milliseconds.
15
14
  */
16
- type JobBackoff = number | {
17
- /** `fixed` waits `delay` every time; `exponential` waits `delay * 2^(attempt - 1)`. */type: "fixed" | "exponential"; /** Base delay in milliseconds. */
15
+ export type JobBackoff = number | {
16
+ /** `fixed` waits `delay` every time; `exponential` waits `delay * 2^(attempt - 1)`. */
17
+ type: "fixed" | "exponential";
18
+ /** Base delay in milliseconds. */
18
19
  delay: number;
19
20
  };
20
21
  /**
21
22
  * How many finished jobs to keep: `true` removes them immediately, `false`
22
23
  * keeps them all, a number keeps the newest N.
23
24
  */
24
- type JobRetention = boolean | number;
25
+ export type JobRetention = boolean | number;
25
26
  /**
26
27
  * Options that shape how a job is retried and retained. Set app-wide in
27
28
  * `queue.defaultJobOptions`, per job in `defineJob`.
28
29
  */
29
- type JobOptions = {
30
- /** Total attempts including the first. Default `1` (no retry). */attempts?: number; /** Wait between attempts. */
31
- backoff?: JobBackoff; /** Retention for completed jobs. Default keeps them. */
32
- removeOnComplete?: JobRetention; /** Retention for failed jobs. Default keeps them, so `failedJobs()` can list them. */
30
+ export type JobOptions = {
31
+ /** Total attempts including the first. Default `1` (no retry). */
32
+ attempts?: number;
33
+ /** Wait between attempts. */
34
+ backoff?: JobBackoff;
35
+ /** Retention for completed jobs. Default keeps them. */
36
+ removeOnComplete?: JobRetention;
37
+ /** Retention for failed jobs. Default keeps them, so `failedJobs()` can list them. */
33
38
  removeOnFail?: JobRetention;
34
39
  };
35
40
  /**
36
41
  * In-process worker settings.
37
42
  */
38
- type QueueWorkersConfig = {
43
+ export type QueueWorkersConfig = {
39
44
  /**
40
45
  * Start workers in this process. Default `true`. Set `false` for a process
41
46
  * that only dispatches jobs while another process consumes them.
42
47
  */
43
- enabled?: boolean; /** Jobs processed in parallel per queue. Default `1`. */
48
+ enabled?: boolean;
49
+ /** Jobs processed in parallel per queue. Default `1`. */
44
50
  concurrency?: number;
45
51
  /**
46
52
  * How long shutdown waits for active jobs before force-closing the workers,
@@ -53,8 +59,10 @@ type QueueWorkersConfig = {
53
59
  * `enabled` is `true`. Equivalent to calling `queueDashboard()` yourself at
54
60
  * boot, driven by config instead — see `warlock add bull-board`.
55
61
  */
56
- type QueueDashboardConfig = {
57
- /** Mount the dashboard at boot. Default `false`. */enabled?: boolean; /** URL path the dashboard is mounted on. Default `"/admin/queues"`. */
62
+ export type QueueDashboardConfig = {
63
+ /** Mount the dashboard at boot. Default `false`. */
64
+ enabled?: boolean;
65
+ /** URL path the dashboard is mounted on. Default `"/admin/queues"`. */
58
66
  path?: string;
59
67
  /**
60
68
  * Run before every dashboard route — this is how the dashboard is guarded.
@@ -66,47 +74,65 @@ type QueueDashboardConfig = {
66
74
  /**
67
75
  * The `queue` configuration key — `src/config/queue.ts`.
68
76
  */
69
- type QueueConfig = {
77
+ export type QueueConfig = {
70
78
  /**
71
79
  * Redis connection. Any BullMQ connection option: `{ host, port, password, db }`,
72
80
  * `{ url }`, or an ioredis instance.
73
81
  */
74
- connection: ConnectionOptions; /** Redis key prefix for every queue. Default `"warlock"`. */
75
- prefix?: string; /** Queue name used when a job does not name one. Default `"default"`. */
76
- defaultQueue?: string; /** Defaults merged under every job's own options. */
77
- defaultJobOptions?: JobOptions; /** In-process workers. */
78
- workers?: QueueWorkersConfig; /** The bull-board job dashboard. */
82
+ connection: ConnectionOptions;
83
+ /** Redis key prefix for every queue. Default `"warlock"`. */
84
+ prefix?: string;
85
+ /** Queue name used when a job does not name one. Default `"default"`. */
86
+ defaultQueue?: string;
87
+ /** Defaults merged under every job's own options. */
88
+ defaultJobOptions?: JobOptions;
89
+ /** In-process workers. */
90
+ workers?: QueueWorkersConfig;
91
+ /** The bull-board job dashboard. */
79
92
  dashboard?: QueueDashboardConfig;
80
93
  };
81
94
  /** A progress value: a number (e.g. a percentage) or a JSON object. */
82
- type JobProgress = number | Record<string, unknown>;
95
+ export type JobProgress = number | Record<string, unknown>;
83
96
  /**
84
97
  * What a job handler receives beside its payload.
85
98
  */
86
- type JobContext = {
87
- /** The job id. */id: string; /** The job name given to `defineJob`. */
88
- name: string; /** The queue the job runs on. */
89
- queue: string; /** The current attempt, starting at `1`. */
90
- attempt: number; /** Total attempts allowed. */
91
- maxAttempts: number; /** Record progress; readable through `job.find(id)`. */
92
- progress(value: JobProgress): Promise<void>; /** Append a line to the job's log. */
93
- log(line: string): Promise<void>; /** State captured at dispatch by the registered queue context, if any. */
99
+ export type JobContext = {
100
+ /** The job id. */
101
+ id: string;
102
+ /** The job name given to `defineJob`. */
103
+ name: string;
104
+ /** The queue the job runs on. */
105
+ queue: string;
106
+ /** The current attempt, starting at `1`. */
107
+ attempt: number;
108
+ /** Total attempts allowed. */
109
+ maxAttempts: number;
110
+ /** Record progress; readable through `job.find(id)`. */
111
+ progress(value: JobProgress): Promise<void>;
112
+ /** Append a line to the job's log. */
113
+ log(line: string): Promise<void>;
114
+ /** State captured at dispatch by the registered queue context, if any. */
94
115
  context: unknown;
95
116
  };
96
117
  /**
97
118
  * The definition passed to `defineJob`.
98
119
  */
99
- type JobDefinition<TPayload, TResult> = JobOptions & {
100
- /** Set `false` to skip capturing the queue context for this job. */context?: false; /** Unique job name, e.g. `"invoices.send"`. */
101
- name: string; /** Queue to run on. Default: `queue.defaultQueue` (`"default"`). */
102
- queue?: string; /** The work. Throw to fail the attempt; the return value is stored as the job result. */
120
+ export type JobDefinition<TPayload, TResult> = JobOptions & {
121
+ /** Set `false` to skip capturing the queue context for this job. */
122
+ context?: false;
123
+ /** Unique job name, e.g. `"invoices.send"`. */
124
+ name: string;
125
+ /** Queue to run on. Default: `queue.defaultQueue` (`"default"`). */
126
+ queue?: string;
127
+ /** The work. Throw to fail the attempt; the return value is stored as the job result. */
103
128
  handle(payload: TPayload, context: JobContext): Promise<TResult> | TResult;
104
129
  };
105
130
  /**
106
131
  * Per-dispatch options.
107
132
  */
108
- type DispatchOptions = {
109
- /** Wait before the job becomes available. */delay?: Duration;
133
+ export type DispatchOptions = {
134
+ /** Wait before the job becomes available. */
135
+ delay?: Duration;
110
136
  /**
111
137
  * Priority: `1` is the highest; larger numbers run later. Omit for no
112
138
  * priority — such jobs run ahead of every prioritized job.
@@ -116,20 +142,22 @@ type DispatchOptions = {
116
142
  * Explicit job id. Dispatching again with an id that still exists is a
117
143
  * no-op, which makes dispatch idempotent.
118
144
  */
119
- jobId?: string; /** Override the job's attempts for this dispatch. */
120
- attempts?: number; /** Override the job's backoff for this dispatch. */
145
+ jobId?: string;
146
+ /** Override the job's attempts for this dispatch. */
147
+ attempts?: number;
148
+ /** Override the job's backoff for this dispatch. */
121
149
  backoff?: JobBackoff;
122
150
  };
123
151
  /** What `dispatch` resolves with. */
124
- type DispatchedJob = {
152
+ export type DispatchedJob = {
125
153
  id: string;
126
154
  name: string;
127
155
  queue: string;
128
156
  };
129
157
  /** A job's lifecycle state. */
130
- type JobState = "waiting" | "delayed" | "prioritized" | "active" | "completed" | "failed" | "waiting-children" | "unknown";
158
+ export type JobState = "waiting" | "delayed" | "prioritized" | "active" | "completed" | "failed" | "waiting-children" | "unknown";
131
159
  /** A point-in-time view of a job. */
132
- type JobSnapshot<TPayload = unknown, TResult = unknown> = {
160
+ export type JobSnapshot<TPayload = unknown, TResult = unknown> = {
133
161
  id: string;
134
162
  name: string;
135
163
  queue: string;
@@ -143,7 +171,7 @@ type JobSnapshot<TPayload = unknown, TResult = unknown> = {
143
171
  finishedAt?: Date;
144
172
  };
145
173
  /** A failed job, with the means to retry it. */
146
- type FailedJob<TPayload = unknown> = {
174
+ export type FailedJob<TPayload = unknown> = {
147
175
  id: string;
148
176
  name: string;
149
177
  queue: string;
@@ -151,18 +179,20 @@ type FailedJob<TPayload = unknown> = {
151
179
  attemptsMade: number;
152
180
  failedReason: string;
153
181
  stacktrace: string[];
154
- failedAt?: Date; /** Move the job back to waiting so a worker picks it up again. */
182
+ failedAt?: Date;
183
+ /** Move the job back to waiting so a worker picks it up again. */
155
184
  retry(): Promise<void>;
156
185
  };
157
186
  /**
158
187
  * A job returned by `defineJob`.
159
188
  */
160
- type QueueJob<TPayload, TResult = unknown> = {
189
+ export type QueueJob<TPayload, TResult = unknown> = {
161
190
  readonly name: string;
162
- readonly queue: string; /** Enqueue the job. */
163
- dispatch(payload: TPayload, options?: DispatchOptions): Promise<DispatchedJob>; /** Read a job of this type by id; `undefined` when it no longer exists. */
191
+ readonly queue: string;
192
+ /** Enqueue the job. */
193
+ dispatch(payload: TPayload, options?: DispatchOptions): Promise<DispatchedJob>;
194
+ /** Read a job of this type by id; `undefined` when it no longer exists. */
164
195
  find(id: string): Promise<JobSnapshot<TPayload, TResult> | undefined>;
165
196
  };
166
197
  //#endregion
167
- export { DispatchOptions, DispatchedJob, Duration, DurationUnit, FailedJob, JobBackoff, JobContext, JobDefinition, JobOptions, JobProgress, JobRetention, JobSnapshot, JobState, QueueConfig, QueueDashboardConfig, QueueJob, QueueWorkersConfig };
168
198
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/types.ts"],"mappings":";;;;;KAIY,YAAA;AAAZ;;;;AAAA,KAMY,QAAA,wBAAgC,YAAY;AAAxD;;;;AAAA,KAMY,UAAA;EAAA,uFAIN,IAAA;EAEA,KAAK;AAAA;AAOX;;;;AAAA,KAAY,YAAA;AAMZ;;;;AAAA,KAAY,UAAA;EAQK,kEANf,QAAA,WAM2B;EAJ3B,OAAA,GAAU,UAAA,EAAV;EAEA,gBAAA,GAAmB,YAAA,EAAnB;EAEA,YAAA,GAAe,YAAA;AAAA;;;AAAY;KAMjB,kBAAA;EAAkB;;;;EAK5B,OAAA,YAOA;EALA,WAAA;EAKe;AAQjB;;;EARE,eAAA;AAAA;;;;;AAkBuB;KAVb,oBAAA;EAgBW,oDAdrB,OAAA,YAmBY;EAjBZ,IAAA;EAyBU;;;;;EAnBV,UAAA,GAAa,UAAU;AAAA;;;;KAMb,WAAA;EAaA;;;;EARV,UAAA,EAAY,iBAAA,EAcF;EAZV,MAAA;EAEA,YAAA,WAUuC;EARvC,iBAAA,GAAoB,UAAA,EAaA;EAXpB,OAAA,GAAU,kBAAA,EAuBM;EArBhB,SAAA,GAAY,oBAAA;AAAA;;KAIF,WAAA,YAAuB,MAAM;;;;KAK7B,UAAA;EAUV,kBARA,EAAA,UAUgB;EARhB,IAAA,UAQ8B;EAN9B,KAAA,UAQI;EANJ,OAAA,UAQA;EANA,WAAA,UAMO;EAJP,QAAA,CAAS,KAAA,EAAO,WAAA,GAAc,OAAA,QAUP;EARvB,GAAA,CAAI,IAAA,WAAe,OAAA,QAQ0B;EAN7C,OAAA;AAAA;;;;KAMU,aAAA,sBAAmC,UAAA;EAQ6B,oEAN1E,OAAA,UAFkC;EAIlC,IAAA,UAFA;EAIA,KAAA;EAEA,MAAA,CAAO,OAAA,EAAS,QAAA,EAAU,OAAA,EAAS,UAAA,GAAa,OAAA,CAAQ,OAAA,IAAW,OAAA;AAAA;;;;KAMzD,eAAA;EAN8C,6CAQxD,KAAA,GAAQ,QAAA;EARkE;AAAA;AAM5E;;EAOE,QAAA;EASoB;;;;EAJpB,KAAA,WAEA;EAAA,QAAA,WAEU;EAAV,OAAA,GAAU,UAAU;AAAA;AAItB;AAAA,KAAY,aAAA;EACV,EAAA;EACA,IAAA;EACA,KAAA;AAAA;;KAIU,QAAA;AAJL;AAAA,KAeK,WAAA;EACV,EAAA;EACA,IAAA;EACA,KAAA;EACA,KAAA,EAAO,QAAA;EACP,OAAA,EAAS,QAAA;EACT,QAAA,EAAU,WAAA;EACV,YAAA;EACA,MAAA,GAAS,OAAA;EACT,YAAA;EACA,SAAA,EAAW,IAAA;EACX,UAAA,GAAa,IAAA;AAAA;;KAIH,SAAA;EACV,EAAA;EACA,IAAA;EACA,KAAA;EACA,OAAA,EAAS,QAAA;EACT,YAAA;EACA,YAAA;EACA,UAAA;EACA,QAAA,GAAW,IAAA,EAlBX;EAoBA,KAAA,IAAS,OAAA;AAAA;;;;KAMC,QAAA;EAAA,SACD,IAAA;EAAA,SACA,KAAA,UAvBE;EAyBX,QAAA,CAAS,OAAA,EAAS,QAAA,EAAU,OAAA,GAAU,eAAA,GAAkB,OAAA,CAAQ,aAAA,GAxBnD;EA0Bb,IAAA,CAAK,EAAA,WAAa,OAAA,CAAQ,WAAA,CAAY,QAAA,EAAU,OAAA;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/types.ts"],"mappings":";;;;YAIY;;;;;YAMA,gCAAgC;;;;;YAMhC;;EAIN;;EAEA;;;;;;YAOM;;;;;YAMA;;EAEV;;EAEA,UAAU;;EAEV,mBAAmB;;EAEnB,eAAe;;;;;YAML;;;;;EAKV;;EAEA;;;;;EAKA;;;;;;;YAQU;;EAEV;;EAEA;;;;;;EAMA,aAAa;;;;;YAMH;;;;;EAKV,YAAY;;EAEZ;;EAEA;;EAEA,oBAAoB;;EAEpB,UAAU;;EAEV,YAAY;;;YAIF,uBAAuB;;;;YAKvB;;EAEV;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,SAAS,OAAO,cAAc;;EAE9B,IAAI,eAAe;;EAEnB;;;;;YAMU,cAAc,UAAU,WAAW;;EAE7C;;EAEA;;EAEA;;EAEA,OAAO,SAAS,UAAU,SAAS,aAAa,QAAQ,WAAW;;;;;YAMzD;;EAEV,QAAQ;;;;;EAKR;;;;;EAKA;;EAEA;;EAEA,UAAU;;;YAIA;EACV;EACA;EACA;;;YAIU;;YAWA,YAAY,oBAAoB;EAC1C;EACA;EACA;EACA,OAAO;EACP,SAAS;EACT,UAAU;EACV;EACA,SAAS;EACT;EACA,WAAW;EACX,aAAa;;;YAIH,UAAU;EACpB;EACA;EACA;EACA,SAAS;EACT;EACA;EACA;EACA,WAAW;;EAEX,SAAS;;;;;YAMC,SAAS,UAAU;WACpB;WACA;;EAET,SAAS,SAAS,UAAU,UAAU,kBAAkB,QAAQ;;EAEhE,KAAK,aAAa,QAAQ,YAAY,UAAU"}
package/llms-full.txt CHANGED
@@ -71,7 +71,7 @@ Every process must use the same `prefix`, or they will not see each other's jobs
71
71
 
72
72
  ---
73
73
  name: define-jobs
74
- description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
74
+ description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
75
75
  ---
76
76
 
77
77
  # Define and dispatch jobs
@@ -99,11 +99,14 @@ The job module must be imported by the process that runs workers — in a Warloc
99
99
  ```ts
100
100
  const { id } = await sendInvoice.dispatch({ invoiceId: "42" });
101
101
 
102
- await sendInvoice.dispatch({ invoiceId: "43" }, {
103
- delay: "10m", // ms number or "500ms" | "30s" | "10m" | "2h" | "1d"
104
- priority: 1, // 1 runs first; larger numbers later
105
- jobId: "invoice:43", // a second dispatch with a live id is ignored
106
- });
102
+ await sendInvoice.dispatch(
103
+ { invoiceId: "43" },
104
+ {
105
+ delay: "10m", // ms number or "500ms" | "30s" | "10m" | "2h" | "1d"
106
+ priority: 1, // 1 runs first; larger numbers later
107
+ jobId: "invoice:43", // a second dispatch with a live id is ignored
108
+ },
109
+ );
107
110
  ```
108
111
 
109
112
  Option precedence: dispatch options > `defineJob` > `queue.defaultJobOptions`.
@@ -115,6 +118,16 @@ const job = await sendInvoice.find(id);
115
118
  // { id, name, queue, state, payload, progress, attemptsMade, result, failedReason, createdAt, finishedAt }
116
119
  ```
117
120
 
121
+ ## List the defined jobs
122
+
123
+ `registeredJobs()` returns every definition `defineJob()` registered in this process, and `queueOf(definition)` the queue it runs on. Neither touches Redis; `warlock inspect --section jobs` prints the same list for a whole app.
124
+
125
+ ```ts
126
+ import { queueOf, registeredJobs } from "@warlock.js/queue";
127
+
128
+ const jobs = registeredJobs().map((job) => ({ name: job.name, queue: queueOf(job) }));
129
+ ```
130
+
118
131
  ## Carry request context into jobs
119
132
 
120
133
  ```ts
@@ -131,6 +144,23 @@ setQueueContext(
131
144
  - `defineJob({ context: false })` opts a job out.
132
145
  - Throw `UnrecoverableJobError` from a handler or `restore` to fail the job without retries.
133
146
 
147
+ ## Test dispatches without Redis
148
+
149
+ In the test environment `queueFake` from `@warlock.js/core/tests` is installed by default: dispatches are recorded, not pushed, and `find()` answers from the record.
150
+
151
+ ```ts
152
+ // with: import { queueFake } from "@warlock.js/core/tests";
153
+ // plus your job (sendInvoice) and service (placeOrderService)
154
+ it("queues the invoice", async () => {
155
+ const order = await placeOrderService({ items: ["pen"] });
156
+
157
+ queueFake.assertPushed(sendInvoice, { invoiceId: order.invoiceId });
158
+ expect(queueFake.pushed(sendInvoice)).toHaveLength(1);
159
+ });
160
+ ```
161
+
162
+ Jobs are not run — call the handler to test it. Opt out with `tests.fakes: { queue: false }`. The seam is `setDispatchInterceptor({ dispatch, find? })`: while set, every job's `dispatch()` / `find()` goes to it instead of BullMQ; set it back to `undefined` to restore the broker.
163
+
134
164
  ## Rules
135
165
 
136
166
  - A job name with no handler in the worker process fails at once, without retries.
@@ -278,4 +308,3 @@ await orderShipped.queue(user, { order }, { delay: "10m" }); // delivered in 10
278
308
  - Deliveries run under the job name `warlock.notifications.deliver`.
279
309
 
280
310
 
281
-
package/llms.txt CHANGED
@@ -7,7 +7,7 @@
7
7
  ## Skills
8
8
 
9
9
  - [configure-queue](@warlock.js/queue/configure-queue/SKILL.md): Configure `@warlock.js/queue`: the declarative `src/config/queue.ts` (`QueueConfig` — `connection`, `prefix`, `defaultQueue`, `defaultJobOptions`, `workers: { enabled, concurrency, shutdownTimeout }`), registering `queueConnector()` in `warlock.config.ts > connectors`, running a dispatch-only process, and graceful shutdown. Programmatic `setQueueConfig` / `startWorkers` / `closeQueue` for scripts and tests. Triggers: `QueueConfig`, `queueConnector`, `setQueueConfig`, `startWorkers`, `closeQueue`, `workers.enabled`, `shutdownTimeout`; "configure the queue", "connect BullMQ to Redis", "disable workers in the web process", "graceful shutdown of jobs". Skip: writing jobs — the `define-jobs` topic.
10
- - [define-jobs](@warlock.js/queue/define-jobs/SKILL.md): Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.
10
+ - [define-jobs](@warlock.js/queue/define-jobs/SKILL.md): Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.
11
11
  - [manage-failed-jobs](@warlock.js/queue/manage-failed-jobs/SKILL.md): Inspect and retry failed `@warlock.js/queue` jobs: `failedJobs({ queue, start, end })` returns `FailedJob[]` (`id`, `name`, `payload`, `attemptsMade`, `failedReason`, `stacktrace`, `failedAt`, `retry()`), `retryFailedJob(id, { queue })` (throws `FailedJobNotFoundError`), and the bull-board UI — `warlock add bull-board` (requires: ["queue"], so it installs queue first when missing) writes a `dashboard: { enabled, path, middleware }` block to `src/config/queue.ts`; a guard middleware is required in production or boot throws `QueueDashboardUnguardedError`. Manual `queueDashboard(server, { basePath, queues, middleware })` also available — needs `@bull-board/api` + `@bull-board/fastify`, loaded only on call, missing ones throw `QueueDashboardDependencyError`. Triggers: `failedJobs`, `retryFailedJob`, `queueDashboard`, `bull-board`, `dashboard.enabled`, `QueueDashboardUnguardedError`; "list failed jobs", "retry a failed job", "queue dashboard", "job admin UI". Skip: defining retries — the `define-jobs` topic.
12
12
  - [overview](@warlock.js/queue/overview/SKILL.md): @warlock.js/queue overview for orientation and choosing the focused skill for the task.
13
13
  - [queue-notifications](@warlock.js/queue/queue-notifications/SKILL.md): Send `@warlock.js/notifications` `.queue()` deliveries through BullMQ with `bullmqQueue({ queue?, attempts?, backoff? })` from `@warlock.js/notifications` — put it in the `queue` slot of `src/config/notifications.ts`; the queue workers deliver. Honours `SendOptions.delay` (number = seconds, or "10m"), retries a failing `channel.send`, and fails at once for a channel missing from the worker config. Triggers: `bullmqQueue`, `NotificationConfig.queue`, `QueuePackageNotInstalledError`; "queue notifications with BullMQ", "retry notification delivery", "delayed notification". Skip: the herald backend — the `queue-notifications` topic of the `warlock-js-notifications` skill.
package/package.json CHANGED
@@ -11,8 +11,8 @@
11
11
  "peerDependencies": {
12
12
  "@bull-board/api": "^9.0.0",
13
13
  "@bull-board/fastify": "^9.0.0",
14
- "@warlock.js/core": "5.30.0",
15
- "@warlock.js/logger": "5.30.0"
14
+ "@warlock.js/core": "5.32.0",
15
+ "@warlock.js/logger": "5.32.0"
16
16
  },
17
17
  "peerDependenciesMeta": {
18
18
  "@bull-board/api": {
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "author": "hassanzohdy",
40
40
  "license": "MIT",
41
- "version": "5.30.0",
41
+ "version": "5.32.0",
42
42
  "main": "./cjs/index.cjs",
43
43
  "module": "./esm/index.mjs",
44
44
  "types": "./esm/index.d.mts",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: define-jobs
3
- description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
3
+ description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
4
4
  ---
5
5
 
6
6
  # Define and dispatch jobs
@@ -28,11 +28,14 @@ The job module must be imported by the process that runs workers — in a Warloc
28
28
  ```ts
29
29
  const { id } = await sendInvoice.dispatch({ invoiceId: "42" });
30
30
 
31
- await sendInvoice.dispatch({ invoiceId: "43" }, {
32
- delay: "10m", // ms number or "500ms" | "30s" | "10m" | "2h" | "1d"
33
- priority: 1, // 1 runs first; larger numbers later
34
- jobId: "invoice:43", // a second dispatch with a live id is ignored
35
- });
31
+ await sendInvoice.dispatch(
32
+ { invoiceId: "43" },
33
+ {
34
+ delay: "10m", // ms number or "500ms" | "30s" | "10m" | "2h" | "1d"
35
+ priority: 1, // 1 runs first; larger numbers later
36
+ jobId: "invoice:43", // a second dispatch with a live id is ignored
37
+ },
38
+ );
36
39
  ```
37
40
 
38
41
  Option precedence: dispatch options > `defineJob` > `queue.defaultJobOptions`.
@@ -44,6 +47,16 @@ const job = await sendInvoice.find(id);
44
47
  // { id, name, queue, state, payload, progress, attemptsMade, result, failedReason, createdAt, finishedAt }
45
48
  ```
46
49
 
50
+ ## List the defined jobs
51
+
52
+ `registeredJobs()` returns every definition `defineJob()` registered in this process, and `queueOf(definition)` the queue it runs on. Neither touches Redis; `warlock inspect --section jobs` prints the same list for a whole app.
53
+
54
+ ```ts
55
+ import { queueOf, registeredJobs } from "@warlock.js/queue";
56
+
57
+ const jobs = registeredJobs().map((job) => ({ name: job.name, queue: queueOf(job) }));
58
+ ```
59
+
47
60
  ## Carry request context into jobs
48
61
 
49
62
  ```ts
@@ -60,6 +73,23 @@ setQueueContext(
60
73
  - `defineJob({ context: false })` opts a job out.
61
74
  - Throw `UnrecoverableJobError` from a handler or `restore` to fail the job without retries.
62
75
 
76
+ ## Test dispatches without Redis
77
+
78
+ In the test environment `queueFake` from `@warlock.js/core/tests` is installed by default: dispatches are recorded, not pushed, and `find()` answers from the record.
79
+
80
+ ```ts
81
+ // with: import { queueFake } from "@warlock.js/core/tests";
82
+ // plus your job (sendInvoice) and service (placeOrderService)
83
+ it("queues the invoice", async () => {
84
+ const order = await placeOrderService({ items: ["pen"] });
85
+
86
+ queueFake.assertPushed(sendInvoice, { invoiceId: order.invoiceId });
87
+ expect(queueFake.pushed(sendInvoice)).toHaveLength(1);
88
+ });
89
+ ```
90
+
91
+ Jobs are not run — call the handler to test it. Opt out with `tests.fakes: { queue: false }`. The seam is `setDispatchInterceptor({ dispatch, find? })`: while set, every job's `dispatch()` / `find()` goes to it instead of BullMQ; set it back to `undefined` to restore the broker.
92
+
63
93
  ## Rules
64
94
 
65
95
  - A job name with no handler in the worker process fails at once, without retries.
package/skills/index.md CHANGED
@@ -1,11 +1,13 @@
1
1
  ---
2
- description: "Durable background jobs on BullMQ + Redis: retries, delays, priorities, workers, failed-job recovery. Exports `defineJob`, `queueConnector`, `startWorkers`, `closeQueue`, `getQueue`, `failedJobs`, `retryFailedJob`, `mountQueueDashboard`, `QueueConfig`, `QueueJob`. Use for: \"run this in the background\", \"retry a failed job\", \"delay a job\", \"survive restarts\", \"job dashboard\". Not this package: cron or recurring schedules → @warlock.js/scheduler; broker pub/sub messaging → @warlock.js/herald; core `Queue` is an in-memory batcher."
2
+ description: 'Durable background jobs on BullMQ + Redis: retries, delays, priorities, workers, failed-job recovery. Exports `defineJob`, `queueConnector`, `startWorkers`, `closeQueue`, `getQueue`, `failedJobs`, `retryFailedJob`, `mountQueueDashboard`, `QueueConfig`, `QueueJob`. Use for: "run this in the background", "retry a failed job", "delay a job", "survive restarts", "job dashboard". Not this package: cron or recurring schedules → @warlock.js/scheduler; broker pub/sub messaging → @warlock.js/herald; core `Queue` is an in-memory batcher.'
3
3
  ---
4
+
4
5
  # @warlock.js/queue
5
6
 
6
7
  Jobs are stored in Redis (required; not bundled), retried on failure, and run by workers, in the app process by default. `defineJob({ name, handle })` registers a handler and returns a typed job; `job.dispatch(payload, options)` enqueues it. Any process with the same definition and a worker can run it. Server-only.
7
8
 
8
9
  ## The 80% path
10
+
9
11
  1. Orient with `overview.md`.
10
12
  2. Configure `src/config/queue.ts` and register `queueConnector()`: `configure-queue.md`.
11
13
  3. Define and dispatch jobs (attempts, backoff, delay, priority, progress): `define-jobs.md`.
@@ -13,6 +15,7 @@ Jobs are stored in Redis (required; not bundled), retried on failure, and run by
13
15
  5. Back `@warlock.js/notifications` `.queue()` with BullMQ: `queue-notifications.md`.
14
16
 
15
17
  ## Conventions and pitfalls
18
+
16
19
  - Job names are the routing key; a job with no matching `defineJob` in the worker process cannot run.
17
20
  - Handlers must be idempotent: retries re-run them.
18
21
  - Shutdown lets active jobs finish up to `workers.shutdownTimeout`, then closes connections.
@@ -32,4 +32,3 @@ await orderShipped.queue(user, { order }, { delay: "10m" }); // delivered in 10
32
32
  - `channel.send` throws → retried per `attempts` / `backoff`, then listed by `failedJobs()`.
33
33
  - Channel not configured in the worker → fails at once, no retries.
34
34
  - Deliveries run under the job name `warlock.notifications.deliver`.
35
-