@zudojs/queue 1.4.1 → 1.5.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.
package/README.md CHANGED
@@ -165,6 +165,32 @@ queue.events?.on("job:completed", ({ job, result }) => {
165
165
  });
166
166
  ```
167
167
 
168
+ `job:failed` fires on **every** failed attempt, not only the last, and a
169
+ processor failure reports `job.state === "failed"` on a retryable attempt
170
+ and on the final one alike, with `job.attempt` still counting the attempts
171
+ before this one. What follows tells them apart:
172
+
173
+ | Attempt | `job:failed` `job.state` | Then |
174
+ |---------|--------------------------|------|
175
+ | Retryable (attempts left) | `"failed"` | `job:retrying` (`"retrying"`); after the backoff the job is `"waiting"` again |
176
+ | Final (attempts exhausted) | `"failed"` | `job:dead-lettered` (`"dead_letter"`) |
177
+ | Stalled, reclaimed | `"active"` (as found), `JobStalledError` | the job is `"waiting"` again |
178
+ | Stalled `maxStalledCount` times | `"dead_letter"`, `JobStalledError` | `job:dead-lettered` |
179
+
180
+ To alert only on dead letters, subscribe to `job:dead-lettered`. It fires once
181
+ per job, when the job enters the dead-letter store, with the entry's `error`
182
+ (`JobMaxAttemptsError`, or `JobStalledError` for a stalled job) and `reason`
183
+ (the last attempt's error message, or `"Stalled N time(s)."`):
184
+
185
+ ```typescript
186
+ queue.events?.on("job:dead-lettered", ({ job, error, reason }) => {
187
+ alerts.page(`${job.name} ${job.id} dead-lettered: ${reason}`, error);
188
+ });
189
+ ```
190
+
191
+ It fires even if a custom `deadLetterStore.add()` rejects, in step with
192
+ `getStats().deadLettered`. Before 1.5.0 there was no dead-letter event.
193
+
168
194
  ## Workers, timeouts and context
169
195
 
170
196
  **One consumer at a time.** `queue.process()` registers a processor and, by
@@ -574,16 +574,22 @@ export class InMemoryQueue {
574
574
  failedAt: new Date().toISOString(),
575
575
  });
576
576
  this.jobs.set(job.id, deadLettered);
577
+ const reason = `Stalled ${count} time(s).`;
577
578
  void this.deadLetterStore
578
579
  .add({
579
580
  job: deadLettered,
580
581
  deadLetterAt: new Date(),
581
582
  error,
582
583
  attempts: deadLettered.attempt,
583
- reason: `Stalled ${count} time(s).`,
584
+ reason,
584
585
  })
585
586
  .catch(() => { });
586
587
  this.emitter.emit("job:failed", { job: deadLettered, error });
588
+ this.emitter.emit("job:dead-lettered", {
589
+ job: deadLettered,
590
+ error,
591
+ reason,
592
+ });
587
593
  this.recordSettled(deadLettered);
588
594
  continue;
589
595
  }
@@ -171,6 +171,11 @@ export async function handleJobFailure(job, errorMessage, deps) {
171
171
  }
172
172
  const deadLetterJob = updateJobState(incrementedJob, JobStateEnum.DEAD_LETTER, { error: maxAttemptsError.message });
173
173
  jobs.set(job.id, deadLetterJob);
174
+ emitter.emit("job:dead-lettered", {
175
+ job: deadLetterJob,
176
+ error: maxAttemptsError,
177
+ reason: errorMessage,
178
+ });
174
179
  deps.onSettled?.(deadLetterJob);
175
180
  }
176
181
  //# sourceMappingURL=inMemoryQueue.processing.js.map
@@ -267,6 +267,11 @@ export type QueueEventMap = {
267
267
  job: Job;
268
268
  result: unknown;
269
269
  };
270
+ /**
271
+ * An attempt failed. Fires on every failed attempt, retryable or final;
272
+ * `job.state` is `"failed"` for a processor failure either way. Use
273
+ * `job:retrying` or `job:dead-lettered` to tell the two apart.
274
+ */
270
275
  "job:failed": {
271
276
  job: Job;
272
277
  error: Error;
@@ -275,6 +280,17 @@ export type QueueEventMap = {
275
280
  job: Job;
276
281
  attempt: number;
277
282
  };
283
+ /**
284
+ * The job was moved to the dead-letter store (attempts exhausted, or
285
+ * stalled `maxStalledCount` times). Fires once per job, after the
286
+ * `job:failed` for its last attempt; `job.state` is `"dead_letter"` and
287
+ * `error` / `reason` are the dead-letter entry's.
288
+ */
289
+ "job:dead-lettered": {
290
+ job: Job;
291
+ error: Error;
292
+ reason?: string;
293
+ };
278
294
  /** A running job was aborted from outside — a drain, a close, a cancel. */
279
295
  "job:cancelled": {
280
296
  job: Job;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/queue",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Background job and asynchronous task infrastructure with in-memory and adapter-based queue implementations.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -25,9 +25,9 @@
25
25
  "!dist/.tsbuildinfo"
26
26
  ],
27
27
  "dependencies": {
28
- "@zudojs/errors": "1.3.0",
29
- "@zudojs/constants": "1.1.2",
30
- "@zudojs/serialization": "1.2.1"
28
+ "@zudojs/errors": "1.3.1",
29
+ "@zudojs/constants": "1.1.3",
30
+ "@zudojs/serialization": "1.2.2"
31
31
  },
32
32
  "devDependencies": {
33
33
  "typescript": "7.0.2",