@zudojs/queue 1.5.1 → 1.6.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 (123) hide show
  1. package/dist/contextCarrier/contextCarrier.core.d.ts +0 -1
  2. package/dist/contextCarrier/contextCarrier.core.js +0 -1
  3. package/dist/contextCarrier/contextCarrier.type.d.ts +0 -1
  4. package/dist/contextCarrier/contextCarrier.type.js +0 -1
  5. package/dist/contextCarrier/index.d.ts +0 -1
  6. package/dist/contextCarrier/index.js +0 -1
  7. package/dist/deadLetter/deadLetter.core.d.ts +0 -1
  8. package/dist/deadLetter/deadLetter.core.js +0 -1
  9. package/dist/deadLetter/deadLetter.type.d.ts +0 -1
  10. package/dist/deadLetter/deadLetter.type.js +0 -1
  11. package/dist/deadLetter/index.d.ts +0 -1
  12. package/dist/deadLetter/index.js +0 -1
  13. package/dist/inMemoryQueue/inMemoryQueue.core.d.ts +0 -1
  14. package/dist/inMemoryQueue/inMemoryQueue.core.js +1 -1
  15. package/dist/inMemoryQueue/inMemoryQueue.factory.d.ts +0 -1
  16. package/dist/inMemoryQueue/inMemoryQueue.factory.js +0 -1
  17. package/dist/inMemoryQueue/inMemoryQueue.processing.d.ts +19 -3
  18. package/dist/inMemoryQueue/inMemoryQueue.processing.js +37 -15
  19. package/dist/inMemoryQueue/inMemoryQueue.scheduling.d.ts +0 -1
  20. package/dist/inMemoryQueue/inMemoryQueue.scheduling.js +0 -1
  21. package/dist/inMemoryQueue/inMemoryQueue.settle.d.ts +0 -1
  22. package/dist/inMemoryQueue/inMemoryQueue.settle.js +0 -1
  23. package/dist/inMemoryQueue/index.d.ts +0 -1
  24. package/dist/inMemoryQueue/index.js +0 -1
  25. package/dist/inMemoryQueue/polling/inMemoryQueue.poller.d.ts +0 -1
  26. package/dist/inMemoryQueue/polling/inMemoryQueue.poller.js +0 -1
  27. package/dist/inMemoryQueue/polling/inMemoryQueue.select.d.ts +5 -1
  28. package/dist/inMemoryQueue/polling/inMemoryQueue.select.js +33 -10
  29. package/dist/inMemoryQueue/polling/index.d.ts +0 -1
  30. package/dist/inMemoryQueue/polling/index.js +0 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +4 -1
  33. package/dist/job/index.d.ts +0 -1
  34. package/dist/job/index.js +0 -1
  35. package/dist/job/job.core.d.ts +0 -1
  36. package/dist/job/job.core.js +0 -1
  37. package/dist/job/job.type.d.ts +0 -1
  38. package/dist/job/job.type.js +0 -1
  39. package/dist/jobContext/index.d.ts +0 -1
  40. package/dist/jobContext/index.js +0 -1
  41. package/dist/jobContext/jobContext.core.d.ts +0 -1
  42. package/dist/jobContext/jobContext.core.js +0 -1
  43. package/dist/jobContext/jobContext.type.d.ts +0 -1
  44. package/dist/jobContext/jobContext.type.js +0 -1
  45. package/dist/jobFailure/index.d.ts +8 -0
  46. package/dist/jobFailure/index.js +7 -0
  47. package/dist/jobFailure/jobFailure.unrecoverable.d.ts +51 -0
  48. package/dist/jobFailure/jobFailure.unrecoverable.js +65 -0
  49. package/dist/jobOptions/index.d.ts +0 -1
  50. package/dist/jobOptions/index.js +0 -1
  51. package/dist/jobOptions/jobOptions.core.d.ts +0 -1
  52. package/dist/jobOptions/jobOptions.core.js +0 -1
  53. package/dist/jobOptions/jobOptions.type.d.ts +0 -1
  54. package/dist/jobOptions/jobOptions.type.js +0 -1
  55. package/dist/jobResult/index.d.ts +0 -1
  56. package/dist/jobResult/index.js +0 -1
  57. package/dist/jobResult/jobResult.core.d.ts +8 -2
  58. package/dist/jobResult/jobResult.core.js +7 -2
  59. package/dist/jobResult/jobResult.type.d.ts +6 -1
  60. package/dist/jobResult/jobResult.type.js +0 -1
  61. package/dist/jobTypes/index.d.ts +0 -1
  62. package/dist/jobTypes/index.js +0 -1
  63. package/dist/jobTypes/jobTypes.type.d.ts +0 -1
  64. package/dist/jobTypes/jobTypes.type.js +0 -1
  65. package/dist/middleware/index.d.ts +0 -1
  66. package/dist/middleware/index.js +0 -1
  67. package/dist/middleware/middleware.core.d.ts +0 -1
  68. package/dist/middleware/middleware.core.js +0 -1
  69. package/dist/middleware/middleware.type.d.ts +0 -1
  70. package/dist/middleware/middleware.type.js +0 -1
  71. package/dist/processor/index.d.ts +0 -1
  72. package/dist/processor/index.js +0 -1
  73. package/dist/processor/processor.core.d.ts +0 -1
  74. package/dist/processor/processor.core.js +0 -1
  75. package/dist/processor/processor.type.d.ts +0 -1
  76. package/dist/processor/processor.type.js +0 -1
  77. package/dist/queue/index.d.ts +0 -1
  78. package/dist/queue/index.js +0 -1
  79. package/dist/queue/queue.core.d.ts +0 -1
  80. package/dist/queue/queue.core.js +0 -1
  81. package/dist/queue/queue.report.d.ts +0 -1
  82. package/dist/queue/queue.report.js +0 -1
  83. package/dist/queue/queue.type.d.ts +6 -1
  84. package/dist/queue/queue.type.js +0 -1
  85. package/dist/queueEmitter/index.d.ts +0 -1
  86. package/dist/queueEmitter/index.js +0 -1
  87. package/dist/queueEmitter/queueEmitter.core.d.ts +0 -1
  88. package/dist/queueEmitter/queueEmitter.core.js +0 -1
  89. package/dist/queueEmitter/queueEmitter.type.d.ts +0 -1
  90. package/dist/queueEmitter/queueEmitter.type.js +0 -1
  91. package/dist/queueErrors/index.d.ts +7 -0
  92. package/dist/queueErrors/index.js +6 -0
  93. package/dist/queueErrors/queueError.base.d.ts +13 -0
  94. package/dist/queueErrors/queueError.base.js +12 -0
  95. package/dist/queueManager/index.d.ts +0 -1
  96. package/dist/queueManager/index.js +0 -1
  97. package/dist/queueManager/queueManager.core.d.ts +0 -1
  98. package/dist/queueManager/queueManager.core.js +0 -1
  99. package/dist/queueManager/queueManager.type.d.ts +0 -1
  100. package/dist/queueManager/queueManager.type.js +0 -1
  101. package/dist/queueRegistry/index.d.ts +0 -1
  102. package/dist/queueRegistry/index.js +0 -1
  103. package/dist/queueRegistry/queueRegistry.core.d.ts +0 -1
  104. package/dist/queueRegistry/queueRegistry.core.js +0 -1
  105. package/dist/queueRegistry/queueRegistry.type.d.ts +0 -1
  106. package/dist/queueRegistry/queueRegistry.type.js +0 -1
  107. package/dist/retryPolicy/index.d.ts +1 -2
  108. package/dist/retryPolicy/index.js +1 -2
  109. package/dist/retryPolicy/retryPolicy.core.d.ts +7 -2
  110. package/dist/retryPolicy/retryPolicy.core.js +8 -3
  111. package/dist/serializer/index.d.ts +0 -1
  112. package/dist/serializer/index.js +0 -1
  113. package/dist/serializer/serializer.core.d.ts +0 -1
  114. package/dist/serializer/serializer.core.js +0 -1
  115. package/dist/serializer/serializer.type.d.ts +0 -1
  116. package/dist/serializer/serializer.type.js +0 -1
  117. package/dist/worker/index.d.ts +0 -1
  118. package/dist/worker/index.js +0 -1
  119. package/dist/worker/worker.core.d.ts +0 -1
  120. package/dist/worker/worker.core.js +0 -1
  121. package/dist/worker/worker.type.d.ts +0 -1
  122. package/dist/worker/worker.type.js +0 -1
  123. package/package.json +4 -4
