@quave/agenda 1.0.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 (216) hide show
  1. package/LICENSE.md +25 -0
  2. package/README.md +380 -0
  3. package/dist/Job.d.ts +190 -0
  4. package/dist/Job.js +689 -0
  5. package/dist/Job.js.map +1 -0
  6. package/dist/JobProcessingQueue.d.ts +47 -0
  7. package/dist/JobProcessingQueue.js +107 -0
  8. package/dist/JobProcessingQueue.js.map +1 -0
  9. package/dist/JobProcessor.d.ts +72 -0
  10. package/dist/JobProcessor.js +512 -0
  11. package/dist/JobProcessor.js.map +1 -0
  12. package/dist/backends/index.d.ts +18 -0
  13. package/dist/backends/index.js +20 -0
  14. package/dist/backends/index.js.map +1 -0
  15. package/dist/cjs/Job.d.ts +190 -0
  16. package/dist/cjs/Job.js +696 -0
  17. package/dist/cjs/Job.js.map +1 -0
  18. package/dist/cjs/JobProcessingQueue.d.ts +47 -0
  19. package/dist/cjs/JobProcessingQueue.js +111 -0
  20. package/dist/cjs/JobProcessingQueue.js.map +1 -0
  21. package/dist/cjs/JobProcessor.d.ts +72 -0
  22. package/dist/cjs/JobProcessor.js +519 -0
  23. package/dist/cjs/JobProcessor.js.map +1 -0
  24. package/dist/cjs/backends/index.d.ts +17 -0
  25. package/dist/cjs/backends/index.js +20 -0
  26. package/dist/cjs/backends/index.js.map +1 -0
  27. package/dist/cjs/decorators/Define.d.ts +33 -0
  28. package/dist/cjs/decorators/Define.js +47 -0
  29. package/dist/cjs/decorators/Define.js.map +1 -0
  30. package/dist/cjs/decorators/Every.d.ts +43 -0
  31. package/dist/cjs/decorators/Every.js +58 -0
  32. package/dist/cjs/decorators/Every.js.map +1 -0
  33. package/dist/cjs/decorators/JobsController.d.ts +43 -0
  34. package/dist/cjs/decorators/JobsController.js +40 -0
  35. package/dist/cjs/decorators/JobsController.js.map +1 -0
  36. package/dist/cjs/decorators/index.d.ts +41 -0
  37. package/dist/cjs/decorators/index.js +57 -0
  38. package/dist/cjs/decorators/index.js.map +1 -0
  39. package/dist/cjs/decorators/metadata.d.ts +50 -0
  40. package/dist/cjs/decorators/metadata.js +41 -0
  41. package/dist/cjs/decorators/metadata.js.map +1 -0
  42. package/dist/cjs/decorators/register.d.ts +52 -0
  43. package/dist/cjs/decorators/register.js +145 -0
  44. package/dist/cjs/decorators/register.js.map +1 -0
  45. package/dist/cjs/index.d.ts +498 -0
  46. package/dist/cjs/index.js +776 -0
  47. package/dist/cjs/index.js.map +1 -0
  48. package/dist/cjs/notifications/BaseNotificationChannel.d.ts +47 -0
  49. package/dist/cjs/notifications/BaseNotificationChannel.js +112 -0
  50. package/dist/cjs/notifications/BaseNotificationChannel.js.map +1 -0
  51. package/dist/cjs/notifications/InMemoryNotificationChannel.d.ts +13 -0
  52. package/dist/cjs/notifications/InMemoryNotificationChannel.js +37 -0
  53. package/dist/cjs/notifications/InMemoryNotificationChannel.js.map +1 -0
  54. package/dist/cjs/notifications/index.d.ts +2 -0
  55. package/dist/cjs/notifications/index.js +8 -0
  56. package/dist/cjs/notifications/index.js.map +1 -0
  57. package/dist/cjs/package.json +1 -0
  58. package/dist/cjs/types/AgendaBackend.d.ts +61 -0
  59. package/dist/cjs/types/AgendaBackend.js +3 -0
  60. package/dist/cjs/types/AgendaBackend.js.map +1 -0
  61. package/dist/cjs/types/AgendaConfig.d.ts +10 -0
  62. package/dist/cjs/types/AgendaConfig.js +3 -0
  63. package/dist/cjs/types/AgendaConfig.js.map +1 -0
  64. package/dist/cjs/types/AgendaStatus.d.ts +32 -0
  65. package/dist/cjs/types/AgendaStatus.js +3 -0
  66. package/dist/cjs/types/AgendaStatus.js.map +1 -0
  67. package/dist/cjs/types/DbOptions.d.ts +4 -0
  68. package/dist/cjs/types/DbOptions.js +3 -0
  69. package/dist/cjs/types/DbOptions.js.map +1 -0
  70. package/dist/cjs/types/DrainOptions.d.ts +37 -0
  71. package/dist/cjs/types/DrainOptions.js +3 -0
  72. package/dist/cjs/types/DrainOptions.js.map +1 -0
  73. package/dist/cjs/types/JobDefinition.d.ts +49 -0
  74. package/dist/cjs/types/JobDefinition.js +3 -0
  75. package/dist/cjs/types/JobDefinition.js.map +1 -0
  76. package/dist/cjs/types/JobLogger.d.ts +111 -0
  77. package/dist/cjs/types/JobLogger.js +3 -0
  78. package/dist/cjs/types/JobLogger.js.map +1 -0
  79. package/dist/cjs/types/JobParameters.d.ts +106 -0
  80. package/dist/cjs/types/JobParameters.js +21 -0
  81. package/dist/cjs/types/JobParameters.js.map +1 -0
  82. package/dist/cjs/types/JobQuery.d.ts +79 -0
  83. package/dist/cjs/types/JobQuery.js +36 -0
  84. package/dist/cjs/types/JobQuery.js.map +1 -0
  85. package/dist/cjs/types/JobRepository.d.ts +101 -0
  86. package/dist/cjs/types/JobRepository.js +3 -0
  87. package/dist/cjs/types/JobRepository.js.map +1 -0
  88. package/dist/cjs/types/NotificationChannel.d.ts +120 -0
  89. package/dist/cjs/types/NotificationChannel.js +3 -0
  90. package/dist/cjs/types/NotificationChannel.js.map +1 -0
  91. package/dist/cjs/utils/backoff.d.ts +152 -0
  92. package/dist/cjs/utils/backoff.js +175 -0
  93. package/dist/cjs/utils/backoff.js.map +1 -0
  94. package/dist/cjs/utils/dateConstraints.d.ts +48 -0
  95. package/dist/cjs/utils/dateConstraints.js +127 -0
  96. package/dist/cjs/utils/dateConstraints.js.map +1 -0
  97. package/dist/cjs/utils/isValidDate.d.ts +1 -0
  98. package/dist/cjs/utils/isValidDate.js +8 -0
  99. package/dist/cjs/utils/isValidDate.js.map +1 -0
  100. package/dist/cjs/utils/nextRunAt.d.ts +10 -0
  101. package/dist/cjs/utils/nextRunAt.js +119 -0
  102. package/dist/cjs/utils/nextRunAt.js.map +1 -0
  103. package/dist/cjs/utils/priority.d.ts +13 -0
  104. package/dist/cjs/utils/priority.js +23 -0
  105. package/dist/cjs/utils/priority.js.map +1 -0
  106. package/dist/cjs/utils/processEvery.d.ts +1 -0
  107. package/dist/cjs/utils/processEvery.js +13 -0
  108. package/dist/cjs/utils/processEvery.js.map +1 -0
  109. package/dist/cjs/utils/stack.d.ts +1 -0
  110. package/dist/cjs/utils/stack.js +21 -0
  111. package/dist/cjs/utils/stack.js.map +1 -0
  112. package/dist/cjs/version.d.ts +9 -0
  113. package/dist/cjs/version.js +13 -0
  114. package/dist/cjs/version.js.map +1 -0
  115. package/dist/decorators/Define.d.ts +33 -0
  116. package/dist/decorators/Define.js +44 -0
  117. package/dist/decorators/Define.js.map +1 -0
  118. package/dist/decorators/Every.d.ts +43 -0
  119. package/dist/decorators/Every.js +55 -0
  120. package/dist/decorators/Every.js.map +1 -0
  121. package/dist/decorators/JobsController.d.ts +43 -0
  122. package/dist/decorators/JobsController.js +37 -0
  123. package/dist/decorators/JobsController.js.map +1 -0
  124. package/dist/decorators/index.d.ts +41 -0
  125. package/dist/decorators/index.js +46 -0
  126. package/dist/decorators/index.js.map +1 -0
  127. package/dist/decorators/metadata.d.ts +50 -0
  128. package/dist/decorators/metadata.js +35 -0
  129. package/dist/decorators/metadata.js.map +1 -0
  130. package/dist/decorators/register.d.ts +52 -0
  131. package/dist/decorators/register.js +139 -0
  132. package/dist/decorators/register.js.map +1 -0
  133. package/dist/index.d.ts +498 -0
  134. package/dist/index.js +744 -0
  135. package/dist/index.js.map +1 -0
  136. package/dist/notifications/BaseNotificationChannel.d.ts +47 -0
  137. package/dist/notifications/BaseNotificationChannel.js +108 -0
  138. package/dist/notifications/BaseNotificationChannel.js.map +1 -0
  139. package/dist/notifications/InMemoryNotificationChannel.d.ts +13 -0
  140. package/dist/notifications/InMemoryNotificationChannel.js +33 -0
  141. package/dist/notifications/InMemoryNotificationChannel.js.map +1 -0
  142. package/dist/notifications/index.d.ts +2 -0
  143. package/dist/notifications/index.js +3 -0
  144. package/dist/notifications/index.js.map +1 -0
  145. package/dist/types/AgendaBackend.d.ts +61 -0
  146. package/dist/types/AgendaBackend.js +2 -0
  147. package/dist/types/AgendaBackend.js.map +1 -0
  148. package/dist/types/AgendaConfig.d.ts +10 -0
  149. package/dist/types/AgendaConfig.js +2 -0
  150. package/dist/types/AgendaConfig.js.map +1 -0
  151. package/dist/types/AgendaStatus.d.ts +32 -0
  152. package/dist/types/AgendaStatus.js +2 -0
  153. package/dist/types/AgendaStatus.js.map +1 -0
  154. package/dist/types/DbOptions.d.ts +4 -0
  155. package/dist/types/DbOptions.js +2 -0
  156. package/dist/types/DbOptions.js.map +1 -0
  157. package/dist/types/DrainOptions.d.ts +37 -0
  158. package/dist/types/DrainOptions.js +2 -0
  159. package/dist/types/DrainOptions.js.map +1 -0
  160. package/dist/types/JobDefinition.d.ts +49 -0
  161. package/dist/types/JobDefinition.js +2 -0
  162. package/dist/types/JobDefinition.js.map +1 -0
  163. package/dist/types/JobLogger.d.ts +111 -0
  164. package/dist/types/JobLogger.js +2 -0
  165. package/dist/types/JobLogger.js.map +1 -0
  166. package/dist/types/JobParameters.d.ts +106 -0
  167. package/dist/types/JobParameters.js +17 -0
  168. package/dist/types/JobParameters.js.map +1 -0
  169. package/dist/types/JobQuery.d.ts +79 -0
  170. package/dist/types/JobQuery.js +33 -0
  171. package/dist/types/JobQuery.js.map +1 -0
  172. package/dist/types/JobRepository.d.ts +101 -0
  173. package/dist/types/JobRepository.js +2 -0
  174. package/dist/types/JobRepository.js.map +1 -0
  175. package/dist/types/NotificationChannel.d.ts +120 -0
  176. package/dist/types/NotificationChannel.js +2 -0
  177. package/dist/types/NotificationChannel.js.map +1 -0
  178. package/dist/utils/backoff.d.ts +152 -0
  179. package/dist/utils/backoff.js +167 -0
  180. package/dist/utils/backoff.js.map +1 -0
  181. package/dist/utils/dateConstraints.d.ts +48 -0
  182. package/dist/utils/dateConstraints.js +117 -0
  183. package/dist/utils/dateConstraints.js.map +1 -0
  184. package/dist/utils/isValidDate.d.ts +1 -0
  185. package/dist/utils/isValidDate.js +5 -0
  186. package/dist/utils/isValidDate.js.map +1 -0
  187. package/dist/utils/nextRunAt.d.ts +10 -0
  188. package/dist/utils/nextRunAt.js +110 -0
  189. package/dist/utils/nextRunAt.js.map +1 -0
  190. package/dist/utils/priority.d.ts +13 -0
  191. package/dist/utils/priority.js +20 -0
  192. package/dist/utils/priority.js.map +1 -0
  193. package/dist/utils/processEvery.d.ts +1 -0
  194. package/dist/utils/processEvery.js +7 -0
  195. package/dist/utils/processEvery.js.map +1 -0
  196. package/dist/utils/stack.d.ts +1 -0
  197. package/dist/utils/stack.js +18 -0
  198. package/dist/utils/stack.js.map +1 -0
  199. package/dist/version.d.ts +9 -0
  200. package/dist/version.js +10 -0
  201. package/dist/version.js.map +1 -0
  202. package/package.json +79 -0
  203. package/test/fixtures/forkHelper.js +40 -0
  204. package/test/fixtures/someJobDefinition.ts +12 -0
  205. package/test/shared/agenda-test-suite.ts +1876 -0
  206. package/test/shared/backoff-test-suite.ts +408 -0
  207. package/test/shared/debounce-test-suite.ts +377 -0
  208. package/test/shared/full-test-suite.ts +161 -0
  209. package/test/shared/index.ts +50 -0
  210. package/test/shared/joblogger-test-suite.ts +305 -0
  211. package/test/shared/jobprocessor-test-suite.ts +266 -0
  212. package/test/shared/notification-channel-test-suite.ts +471 -0
  213. package/test/shared/removeoncomplete-test-suite.ts +228 -0
  214. package/test/shared/repository-test-suite.ts +708 -0
  215. package/test/shared/retry-test-suite.ts +174 -0
  216. package/test/shared/test-utils.ts +100 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Retry test factory
