@buenojs/bueno 0.8.3 → 0.8.5

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 (218) hide show
  1. package/README.md +136 -16
  2. package/dist/cli/{index.js → bin.js} +3036 -1421
  3. package/dist/container/index.js +250 -0
  4. package/dist/context/index.js +219 -0
  5. package/dist/database/index.js +493 -0
  6. package/dist/frontend/index.js +7697 -0
  7. package/dist/health/index.js +364 -0
  8. package/dist/i18n/index.js +345 -0
  9. package/dist/index.js +11043 -6482
  10. package/dist/jobs/index.js +819 -0
  11. package/dist/lock/index.js +367 -0
  12. package/dist/logger/index.js +281 -0
  13. package/dist/metrics/index.js +289 -0
  14. package/dist/middleware/index.js +77 -0
  15. package/dist/migrations/index.js +571 -0
  16. package/dist/modules/index.js +3346 -0
  17. package/dist/notification/index.js +484 -0
  18. package/dist/observability/index.js +331 -0
  19. package/dist/openapi/index.js +776 -0
  20. package/dist/orm/index.js +1356 -0
  21. package/dist/router/index.js +886 -0
  22. package/dist/rpc/index.js +691 -0
  23. package/dist/schema/index.js +400 -0
  24. package/dist/telemetry/index.js +595 -0
  25. package/dist/template/index.js +640 -0
  26. package/dist/templates/index.js +640 -0
  27. package/dist/testing/index.js +1111 -0
  28. package/dist/types/index.js +60 -0
  29. package/package.json +121 -27
  30. package/src/cache/index.ts +2 -1
  31. package/src/cli/bin.ts +2 -2
  32. package/src/cli/commands/build.ts +183 -165
  33. package/src/cli/commands/dev.ts +96 -89
  34. package/src/cli/commands/generate.ts +142 -111
  35. package/src/cli/commands/help.ts +20 -16
  36. package/src/cli/commands/index.ts +3 -6
  37. package/src/cli/commands/migration.ts +124 -105
  38. package/src/cli/commands/new.ts +392 -438
  39. package/src/cli/commands/start.ts +81 -79
  40. package/src/cli/core/args.ts +68 -50
  41. package/src/cli/core/console.ts +89 -95
  42. package/src/cli/core/index.ts +4 -4
  43. package/src/cli/core/prompt.ts +65 -62
  44. package/src/cli/core/spinner.ts +23 -20
  45. package/src/cli/index.ts +46 -38
  46. package/src/cli/templates/database/index.ts +61 -0
  47. package/src/cli/templates/database/mysql.ts +14 -0
  48. package/src/cli/templates/database/none.ts +16 -0
  49. package/src/cli/templates/database/postgresql.ts +14 -0
  50. package/src/cli/templates/database/sqlite.ts +14 -0
  51. package/src/cli/templates/deploy.ts +29 -26
  52. package/src/cli/templates/docker.ts +41 -30
  53. package/src/cli/templates/frontend/index.ts +63 -0
  54. package/src/cli/templates/frontend/none.ts +17 -0
  55. package/src/cli/templates/frontend/react.ts +140 -0
  56. package/src/cli/templates/frontend/solid.ts +134 -0
  57. package/src/cli/templates/frontend/svelte.ts +131 -0
  58. package/src/cli/templates/frontend/vue.ts +130 -0
  59. package/src/cli/templates/generators/index.ts +339 -0
  60. package/src/cli/templates/generators/types.ts +56 -0
  61. package/src/cli/templates/index.ts +35 -2
  62. package/src/cli/templates/project/api.ts +81 -0
  63. package/src/cli/templates/project/default.ts +140 -0
  64. package/src/cli/templates/project/fullstack.ts +111 -0
  65. package/src/cli/templates/project/index.ts +95 -0
  66. package/src/cli/templates/project/minimal.ts +45 -0
  67. package/src/cli/templates/project/types.ts +94 -0
  68. package/src/cli/templates/project/website.ts +263 -0
  69. package/src/cli/utils/fs.ts +55 -41
  70. package/src/cli/utils/index.ts +3 -2
  71. package/src/cli/utils/strings.ts +47 -33
  72. package/src/cli/utils/version.ts +47 -0
  73. package/src/config/env-validation.ts +100 -0
  74. package/src/config/env.ts +169 -41
  75. package/src/config/index.ts +28 -20
  76. package/src/config/loader.ts +25 -16
  77. package/src/config/merge.ts +21 -10
  78. package/src/config/types.ts +545 -25
  79. package/src/config/validation.ts +215 -7
  80. package/src/container/forward-ref.ts +22 -22
  81. package/src/container/index.ts +34 -12
  82. package/src/context/index.ts +11 -1
  83. package/src/database/index.ts +7 -190
  84. package/src/database/orm/builder.ts +457 -0
  85. package/src/database/orm/casts/index.ts +130 -0
  86. package/src/database/orm/casts/types.ts +25 -0
  87. package/src/database/orm/compiler.ts +304 -0
  88. package/src/database/orm/hooks/index.ts +114 -0
  89. package/src/database/orm/index.ts +61 -0
  90. package/src/database/orm/model-registry.ts +59 -0
  91. package/src/database/orm/model.ts +821 -0
  92. package/src/database/orm/relationships/base.ts +146 -0
  93. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  94. package/src/database/orm/relationships/belongs-to.ts +56 -0
  95. package/src/database/orm/relationships/has-many.ts +45 -0
  96. package/src/database/orm/relationships/has-one.ts +41 -0
  97. package/src/database/orm/relationships/index.ts +11 -0
  98. package/src/database/orm/scopes/index.ts +55 -0
  99. package/src/events/__tests__/event-system.test.ts +235 -0
  100. package/src/events/config.ts +238 -0
  101. package/src/events/example-usage.ts +185 -0
  102. package/src/events/index.ts +278 -0
  103. package/src/events/manager.ts +385 -0
  104. package/src/events/registry.ts +182 -0
  105. package/src/events/types.ts +124 -0
  106. package/src/frontend/api-routes.ts +65 -23
  107. package/src/frontend/bundler.ts +76 -34
  108. package/src/frontend/console-client.ts +2 -2
  109. package/src/frontend/console-stream.ts +94 -38
  110. package/src/frontend/dev-server.ts +94 -46
  111. package/src/frontend/file-router.ts +61 -19
  112. package/src/frontend/frameworks/index.ts +37 -10
  113. package/src/frontend/frameworks/react.ts +10 -8
  114. package/src/frontend/frameworks/solid.ts +11 -9
  115. package/src/frontend/frameworks/svelte.ts +15 -9
  116. package/src/frontend/frameworks/vue.ts +13 -11
  117. package/src/frontend/hmr-client.ts +12 -10
  118. package/src/frontend/hmr.ts +146 -103
  119. package/src/frontend/index.ts +14 -5
  120. package/src/frontend/islands.ts +41 -22
  121. package/src/frontend/isr.ts +59 -37
  122. package/src/frontend/layout.ts +36 -21
  123. package/src/frontend/ssr/react.ts +74 -27
  124. package/src/frontend/ssr/solid.ts +54 -20
  125. package/src/frontend/ssr/svelte.ts +48 -14
  126. package/src/frontend/ssr/vue.ts +50 -18
  127. package/src/frontend/ssr.ts +83 -39
  128. package/src/frontend/types.ts +91 -56
  129. package/src/health/index.ts +21 -9
  130. package/src/i18n/engine.ts +305 -0
  131. package/src/i18n/index.ts +38 -0
  132. package/src/i18n/loader.ts +218 -0
  133. package/src/i18n/middleware.ts +164 -0
  134. package/src/i18n/negotiator.ts +162 -0
  135. package/src/i18n/types.ts +158 -0
  136. package/src/index.ts +179 -27
  137. package/src/jobs/drivers/memory.ts +315 -0
  138. package/src/jobs/drivers/redis.ts +459 -0
  139. package/src/jobs/index.ts +30 -0
  140. package/src/jobs/queue.ts +281 -0
  141. package/src/jobs/types.ts +295 -0
  142. package/src/jobs/worker.ts +380 -0
  143. package/src/logger/index.ts +1 -3
  144. package/src/logger/transports/index.ts +62 -22
  145. package/src/metrics/index.ts +25 -16
  146. package/src/migrations/index.ts +9 -0
  147. package/src/modules/filters.ts +13 -17
  148. package/src/modules/guards.ts +49 -26
  149. package/src/modules/index.ts +409 -298
  150. package/src/modules/interceptors.ts +58 -20
  151. package/src/modules/lazy.ts +11 -19
  152. package/src/modules/lifecycle.ts +15 -7
  153. package/src/modules/metadata.ts +15 -5
  154. package/src/modules/pipes.ts +94 -72
  155. package/src/notification/channels/base.ts +68 -0
  156. package/src/notification/channels/email.ts +105 -0
  157. package/src/notification/channels/push.ts +104 -0
  158. package/src/notification/channels/sms.ts +105 -0
  159. package/src/notification/channels/whatsapp.ts +104 -0
  160. package/src/notification/index.ts +48 -0
  161. package/src/notification/service.ts +354 -0
  162. package/src/notification/types.ts +344 -0
  163. package/src/observability/__tests__/observability.test.ts +483 -0
  164. package/src/observability/breadcrumbs.ts +114 -0
  165. package/src/observability/index.ts +136 -0
  166. package/src/observability/interceptor.ts +85 -0
  167. package/src/observability/service.ts +303 -0
  168. package/src/observability/trace.ts +37 -0
  169. package/src/observability/types.ts +196 -0
  170. package/src/openapi/__tests__/decorators.test.ts +335 -0
  171. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  172. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  173. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  174. package/src/openapi/decorators.ts +328 -0
  175. package/src/openapi/document-builder.ts +274 -0
  176. package/src/openapi/index.ts +112 -0
  177. package/src/openapi/metadata.ts +112 -0
  178. package/src/openapi/route-scanner.ts +289 -0
  179. package/src/openapi/schema-generator.ts +256 -0
  180. package/src/openapi/swagger-module.ts +166 -0
  181. package/src/openapi/types.ts +398 -0
  182. package/src/orm/index.ts +10 -0
  183. package/src/rpc/index.ts +3 -1
  184. package/src/schema/index.ts +9 -0
  185. package/src/security/index.ts +15 -6
  186. package/src/ssg/index.ts +9 -8
  187. package/src/telemetry/index.ts +76 -22
  188. package/src/template/index.ts +7 -0
  189. package/src/templates/engine.ts +224 -0
  190. package/src/templates/index.ts +9 -0
  191. package/src/templates/loader.ts +331 -0
  192. package/src/templates/renderers/markdown.ts +212 -0
  193. package/src/templates/renderers/simple.ts +269 -0
  194. package/src/templates/types.ts +154 -0
  195. package/src/testing/index.ts +100 -27
  196. package/src/types/optional-deps.d.ts +347 -187
  197. package/src/validation/index.ts +92 -2
  198. package/src/validation/schemas.ts +536 -0
  199. package/tests/integration/fullstack.test.ts +4 -4
  200. package/tests/unit/database.test.ts +2 -72
  201. package/tests/unit/env-validation.test.ts +166 -0
  202. package/tests/unit/events.test.ts +910 -0
  203. package/tests/unit/i18n.test.ts +455 -0
  204. package/tests/unit/jobs.test.ts +493 -0
  205. package/tests/unit/notification.test.ts +988 -0
  206. package/tests/unit/observability.test.ts +453 -0
  207. package/tests/unit/orm/builder.test.ts +323 -0
  208. package/tests/unit/orm/casts.test.ts +179 -0
  209. package/tests/unit/orm/compiler.test.ts +220 -0
  210. package/tests/unit/orm/eager-loading.test.ts +285 -0
  211. package/tests/unit/orm/hooks.test.ts +191 -0
  212. package/tests/unit/orm/model.test.ts +373 -0
  213. package/tests/unit/orm/relationships.test.ts +303 -0
  214. package/tests/unit/orm/scopes.test.ts +74 -0
  215. package/tests/unit/templates-simple.test.ts +53 -0
  216. package/tests/unit/templates.test.ts +454 -0
  217. package/tests/unit/validation.test.ts +18 -24
  218. package/tsconfig.json +11 -3