@@ -33,4 +33,3 @@ export declare function captureContext(carriers: readonly QueueContextCarrier[]
33
33
  * @returns Whatever `fn` resolves to.
34
34
  */
35
35
  export declare function runWithContext<T>(carriers: readonly QueueContextCarrier[] | undefined, job: Job<unknown>, fn: () => Promise<T>): Promise<T>;
36
- //# sourceMappingURL=contextCarrier.core.d.ts.map
@@ -77,4 +77,3 @@ export function runWithContext(carriers, job, fn) {
77
77
  }
78
78
  return run();
79
79
  }
80
- //# sourceMappingURL=contextCarrier.core.js.map
@@ -38,4 +38,3 @@ export interface QueueContextCarrier<TValue = unknown> {
38
38
  */
39
39
  restore<T>(value: TValue, run: () => Promise<T>): Promise<T>;
40
40
  }
41
- //# sourceMappingURL=contextCarrier.type.d.ts.map
@@ -4,4 +4,3 @@
4
4
  * @module contextCarrier/contextCarrier.type
5
5
  */
6
6
  export {};
7
- //# sourceMappingURL=contextCarrier.type.js.map
@@ -8,4 +8,3 @@
8
8
  */
9
9
  export type { QueueContextCarrier } from "./contextCarrier.type.js";