3
+ *
4
+ * This file exports a test factory for job retry tests.
5
+ *
6
+ * Usage:
7
+ * ```typescript
8
+ * import { retryTestSuite } from 'agenda/test/retry.test';
9
+ *
10
+ * retryTestSuite({
11
+ * name: 'Retry with MongoDB',
12
+ * createBackend: async () => new MongoBackend({ mongo: db }),
13
+ * cleanupBackend: async (backend) => await backend.disconnect(),
14
+ * clearJobs: async (backend) => await db.collection('agendaJobs').deleteMany({})
15
+ * });
16
+ * ```
17
+ */
18
+
19
+ import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
20
+ import type { AgendaBackend } from '../../src/index.js';
21
+ import { Agenda } from '../../src/index.js';
22
+
23
+ export interface RetryTestConfig {
24
+ /** Name for the test suite */
25
+ name: string;
26
+ /** Factory to create a fresh backend instance */
27
+ createBackend: () => Promise<AgendaBackend>;
28
+ /** Cleanup function called after tests */
29
+ cleanupBackend: (backend: AgendaBackend) => Promise<void>;
30
+ /** Clear all jobs between tests */
31
+ clearJobs: (backend: AgendaBackend) => Promise<void>;
32
+ }
33
+
34
+ /**
35
+ * Retry tests
36
+ */
37
+ export function retryTestSuite(config: RetryTestConfig): void {
38
+ describe(`${config.name} - Retry`, () => {
39
+ let backend: AgendaBackend;
40
+ let agenda: Agenda;
41
+
42
+ beforeAll(async () => {
43
+ backend = await config.createBackend();
44
+ });
45
+
46
+ afterAll(async () => {
47
+ await config.cleanupBackend(backend);
48
+ });
49
+
50
+ beforeEach(async () => {
51
+ await config.clearJobs(backend);
52
+ agenda = new Agenda({
53
+ backend,
54
+ processEvery: 100
55
+ });
56
+ await agenda.ready;
57
+ });
58
+
59
+ afterEach(async () => {
60
+ if (agenda) {
61
+ await agenda.stop();
62
+ }
63
+ await config.clearJobs(backend);
64
+ });
65
+
66
+ it('should retry a job - via attrs.nextRunAt', async () => {
67
+ let shouldFail = true;
68
+
69
+ agenda.define('a job', (_job, done) => {
70
+ if (shouldFail) {
71
+ shouldFail = false;
72
+ return done(new Error('test failure'));
73
+ }
74
+
75
+ done();
76
+ return undefined;
77
+ });
78
+
79
+ agenda.on('fail:a job', (_err, job) => {
80
+ // Do nothing as this is expected to fail.
81
+ job.attrs.nextRunAt = new Date();
82
+ });
83
+
84
+ const successPromise = new Promise(resolve => {
85
+ agenda.on('success:a job', resolve);
86
+ });
87
+
88
+ await agenda.now('a job');
89
+
90
+ await agenda.start();
91
+ await successPromise;
92
+ });
93
+
94
+ it('should retry a job - via schedule()', async () => {
95
+ let shouldFail = true;
96
+
97
+ agenda.define('a job', (_job, done) => {
98
+ if (shouldFail) {
99
+ shouldFail = false;
100
+ return done(new Error('test failure'));
101
+ }
102
+
103
+ done();
104
+ return undefined;
105
+ });
106
+
107
+ agenda.on('fail:a job', (_err, job) => {
108
+ // Do nothing as this is expected to fail.
109
+ job.schedule('now').save();
110
+ });
111
+
112
+ const successPromise = new Promise(resolve => {
113
+ agenda.on('success:a job', resolve);
114
+ });
115
+
116
+ await agenda.now('a job');
117
+
118
+ await agenda.start();
119
+ await successPromise;
120
+ });
121
+
122
+ it('should track failCount on retry - via attrs.nextRunAt', async () => {
123
+ let attempts = 0;
124
+
125
+ agenda.define('failing-job', async () => {
126
+ attempts++;
127
+ if (attempts < 3) {
128
+ throw new Error(`Attempt ${attempts} failed`);
129
+ }
130
+ });
131
+
132
+ agenda.on('fail:failing-job', (_err, job) => {
133
+ // Retry immediately
134
+ job.attrs.nextRunAt = new Date();
135
+ });
136
+
137
+ const successPromise = new Promise(resolve => {
138
+ agenda.on('success:failing-job', resolve);
139
+ });
140
+
141
+ await agenda.now('failing-job');
142
+ await agenda.start();
143
+ await successPromise;
144
+ expect(attempts).toBe(3);
145
+ });
146
+
147
+ it('should track failCount on retry - via schedule()', async () => {
148
+ let attempts = 0;
149
+
150
+ agenda.define('failing-job', async () => {
151
+ attempts++;
152
+ if (attempts < 3) {
153
+ throw new Error(`Attempt ${attempts} failed`);
154
+ }
155
+ });
156
+
157
+ agenda.on('fail:failing-job', (_err, job) => {
158
+ // Retry immediately
159
+ job.schedule('now').save();
160
+ });
161
+
162
+ const successPromise = new Promise(resolve => {
163
+ agenda.on('success:failing-job', resolve);
164
+ });
165
+
166
+ await agenda.now('failing-job');
167
+ await agenda.start();
168
+ await successPromise;
169
+
170
+ expect(attempts).toBe(3);
171
+ });
172
+ });
173
+ }
174
+
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Shared test utilities for Agenda test suites
3
+ */
4
+
5
+ import type { Agenda, AgendaEventName } from '../../src/index.js';
6
+
7
+ /**
8
+ * Promise-based delay utility
9
+ */
10
+ export const delay = (ms: number): Promise<void> =>
11
+ new Promise(resolve => setTimeout(resolve, ms));
12
+
13
+ /**
14
+ * Poll a predicate until it returns true or the timeout elapses.
15
+ * Resolves true as soon as the predicate holds, false on timeout.
16
+ */
17
+ export async function waitFor(
18
+ predicate: () => boolean | Promise<boolean>,
19
+ timeoutMs: number = 5000,
20
+ intervalMs: number = 25
21
+ ): Promise<boolean> {
22
+ const deadline = Date.now() + timeoutMs;
23
+ for (;;) {
24
+ if (await predicate()) {
25
+ return true;
26
+ }
27
+ if (Date.now() >= deadline) {
28
+ return false;
29
+ }
30
+ await delay(intervalMs);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Wait for a single event with timeout
36
+ */
37
+ export function waitForEvent(
38
+ agenda: Agenda,
39
+ eventName: AgendaEventName,
40
+ timeoutMs: number = 5000
41
+ ): Promise<unknown> {
42
+ return new Promise((resolve, reject) => {
43
+ const timeout = setTimeout(() => {
44
+ reject(new Error(`Timeout waiting for event: ${eventName}`));
45
+ }, timeoutMs);
46
+
47
+ agenda.once(eventName, (...args: unknown[]) => {
48
+ clearTimeout(timeout);
49
+ resolve(args[0]);
50
+ });
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Schedule a job with agenda.now() and wait for an event (race-condition safe).
56
+ * Sets up the event listener BEFORE scheduling the job to avoid missing fast events.
57
+ */
58
+ export async function runJobAndWait(
59
+ agenda: Agenda,
60
+ jobName: string,
61
+ eventName: AgendaEventName,
62
+ data?: Record<string, unknown>,
63
+ timeoutMs: number = 5000
64
+ ): Promise<void> {
65
+ const eventPromise = waitForEvent(agenda, eventName, timeoutMs);
66
+ await agenda.now(jobName, data);
67
+
68
+ await eventPromise;
69
+ await new Promise(resolve => process.nextTick(resolve));
70
+ }
71
+
72
+ /**
73
+ * Wait for multiple occurrences of an event with timeout
74
+ */
75
+ export function waitForEvents(
76
+ agenda: Agenda,
77
+ eventName: AgendaEventName,
78
+ count: number,
79
+ timeoutMs: number = 10000
80
+ ): Promise<unknown[]> {
81
+ return new Promise((resolve, reject) => {
82
+ const results: unknown[] = [];
83
+ const timeout = setTimeout(() => {
84
+ reject(new Error(`Timeout waiting for ${count} ${eventName} events, got ${results.length}`));
85
+ }, timeoutMs);
86
+
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ const handler = (...args: any[]) => {
89
+ results.push(args[0]);
90
+ if (results.length >= count) {
91
+ clearTimeout(timeout);
92
+ agenda.off(eventName, handler);
93
+ resolve(results);
94
+ }
95
+ };
96
+
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ agenda.on(eventName as any, handler);
99
+ });
100
+ }