@@ -0,0 +1,493 @@
1
+ /**
2
+ * Job Queue Unit Tests
3
+ *
4
+ * Tests for job queue, drivers, and core functionality
5
+ */
6
+
7
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
8
+ import {
9
+ JobQueue,
10
+ JobWorker,
11
+ MemoryJobQueueDriver,
12
+ type Job,
13
+ type JobHandler,
14
+ } from "../../src/jobs";
15
+
16
+ // ============= Test Fixtures =============
17
+
18
+ interface TestJobData {
19
+ message: string;
20
+ delay?: number;
21
+ }
22
+
23
+ // ============= Memory Driver Tests =============
24
+
25
+ describe("MemoryJobQueueDriver", () => {
26
+ let driver: MemoryJobQueueDriver;
27
+
28
+ beforeEach(async () => {
29
+ driver = new MemoryJobQueueDriver();
30
+ await driver.connect();
31
+ });
32
+
33
+ afterEach(async () => {
34
+ await driver.disconnect();
35
+ });
36
+
37
+ test("should enqueue a job", async () => {
38
+ const jobId = await driver.enqueue<TestJobData>("test", {
39
+ message: "hello",
40
+ });
41
+
42
+ expect(jobId).toBeDefined();
43
+ expect(typeof jobId).toBe("string");
44
+ });
45
+
46
+ test("should retrieve enqueued job", async () => {
47
+ const jobId = await driver.enqueue<TestJobData>("test", {
48
+ message: "hello",
49
+ });
50
+
51
+ const job = await driver.getJob(jobId);
52
+ expect(job).toBeDefined();
53
+ expect(job?.name).toBe("test");
54
+ expect(job?.data).toEqual({ message: "hello" });
55
+ expect(job?.status).toBe("pending");
56
+ });
57
+
58
+ test("should claim pending jobs", async () => {
59
+ await driver.enqueue<TestJobData>("test", { message: "job1" });
60
+ await driver.enqueue<TestJobData>("test", { message: "job2" });
61
+
62
+ const claimed = await driver.claim(10, 30000);
63
+ expect(claimed.length).toBe(2);
64
+ expect(claimed[0].status).toBe("processing");
65
+ expect(claimed[0].attempts).toBe(1);
66
+ });
67
+
68
+ test("should mark job as completed", async () => {
69
+ const jobId = await driver.enqueue<TestJobData>("test", {
70
+ message: "hello",
71
+ });
72
+
73
+ const claimed = await driver.claim(1, 30000);
74
+ await driver.complete(claimed[0].id);
75
+
76
+ const job = await driver.getJob(jobId);
77
+ expect(job?.status).toBe("completed");
78
+ expect(job?.completedAt).toBeDefined();
79
+ });
80
+
81
+ test("should mark job as failed", async () => {
82
+ const jobId = await driver.enqueue<TestJobData>("test", {
83
+ message: "hello",
84
+ });
85
+
86
+ const claimed = await driver.claim(1, 30000);
87
+ await driver.fail(claimed[0].id, "Test error", "Stack trace");
88
+
89
+ const job = await driver.getJob(jobId);
90
+ expect(job?.status).toBe("failed");
91
+ expect(job?.error).toBe("Test error");
92
+ expect(job?.stackTrace).toBe("Stack trace");
93
+ });
94
+
95
+ test("should schedule job retry", async () => {
96
+ const jobId = await driver.enqueue<TestJobData>("test", {
97
+ message: "hello",
98
+ });
99
+
100
+ const claimed = await driver.claim(1, 30000);
101
+ await driver.scheduleRetry(claimed[0].id, 1000, "Temp error");
102
+
103
+ const job = await driver.getJob(jobId);
104
+ expect(job?.status).toBe("delayed");
105
+ expect(job?.error).toBe("Temp error");
106
+ expect(job?.scheduledFor).toBeDefined();
107
+ });
108
+
109
+ test("should track metrics", async () => {
110
+ await driver.enqueue<TestJobData>("test", { message: "job1" });
111
+ await driver.enqueue<TestJobData>("test", { message: "job2" });
112
+
113
+ const metrics = await driver.getMetrics();
114
+ expect(metrics.enqueued).toBe(2);
115
+ expect(metrics.pending).toBe(2);
116
+ expect(metrics.processing).toBe(0);
117
+ expect(metrics.processed).toBe(0);
118
+ expect(metrics.failed).toBe(0);
119
+ });
120
+
121
+ test("should clear all jobs", async () => {
122
+ await driver.enqueue<TestJobData>("test", { message: "job1" });
123
+ await driver.enqueue<TestJobData>("test", { message: "job2" });
124
+
125
+ await driver.clear();
126
+
127
+ const metrics = await driver.getMetrics();
128
+ expect(metrics.enqueued).toBe(0);
129
+ expect(metrics.pending).toBe(0);
130
+ });
131
+
132
+ test("should handle delayed jobs", async () => {
133
+ const futureTime = new Date(Date.now() + 1000);
134
+ const jobId = await driver.enqueue<TestJobData>(
135
+ "test",
136
+ {
137
+ message: "delayed",
138
+ },
139
+ { delay: futureTime },
140
+ );
141
+
142
+ const claimed = await driver.claim(10, 30000);
143
+ expect(claimed.length).toBe(0);
144
+
145
+ const job = await driver.getJob(jobId);
146
+ expect(job?.status).toBe("delayed");
147
+ });
148
+
149
+ test("should respect batch size", async () => {
150
+ for (let i = 0; i < 5; i++) {
151
+ await driver.enqueue<TestJobData>("test", { message: `job${i}` });
152
+ }
153
+
154
+ const claimed = await driver.claim(3, 30000);
155
+ expect(claimed.length).toBe(3);
156
+ });
157
+
158
+ test("should calculate job duration", async () => {
159
+ const jobId = await driver.enqueue<TestJobData>("test", {
160
+ message: "hello",
161
+ });
162
+
163
+ const claimed = await driver.claim(1, 30000);
164
+
165
+ // Simulate some work
166
+ await new Promise((resolve) => setTimeout(resolve, 100));
167
+
168
+ await driver.complete(claimed[0].id);
169
+
170
+ const job = await driver.getJob(jobId);
171
+ expect(job?.duration).toBeDefined();
172
+ expect(job?.duration! >= 100).toBeTruthy();
173
+ });
174
+ });
175
+
176
+ // ============= JobQueue Tests =============
177
+
178
+ describe("JobQueue", () => {
179
+ let queue: JobQueue;
180
+
181
+ beforeEach(async () => {
182
+ queue = new JobQueue({ driver: "memory" });
183
+ await queue.init();
184
+ });
185
+
186
+ afterEach(async () => {
187
+ await queue.shutdown();
188
+ });
189
+
190
+ test("should create queue instance", () => {
191
+ expect(queue).toBeDefined();
192
+ });
193
+
194
+ test("should enqueue job and return ID", async () => {
195
+ const jobId = await queue.enqueue<TestJobData>("test", {
196
+ message: "hello",
197
+ });
198
+
199
+ expect(jobId).toBeDefined();
200
+ expect(typeof jobId).toBe("string");
201
+ });
202
+
203
+ test("should register job handler", () => {
204
+ const handler: JobHandler<TestJobData> = async () => {
205
+ // Handler implementation
206
+ };
207
+
208
+ queue.on("test", handler);
209
+
210
+ // Handler should be registered without error
211
+ expect(true).toBeTruthy();
212
+ });
213
+
214
+ test("should support wildcard handlers", () => {
215
+ const emailHandler: JobHandler = async () => {
216
+ // Email handler
217
+ };
218
+
219
+ queue.on("email.*", emailHandler);
220
+
221
+ // Should register without error
222
+ expect(true).toBeTruthy();
223
+ });
224
+
225
+ test("should retrieve job by ID", async () => {
226
+ const jobId = await queue.enqueue<TestJobData>("test", {
227
+ message: "hello",
228
+ });
229
+
230
+ const job = await queue.getJob(jobId);
231
+ expect(job).toBeDefined();
232
+ expect(job?.id).toBe(jobId);
233
+ expect(job?.name).toBe("test");
234
+ });
235
+
236
+ test("should get queue metrics", async () => {
237
+ await queue.enqueue<TestJobData>("test", { message: "job1" });
238
+ await queue.enqueue<TestJobData>("test", { message: "job2" });
239
+
240
+ const metrics = await queue.getMetrics();
241
+ expect(metrics.enqueued).toBe(2);
242
+ expect(metrics.pending).toBe(2);
243
+ });
244
+
245
+ test("should clear all jobs", async () => {
246
+ await queue.enqueue<TestJobData>("test", { message: "job1" });
247
+ await queue.enqueue<TestJobData>("test", { message: "job2" });
248
+
249
+ await queue.clear();
250
+
251
+ const metrics = await queue.getMetrics();
252
+ expect(metrics.enqueued).toBe(0);
253
+ });
254
+
255
+ test("should check connection status", async () => {
256
+ const isConnected = await queue.isConnected();
257
+ expect(isConnected).toBe(true);
258
+ });
259
+ });
260
+
261
+ // ============= JobWorker Tests =============
262
+
263
+ describe("JobWorker", () => {
264
+ let worker: JobWorker;
265
+
266
+ beforeEach(async () => {
267
+ worker = new JobWorker({ driver: "memory" });
268
+ await worker.init();
269
+ });
270
+
271
+ afterEach(async () => {
272
+ await worker.stop();
273
+ });
274
+
275
+ test("should create worker instance", () => {
276
+ expect(worker).toBeDefined();
277
+ });
278
+
279
+ test("should register job handler", () => {
280
+ const handler: JobHandler<TestJobData> = async () => {
281
+ // Handler implementation
282
+ };
283
+
284
+ worker.handle("test", handler);
285
+
286
+ // Handler should be registered without error
287
+ expect(true).toBeTruthy();
288
+ });
289
+
290
+ test("should support wildcard patterns", () => {
291
+ const handler: JobHandler = async () => {
292
+ // Handler implementation
293
+ };
294
+
295
+ worker.handle("email.*", handler);
296
+
297
+ // Should register without error
298
+ expect(true).toBeTruthy();
299
+ });
300
+
301
+ test("should unregister handler", () => {
302
+ const handler: JobHandler = async () => {
303
+ // Handler implementation
304
+ };
305
+
306
+ worker.handle("test", handler);
307
+ worker.unhandle("test");
308
+
309
+ // Handler should be unregistered without error
310
+ expect(true).toBeTruthy();
311
+ });
312
+
313
+ test("should track in-flight jobs", () => {
314
+ const count = worker.getInFlightCount();
315
+ expect(count).toBe(0);
316
+ });
317
+
318
+ test("should indicate active status", () => {
319
+ const isActive = worker.isActive();
320
+ expect(isActive).toBe(false);
321
+ });
322
+
323
+ test("should get metrics", async () => {
324
+ const metrics = await worker.getMetrics();
325
+ expect(metrics).toBeDefined();
326
+ expect(metrics.enqueued).toBe(0);
327
+ expect(metrics.processed).toBe(0);
328
+ });
329
+ });
330
+
331
+ // ============= Job Serialization Tests =============
332
+
333
+ describe("Job Serialization", () => {
334
+ let driver: MemoryJobQueueDriver;
335
+
336
+ beforeEach(async () => {
337
+ driver = new MemoryJobQueueDriver();
338
+ await driver.connect();
339
+ });
340
+
341
+ afterEach(async () => {
342
+ await driver.disconnect();
343
+ });
344
+
345
+ test("should serialize object data", async () => {
346
+ const data = { nested: { key: "value" }, array: [1, 2, 3] };
347
+ const jobId = await driver.enqueue("test", data);
348
+
349
+ const job = await driver.getJob(jobId);
350
+ expect(job?.data).toEqual(data);
351
+ });
352
+
353
+ test("should serialize null and undefined", async () => {
354
+ const data = { value: null };
355
+ const jobId = await driver.enqueue("test", data);
356
+
357
+ const job = await driver.getJob(jobId);
358
+ expect(job?.data.value).toBe(null);
359
+ });
360
+
361
+ test("should handle job metadata", async () => {
362
+ const jobId = await driver.enqueue(
363
+ "test",
364
+ { message: "hello" },
365
+ { metadata: { userId: 123, priority: "high" } },
366
+ );
367
+
368
+ const job = await driver.getJob(jobId);
369
+ expect(job?.metadata).toEqual({ userId: 123, priority: "high" });
370
+ });
371
+
372
+ test("should respect job options", async () => {
373
+ const jobId = await driver.enqueue(
374
+ "test",
375
+ { message: "hello" },
376
+ {
377
+ priority: 5,
378
+ maxRetries: 5,
379
+ },
380
+ );
381
+
382
+ const job = await driver.getJob(jobId);
383
+ expect(job?.priority).toBe(5);
384
+ expect(job?.maxRetries).toBe(5);
385
+ });
386
+ });
387
+
388
+ // ============= Job Status Transitions =============
389
+
390
+ describe("Job Status Transitions", () => {
391
+ let driver: MemoryJobQueueDriver;
392
+
393
+ beforeEach(async () => {
394
+ driver = new MemoryJobQueueDriver();
395
+ await driver.connect();
396
+ });
397
+
398
+ afterEach(async () => {
399
+ await driver.disconnect();
400
+ });
401
+
402
+ test("should transition from pending to processing", async () => {
403
+ const jobId = await driver.enqueue("test", { message: "hello" });
404
+ const job1 = await driver.getJob(jobId);
405
+ expect(job1?.status).toBe("pending");
406
+
407
+ await driver.claim(1, 30000);
408
+ const job2 = await driver.getJob(jobId);
409
+ expect(job2?.status).toBe("processing");
410
+ });
411
+
412
+ test("should transition from processing to completed", async () => {
413
+ const jobId = await driver.enqueue("test", { message: "hello" });
414
+
415
+ const claimed = await driver.claim(1, 30000);
416
+ await driver.complete(claimed[0].id);
417
+
418
+ const job = await driver.getJob(jobId);
419
+ expect(job?.status).toBe("completed");
420
+ });
421
+
422
+ test("should transition from processing to failed", async () => {
423
+ const jobId = await driver.enqueue("test", { message: "hello" });
424
+
425
+ const claimed = await driver.claim(1, 30000);
426
+ await driver.fail(claimed[0].id, "Error message");
427
+
428
+ const job = await driver.getJob(jobId);
429
+ expect(job?.status).toBe("failed");
430
+ });
431
+
432
+ test("should transition from processing to delayed (retry)", async () => {
433
+ const jobId = await driver.enqueue("test", { message: "hello" });
434
+
435
+ const claimed = await driver.claim(1, 30000);
436
+ await driver.scheduleRetry(claimed[0].id, 1000, "Error");
437
+
438
+ const job = await driver.getJob(jobId);
439
+ expect(job?.status).toBe("delayed");
440
+ });
441
+ });
442
+
443
+ // ============= Concurrent Operations Tests =============
444
+
445
+ describe("Concurrent Operations", () => {
446
+ let driver: MemoryJobQueueDriver;
447
+
448
+ beforeEach(async () => {
449
+ driver = new MemoryJobQueueDriver();
450
+ await driver.connect();
451
+ });
452
+
453
+ afterEach(async () => {
454
+ await driver.disconnect();
455
+ });
456
+
457
+ test("should handle concurrent enqueues", async () => {
458
+ const promises = Array.from({ length: 10 }, (_, i) =>
459
+ driver.enqueue("test", { message: `job${i}` }),
460
+ );
461
+
462
+ const jobIds = await Promise.all(promises);
463
+ expect(jobIds.length).toBe(10);
464
+ expect(new Set(jobIds).size).toBe(10); // All unique
465
+ });
466
+
467
+ test("should handle concurrent claims", async () => {
468
+ for (let i = 0; i < 20; i++) {
469
+ await driver.enqueue("test", { message: `job${i}` });
470
+ }
471
+
472
+ const [batch1, batch2] = await Promise.all([
473
+ driver.claim(5, 30000),
474
+ driver.claim(5, 30000),
475
+ ]);
476
+
477
+ expect(batch1.length).toBe(5);
478
+ expect(batch2.length).toBe(5);
479
+ expect(batch1[0].id).not.toBe(batch2[0].id);
480
+ });
481
+
482
+ test("should track concurrent metrics", async () => {
483
+ const promises = Array.from({ length: 15 }, (_, i) =>
484
+ driver.enqueue("test", { message: `job${i}` }),
485
+ );
486
+
487
+ await Promise.all(promises);
488
+
489
+ const metrics = await driver.getMetrics();
490
+ expect(metrics.enqueued).toBe(15);
491
+ expect(metrics.pending).toBe(15);
492
+ });
493
+ });