10
10
  export { CONTEXT_METADATA_KEY, captureContext, runWithContext, } from "./contextCarrier.core.js";
11
- //# sourceMappingURL=index.d.ts.map
@@ -7,4 +7,3 @@
7
7
  * the request that enqueued it.
8
8
  */
9
9
  export { CONTEXT_METADATA_KEY, captureContext, runWithContext, } from "./contextCarrier.core.js";
10
- //# sourceMappingURL=index.js.map
@@ -35,4 +35,3 @@ export declare function createInMemoryDeadLetterStore<TData = unknown>(options?:
35
35
  export declare function moveToDeadLetter<TData>(store: DeadLetterStore<TData>, job: Job<TData>, error: Error, options?: {
36
36
  reason?: string;
37
37
  }): Promise<void>;
38
- //# sourceMappingURL=deadLetter.core.d.ts.map
@@ -64,4 +64,3 @@ export async function moveToDeadLetter(store, job, error, options) {
64
64
  };
65
65
  await store.add(deadLetterJob);
66
66
  }
67
- //# sourceMappingURL=deadLetter.core.js.map
@@ -30,4 +30,3 @@ export interface DeadLetterStore<TData = unknown> {
30
30
  /** Clear all dead letter jobs. */
31
31
  clear(): Promise<void>;
32
32
  }
33
- //# sourceMappingURL=deadLetter.type.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=deadLetter.type.js.map
@@ -7,4 +7,3 @@
7
7
  export { DEFAULT_DEAD_LETTER_JOBS, createInMemoryDeadLetterStore, moveToDeadLetter, } from "./deadLetter.core.js";
8
8
  export type { InMemoryDeadLetterStoreOptions } from "./deadLetter.core.js";
9
9
  export type { DeadLetterJob, DeadLetterStore } from "./deadLetter.type.js";
10
- //# sourceMappingURL=index.d.ts.map
@@ -5,4 +5,3 @@
5
5
  * exceeded their retry attempts.
6
6
  */
7
7
  export { DEFAULT_DEAD_LETTER_JOBS, createInMemoryDeadLetterStore, moveToDeadLetter, } from "./deadLetter.core.js";
8
- //# sourceMappingURL=index.js.map
@@ -177,4 +177,3 @@ export declare class InMemoryQueue<TData = unknown> implements Queue<TData> {
177
177
  */
178
178
  private pruneSettled;
179
179
  }
180
- //# sourceMappingURL=inMemoryQueue.core.d.ts.map
@@ -489,6 +489,7 @@ export class InMemoryQueue {
489
489
  onJobReady: () => this.jobReady(),
490
490
  isDisposed: () => this.disposed,
491
491
  ...(this.options.logger ? { logger: this.options.logger } : {}),
492
+ ...(this.options.random ? { random: this.options.random } : {}),
492
493
  });
493
494
  }
494
495
  catch (error) {
@@ -631,4 +632,3 @@ export class InMemoryQueue {
631
632
  }
632
633
  }
633
634
  }
634
- //# sourceMappingURL=inMemoryQueue.core.js.map
@@ -4,4 +4,3 @@ import type { QueueOptions, Queue } from "../queue/queue.type.js";
4
4
  * Creates an InMemoryQueue.
5
5
  */
6
6
  export declare function createInMemoryQueue<TData>(name: QueueName, options?: QueueOptions): Queue<TData>;
7
- //# sourceMappingURL=inMemoryQueue.factory.d.ts.map
@@ -5,4 +5,3 @@ import { InMemoryQueue } from "./inMemoryQueue.core.js";
5
5
  export function createInMemoryQueue(name, options) {
6
6
  return new InMemoryQueue(name, options);
7
7
  }
8
- //# sourceMappingURL=inMemoryQueue.factory.js.map
@@ -46,6 +46,21 @@ export interface ProcessJobDependencies<TData> {
46
46
  * when the queue was configured without a logger.
47
47
  */
48
48
  readonly logger?: QueueLogger;
49
+ /** See `QueueOptions.random`. */
50
+ readonly random?: () => number;
51
+ }
52
+ /**
53
+ * How an attempt failed, beyond its message.
54
+ */
55
+ export interface JobFailureDetails {
56
+ /** The value the processor threw or the result it returned, if any. */
57
+ readonly error?: unknown;
58
+ /**
59
+ * The failure is permanent: dead-letter the job now rather than retry.
60
+ * Set when the thrown error was marked with `markUnrecoverable` or the
61
+ * returned `JobResult` carries `unrecoverable: true`.
62
+ */
63
+ readonly unrecoverable?: boolean;
49
64
  }
50
65
  /**
51
66
  * Process a single job with middleware, retry, and failure handling.
@@ -67,7 +82,8 @@ export declare function processJob<TData>(job: Job<TData>, processor: Processor<
67
82
  *
68
83
  * A failure that will be retried is reported as `job:retrying` and counted
69
84
  * separately; only a terminal failure increments `failedCount`, so the
70
- * counters describe outcomes rather than attempts.
85
+ * counters describe outcomes rather than attempts. An unrecoverable failure
86
+ * skips the remaining attempts and is dead-lettered with the processor's
87
+ * own error.
71
88
  */
72
- export declare function handleJobFailure<TData>(job: Job<TData>, errorMessage: string, deps: ProcessJobDependencies<TData>): Promise<void>;
73
- //# sourceMappingURL=inMemoryQueue.processing.d.ts.map
89
+ export declare function handleJobFailure<TData>(job: Job<TData>, errorMessage: string, deps: ProcessJobDependencies<TData>, details?: JobFailureDetails): Promise<void>;
@@ -7,6 +7,7 @@ import { calculateRetryDelay, resolveBackoff, shouldRetry, } from "../retryPolic
7
7
  import { moveToDeadLetter } from "../deadLetter/deadLetter.core.js";
8
8
  import { runWithContext } from "../contextCarrier/contextCarrier.core.js";
9
9
  import { DEFAULT_TIMEOUT_GRACE_MS, settleWithin, } from "./inMemoryQueue.settle.js";
10
+ import { isUnrecoverableJobError } from "../jobFailure/jobFailure.unrecoverable.js";
10
11
  import { JobMaxAttemptsError } from "@zudojs/errors";
11
12
  /**
12
13
  * Narrows a processor's return value to a `JobResult`.
@@ -77,7 +78,9 @@ export async function processJob(job, processor, options, deps) {
77
78
  },
78
79
  }));
79
80
  if (isJobResult(result) && !result.success) {
80
- await handleJobFailure(updatedJob, result.error ?? "Job failed", deps);
81
+ await handleJobFailure(updatedJob, result.error ?? "Job failed", deps, {
82
+ unrecoverable: result.unrecoverable === true,
83
+ });
81
84
  }
82
85
  else {
83
86
  const completedJob = updateJobState(updatedJob, JobStateEnum.COMPLETED, {
@@ -100,7 +103,10 @@ export async function processJob(job, processor, options, deps) {
100
103
  if (abortController.signal.aborted && !timedOut) {
101
104
  emitter.emit("job:cancelled", { job: updatedJob });
102
105
  }
103
- await handleJobFailure(updatedJob, errorMessage, deps);
106
+ await handleJobFailure(updatedJob, errorMessage, deps, {
107
+ error,
108
+ unrecoverable: isUnrecoverableJobError(error),
109
+ });
104
110
  }
105
111
  finally {
106
112
  counters.processedCount++;
@@ -111,22 +117,23 @@ export async function processJob(job, processor, options, deps) {
111
117
  *
112
118
  * A failure that will be retried is reported as `job:retrying` and counted
113
119
  * separately; only a terminal failure increments `failedCount`, so the
114
- * counters describe outcomes rather than attempts.
120
+ * counters describe outcomes rather than attempts. An unrecoverable failure
121
+ * skips the remaining attempts and is dead-lettered with the processor's
122
+ * own error.
115
123
  */
116
- export async function handleJobFailure(job, errorMessage, deps) {
124
+ export async function handleJobFailure(job, errorMessage, deps, details = {}) {
117
125
  const { jobs, emitter, counters, deadLetterStore } = deps;
126
+ const thrown = details.error instanceof Error ? details.error : new Error(errorMessage);
118
127
  const failedJob = updateJobState(job, JobStateEnum.FAILED, {
119
128
  error: errorMessage,
120
129
  failedAt: new Date().toISOString(),
121
130
  });
122
131
  jobs.set(job.id, failedJob);
123
- emitter.emit("job:failed", {
124
- job: failedJob,
125
- error: new Error(errorMessage),
126
- });
132
+ emitter.emit("job:failed", { job: failedJob, error: thrown });
127
133
  const incrementedJob = incrementJobAttempt(failedJob);
128
134
  jobs.set(job.id, incrementedJob);
129
- if (shouldRetry(incrementedJob.attempt, incrementedJob.maxAttempts)) {
135
+ if (details.unrecoverable !== true &&
136
+ shouldRetry(incrementedJob.attempt, incrementedJob.maxAttempts)) {
130
137
  const retryingJob = updateJobState(incrementedJob, JobStateEnum.RETRYING);
131
138
  jobs.set(job.id, retryingJob);
132
139
  counters.retriedCount++;
@@ -135,7 +142,7 @@ export async function handleJobFailure(job, errorMessage, deps) {
135
142
  attempt: incrementedJob.attempt,
136
143
  });
137
144
  const backoff = resolveBackoff(incrementedJob.backoff);
138
- const delay = calculateRetryDelay(incrementedJob.attempt, backoff);
145
+ const delay = calculateRetryDelay(incrementedJob.attempt, backoff, deps.random);
139
146
  const timer = setTimeout(() => {
140
147
  deps.deregisterRetryTimer?.(job.id);
141
148
  if (deps.isDisposed()) {
@@ -159,9 +166,11 @@ export async function handleJobFailure(job, errorMessage, deps) {
159
166
  }
160
167
  counters.failedCount++;
161
168
  counters.deadLetteredCount++;
162
- const maxAttemptsError = new JobMaxAttemptsError(job.id, incrementedJob.attempt, incrementedJob.maxAttempts, { queueName: job.queueName });
169
+ const deadLetterError = details.unrecoverable === true
170
+ ? thrown
171
+ : describeExhaustedAttempts(incrementedJob, errorMessage);
163
172
  try {
164
- await moveToDeadLetter(deadLetterStore, incrementedJob, maxAttemptsError, {
173
+ await moveToDeadLetter(deadLetterStore, incrementedJob, deadLetterError, {
165
174
  reason: errorMessage,
166
175
  });
167
176
  }
@@ -169,13 +178,26 @@ export async function handleJobFailure(job, errorMessage, deps) {
169
178
  // A dead letter store that rejects must not mask the original
170
179
  // failure or strand the job in `failed`.
171
180
  }
172
- const deadLetterJob = updateJobState(incrementedJob, JobStateEnum.DEAD_LETTER, { error: maxAttemptsError.message });
181
+ const deadLetterJob = updateJobState(incrementedJob, JobStateEnum.DEAD_LETTER, { error: deadLetterError.message });
173
182
  jobs.set(job.id, deadLetterJob);
174
183
  emitter.emit("job:dead-lettered", {
175
184
  job: deadLetterJob,
176
- error: maxAttemptsError,
185
+ error: deadLetterError,
177
186
  reason: errorMessage,
178
187
  });
179
188
  deps.onSettled?.(deadLetterJob);
180
189
  }
181
- //# sourceMappingURL=inMemoryQueue.processing.js.map
190
+ /**
191
+ * The error recorded for a job whose attempts ran out.
192
+ *
193
+ * The message names the last failure, so a dead-letter entry says what
194
+ * went wrong ("... Last error: card declined") rather than only that the
195
+ * attempts were used up; the bare reason is still on `reason`.
196
+ */
197
+ function describeExhaustedAttempts(job, lastError) {
198
+ const error = new JobMaxAttemptsError(job.id, job.attempt, job.maxAttempts, {
199
+ queueName: job.queueName,
200
+ });
201
+ error.message = `${error.message} Last error: ${lastError}`;
202
+ return error;
203
+ }
@@ -26,4 +26,3 @@ export declare function promoteDueScheduledJobs<TData>(jobs: Map<string, Job<TDa
26
26
  * timer belonging to a promoted job.
27
27
  */
28
28
  export declare function scheduleDelayedJobs<TData>(jobs: Map<string, Job<TData>>): void;
29
- //# sourceMappingURL=inMemoryQueue.scheduling.d.ts.map
@@ -72,4 +72,3 @@ export function promoteDueScheduledJobs(jobs, scheduledTimers) {
72
72
  export function scheduleDelayedJobs(jobs) {
73
73
  promoteDueScheduledJobs(jobs);
74
74
  }
75
- //# sourceMappingURL=inMemoryQueue.scheduling.js.map
@@ -21,4 +21,3 @@ export declare const DEFAULT_TIMEOUT_GRACE_MS = 5000;
21
21
  * @returns True when the processor settled within the grace period.
22
22
  */
23
23
  export declare function settleWithin(pending: Promise<unknown> | undefined, graceMs: number): Promise<boolean>;
24
- //# sourceMappingURL=inMemoryQueue.settle.d.ts.map
@@ -36,4 +36,3 @@ export async function settleWithin(pending, graceMs) {
36
36
  clearTimeout(timer);
37
37
  }
38
38
  }
39
- //# sourceMappingURL=inMemoryQueue.settle.js.map
@@ -5,4 +5,3 @@
5
5
  */
6
6
  export * from "./inMemoryQueue.core.js";
7
7
  export * from "./inMemoryQueue.factory.js";
8
- //# sourceMappingURL=index.d.ts.map
@@ -5,4 +5,3 @@
5
5
  */
6
6
  export * from "./inMemoryQueue.core.js";
7
7
  export * from "./inMemoryQueue.factory.js";
8
- //# sourceMappingURL=index.js.map
@@ -61,4 +61,3 @@ export declare class QueuePoller {
61
61
  private run;
62
62
  private nextDelay;
63
63
  }
64
- //# sourceMappingURL=inMemoryQueue.poller.d.ts.map
@@ -129,4 +129,3 @@ export class QueuePoller {
129
129
  return this.backoffMs;
130
130
  }
131
131
  }
132
- //# sourceMappingURL=inMemoryQueue.poller.js.map
@@ -19,8 +19,12 @@ export declare function runnableAt(job: Job<unknown>): number;
19
19
  * first ({@link runnableAt}); among equals, the one added first. The
20
20
  * incumbent is tracked by reference rather than by a sentinel priority, so
21
21
  * negative priorities are selectable like any other.
22
+ *
23
+ * One pass over the job map per selection. A job that cannot beat the
24
+ * incumbent on priority is skipped before its timestamps are consulted, and
25
+ * timestamps are parsed once per record, so the pass is a state check per
26
+ * job rather than a date parse per job.
22
27
  */
23
28
  export declare function selectNextJob<TData>(jobs: Iterable<Job<TData>>, now: number, predicate?: (job: Job<TData>) => boolean): Job<TData> | null;
24
29
  /** Whether any job a consumer can run is still owed a run. */
25
30
  export declare function hasPendingWork<TData>(jobs: Iterable<Job<TData>>, isConsumable: (job: Job<TData>) => boolean): boolean;
26
- //# sourceMappingURL=inMemoryQueue.select.d.ts.map
@@ -11,6 +11,27 @@ const PENDING_STATES = new Set([
11
11
  JobStateEnum.RETRYING,
12
12
  JobStateEnum.ACTIVE,
13
13
  ]);
14
+ /**
15
+ * Job records are immutable and replaced on every state change, so a parsed
16
+ * timestamp stays valid for as long as the record itself is reachable. The
17
+ * cache turns the per-poll cost of a waiting job from an ISO-8601 parse (or
18
+ * two, for a delayed job) into a map lookup.
19
+ */
20
+ const jobTimes = new WeakMap();
21
+ function timesOf(job) {
22
+ const cached = jobTimes.get(job);
23
+ if (cached !== undefined)
24
+ return cached;
25
+ const created = new Date(job.createdAt).getTime();
26
+ const scheduled = job.scheduledAt
27
+ ? new Date(job.scheduledAt).getTime()
28
+ : Number.NaN;
29
+ const times = Number.isNaN(scheduled)
30
+ ? { runnableAt: created, dueAt: Number.NEGATIVE_INFINITY }
31
+ : { runnableAt: Math.max(created, scheduled), dueAt: scheduled };
32
+ jobTimes.set(job, times);
33
+ return times;
34
+ }
14
35
  /**
15
36
  * When a job became runnable, in epoch milliseconds: its creation, or for a
16
37
  * delayed job the moment its delay elapsed.
@@ -19,11 +40,7 @@ const PENDING_STATES = new Set([
19
40
  * that had been ready and waiting while it was still delayed.
20
41
  */
21
42
  export function runnableAt(job) {
22
- const created = new Date(job.createdAt).getTime();
23
- if (!job.scheduledAt)
24
- return created;
25
- const scheduled = new Date(job.scheduledAt).getTime();
26
- return Number.isNaN(scheduled) ? created : Math.max(created, scheduled);
43
+ return timesOf(job).runnableAt;
27
44
  }
28
45
  /**
29
46
  * Selects the job that runs next among those that are waiting and due.
@@ -32,6 +49,11 @@ export function runnableAt(job) {
32
49
  * first ({@link runnableAt}); among equals, the one added first. The
33
50
  * incumbent is tracked by reference rather than by a sentinel priority, so
34
51
  * negative priorities are selectable like any other.
52
+ *
53
+ * One pass over the job map per selection. A job that cannot beat the
54
+ * incumbent on priority is skipped before its timestamps are consulted, and
55
+ * timestamps are parsed once per record, so the pass is a state check per
56
+ * job rather than a date parse per job.
35
57
  */
36
58
  export function selectNextJob(jobs, now, predicate) {
37
59
  let next = null;
@@ -39,16 +61,18 @@ export function selectNextJob(jobs, now, predicate) {
39
61
  for (const job of jobs) {
40
62
  if (job.state !== JobStateEnum.WAITING)
41
63
  continue;
42
- if (job.scheduledAt && new Date(job.scheduledAt).getTime() > now)
64
+ if (next !== null && job.priority < next.priority)
65
+ continue;
66
+ const times = timesOf(job);
67
+ if (times.dueAt > now)
43
68
  continue;
44
69
  if (predicate && !predicate(job))
45
70
  continue;
46
- const at = runnableAt(job);
47
71
  if (next === null ||
48
72
  job.priority > next.priority ||
49
- (job.priority === next.priority && at < nextAt)) {
73
+ times.runnableAt < nextAt) {
50
74
  next = job;
51
- nextAt = at;
75
+ nextAt = times.runnableAt;
52
76
  }
53
77
  }
54
78
  return next;
@@ -61,4 +85,3 @@ export function hasPendingWork(jobs, isConsumable) {
61
85
  }
62
86
  return false;
63
87
  }
64
- //# sourceMappingURL=inMemoryQueue.select.js.map
@@ -7,4 +7,3 @@
7
7
  */
8
8
  export * from "./inMemoryQueue.poller.js";
9
9
  export * from "./inMemoryQueue.select.js";
10
- //# sourceMappingURL=index.d.ts.map
@@ -7,4 +7,3 @@
7
7
  */
8
8
  export * from "./inMemoryQueue.poller.js";
9
9
  export * from "./inMemoryQueue.select.js";
10
- //# sourceMappingURL=index.js.map
package/dist/index.d.ts CHANGED
@@ -24,4 +24,5 @@ export * from "./worker/index.js";
24
24
  export * from "./inMemoryQueue/index.js";
25
25
  export * from "./deadLetter/index.js";
26
26
  export * from "./contextCarrier/index.js";
27
- //# sourceMappingURL=index.d.ts.map
27
+ export * from "./jobFailure/index.js";
28
+ export * from "./queueErrors/index.js";
package/dist/index.js CHANGED
@@ -41,4 +41,7 @@ export * from "./inMemoryQueue/index.js";
41
41
  export * from "./deadLetter/index.js";
42
42
  // Context propagation across the queue boundary
43
43
  export * from "./contextCarrier/index.js";
44
- //# sourceMappingURL=index.js.map
44
+ // Permanent failures
45
+ export * from "./jobFailure/index.js";
46
+ // Error classes (owned by @zudojs/errors)
47
+ export * from "./queueErrors/index.js";
@@ -6,4 +6,3 @@
6
6
  */
7
7
  export { createJob, isJob, updateJobState, incrementJobAttempt, } from "./job.core.js";
8
8
  export type { Job, JobInput } from "./job.type.js";
9
- //# sourceMappingURL=index.d.ts.map
package/dist/job/index.js CHANGED
@@ -5,4 +5,3 @@
5
5
  * and manipulating job instances.
6
6
  */
7
7
  export { createJob, isJob, updateJobState, incrementJobAttempt, } from "./job.core.js";
8
- //# sourceMappingURL=index.js.map
@@ -16,4 +16,3 @@ export declare function updateJobState<TData>(job: Job<TData>, state: JobState,
16
16
  * Increments a job's attempt counter.
17
17
  */
18
18
  export declare function incrementJobAttempt<TData>(job: Job<TData>): Job<TData>;
19
- //# sourceMappingURL=job.core.d.ts.map
@@ -79,4 +79,3 @@ export function incrementJobAttempt(job) {
79
79
  updatedAt: new Date().toISOString(),
80
80
  };
81
81
  }
82
- //# sourceMappingURL=job.core.js.map
@@ -67,4 +67,3 @@ export interface JobInput<TData = unknown> {
67
67
  /** Job options. */
68
68
  readonly options?: JobOptions;
69
69
  }
70
- //# sourceMappingURL=job.type.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=job.type.js.map
@@ -5,4 +5,3 @@
5
5
  */
6
6
  export { createJobContext, isJobContext } from "./jobContext.core.js";
7
7
  export type { JobContext } from "./jobContext.type.js";
8
- //# sourceMappingURL=index.d.ts.map
@@ -4,4 +4,3 @@
4
4
  * Provides context information to job processors during execution.
5
5
  */
6
6
  export { createJobContext, isJobContext } from "./jobContext.core.js";
7
- //# sourceMappingURL=index.js.map
@@ -14,4 +14,3 @@ export declare function createJobContext<TData>(job: Job<TData>, signal: AbortSi
14
14
  * Checks if a value is a valid JobContext.
15
15
  */
16
16
  export declare function isJobContext(value: unknown): value is JobContext;
17
- //# sourceMappingURL=jobContext.core.d.ts.map
@@ -34,4 +34,3 @@ export function isJobContext(value) {
34
34
  "updateProgress" in value &&
35
35
  "log" in value);
36
36
  }
37
- //# sourceMappingURL=jobContext.core.js.map
@@ -20,4 +20,3 @@ export interface JobContext<TData = unknown> {
20
20
  /** Log a message with job context. */
21
21
  log(message: string, data?: Record<string, unknown>): void;
22
22
  }
23
- //# sourceMappingURL=jobContext.type.d.ts.map
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=jobContext.type.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Permanent job failures: marking an error so the job is dead-lettered
3
+ * without further attempts.
4
+ *
5
+ * @module jobFailure
6
+ */
7
+ export { markUnrecoverable, createUnrecoverableJobError, isUnrecoverableJobError, } from "./jobFailure.unrecoverable.js";
8
+ export type { UnrecoverableJobErrorOptions } from "./jobFailure.unrecoverable.js";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Permanent job failures: marking an error so the job is dead-lettered
3
+ * without further attempts.
4
+ *
5
+ * @module jobFailure
6
+ */
7
+ export { markUnrecoverable, createUnrecoverableJobError, isUnrecoverableJobError, } from "./jobFailure.unrecoverable.js";
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Marking a job failure as permanent.
3
+ *
4
+ * By default every failure is retried until `attempts` is exhausted. Some
5
+ * failures are known to be permanent the moment they happen — a payload
6
+ * that fails validation, a record that no longer exists — and retrying them
7
+ * only burns the remaining attempts and delays the dead-letter entry an
8
+ * operator needs to see. A processor marks such a failure unrecoverable and
9
+ * the queue dead-letters the job at once.
10
+ *
11
+ * @module jobFailure/unrecoverable
12
+ */
13
+ import { JobError } from "@zudojs/errors";
14
+ /**
15
+ * Options for {@link createUnrecoverableJobError}.
16
+ */
17
+ export interface UnrecoverableJobErrorOptions {
18
+ readonly queueName?: string;
19
+ readonly jobId?: string;
20
+ readonly cause?: unknown;
21
+ }
22
+ /**
23
+ * Marks an error so the job that throws it is dead-lettered without
24
+ * further attempts. Works on any error, including one from a library the
25
+ * processor calls into.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * queue.process("charge", async (job) => {
30
+ * const card = await cards.find(job.data.cardId);
31
+ * if (!card) throw markUnrecoverable(new Error("card no longer exists"));
32
+ * });
33
+ * ```
34
+ */
35
+ export declare function markUnrecoverable<TError extends object>(error: TError): TError;
36
+ /**
37
+ * Creates a `JobError` already marked unrecoverable.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * throw createUnrecoverableJobError("payload failed validation", {
42
+ * jobId: job.id,
43
+ * queueName: job.queueName,
44
+ * });
45
+ * ```
46
+ */
47
+ export declare function createUnrecoverableJobError(message: string, options?: UnrecoverableJobErrorOptions): JobError;
48
+ /**
49
+ * Whether a thrown value was marked with {@link markUnrecoverable}.
50
+ */
51
+ export declare function isUnrecoverableJobError(value: unknown): boolean;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Marking a job failure as permanent.
3
+ *
4
+ * By default every failure is retried until `attempts` is exhausted. Some
5
+ * failures are known to be permanent the moment they happen — a payload
6
+ * that fails validation, a record that no longer exists — and retrying them
7
+ * only burns the remaining attempts and delays the dead-letter entry an
8
+ * operator needs to see. A processor marks such a failure unrecoverable and
9
+ * the queue dead-letters the job at once.
10
+ *
11
+ * @module jobFailure/unrecoverable
12
+ */
13
+ import { JobError } from "@zudojs/errors";
14
+ /** Brand carried by an error whose job must not be retried. */
15
+ const UNRECOVERABLE = Symbol.for("@zudojs/queue.unrecoverable");
16
+ /**
17
+ * Marks an error so the job that throws it is dead-lettered without
18
+ * further attempts. Works on any error, including one from a library the
19
+ * processor calls into.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * queue.process("charge", async (job) => {
24
+ * const card = await cards.find(job.data.cardId);
25
+ * if (!card) throw markUnrecoverable(new Error("card no longer exists"));
26
+ * });
27
+ * ```
28
+ */
29
+ export function markUnrecoverable(error) {
30
+ if (!Object.prototype.hasOwnProperty.call(error, UNRECOVERABLE)) {
31
+ Object.defineProperty(error, UNRECOVERABLE, {
32
+ value: true,
33
+ enumerable: false,
34
+ configurable: false,
35
+ writable: false,
36
+ });
37
+ }
38
+ return error;
39
+ }
40
+ /**
41
+ * Creates a `JobError` already marked unrecoverable.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * throw createUnrecoverableJobError("payload failed validation", {
46
+ * jobId: job.id,
47
+ * queueName: job.queueName,
48
+ * });
49
+ * ```
50
+ */
51
+ export function createUnrecoverableJobError(message, options = {}) {
52
+ return markUnrecoverable(new JobError(message, {
53
+ queueName: options.queueName,
54
+ jobId: options.jobId,
55
+ cause: options.cause,
56
+ }));
57
+ }
58
+ /**
59
+ * Whether a thrown value was marked with {@link markUnrecoverable}.
60
+ */
61
+ export function isUnrecoverableJobError(value) {
62
+ return (typeof value === "object" &&
63
+ value !== null &&
64
+ value[UNRECOVERABLE] === true);
65
+ }
@@ -5,4 +5,3 @@
5
5
  */
6
6
  export { DEFAULT_JOB_OPTIONS, mergeJobOptions } from "./jobOptions.core.js";
7
7
  export type { BackoffOptions, JobOptions } from "./jobOptions.type.js";
8
- //# sourceMappingURL=index.d.ts.map