@guren/server 0.2.0-alpha.7 → 1.0.0-rc.9

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 (54) hide show
  1. package/dist/Application-DtWDHXr1.d.ts +2110 -0
  2. package/dist/BroadcastManager-AkIWUGJo.d.ts +466 -0
  3. package/dist/CacheManager-BkvHEOZX.d.ts +244 -0
  4. package/dist/ConsoleKernel-CqCVrdZs.d.ts +207 -0
  5. package/dist/EventManager-CmIoLt7r.d.ts +207 -0
  6. package/dist/Gate-CNkBYf8m.d.ts +268 -0
  7. package/dist/HealthManager-DUyMIzsZ.d.ts +141 -0
  8. package/dist/I18nManager-Dtgzsf5n.d.ts +270 -0
  9. package/dist/LogManager-7mxnkaPM.d.ts +256 -0
  10. package/dist/MailManager-DpMvYiP9.d.ts +292 -0
  11. package/dist/Scheduler-BstvSca7.d.ts +469 -0
  12. package/dist/StorageManager-oZTHqaza.d.ts +337 -0
  13. package/dist/api-token-JOif2CtG.d.ts +1792 -0
  14. package/dist/app-key-CsBfRC_Q.d.ts +214 -0
  15. package/dist/auth/index.d.ts +418 -0
  16. package/dist/auth/index.js +6742 -0
  17. package/dist/authorization/index.d.ts +129 -0
  18. package/dist/authorization/index.js +621 -0
  19. package/dist/broadcasting/index.d.ts +233 -0
  20. package/dist/broadcasting/index.js +907 -0
  21. package/dist/cache/index.d.ts +233 -0
  22. package/dist/cache/index.js +817 -0
  23. package/dist/encryption/index.d.ts +222 -0
  24. package/dist/encryption/index.js +602 -0
  25. package/dist/events/index.d.ts +155 -0
  26. package/dist/events/index.js +330 -0
  27. package/dist/health/index.d.ts +185 -0
  28. package/dist/health/index.js +379 -0
  29. package/dist/i18n/index.d.ts +101 -0
  30. package/dist/i18n/index.js +597 -0
  31. package/dist/index-9_Jzj5jo.d.ts +7 -0
  32. package/dist/index.d.ts +2628 -619
  33. package/dist/index.js +22229 -3116
  34. package/dist/lambda/index.d.ts +156 -0
  35. package/dist/lambda/index.js +91 -0
  36. package/dist/logging/index.d.ts +50 -0
  37. package/dist/logging/index.js +557 -0
  38. package/dist/mail/index.d.ts +288 -0
  39. package/dist/mail/index.js +695 -0
  40. package/dist/mcp/index.d.ts +139 -0
  41. package/dist/mcp/index.js +382 -0
  42. package/dist/notifications/index.d.ts +271 -0
  43. package/dist/notifications/index.js +741 -0
  44. package/dist/queue/index.d.ts +423 -0
  45. package/dist/queue/index.js +958 -0
  46. package/dist/runtime/index.d.ts +93 -0
  47. package/dist/runtime/index.js +834 -0
  48. package/dist/scheduling/index.d.ts +41 -0
  49. package/dist/scheduling/index.js +836 -0
  50. package/dist/storage/index.d.ts +196 -0
  51. package/dist/storage/index.js +832 -0
  52. package/dist/vite/index.js +203 -3
  53. package/package.json +93 -6
  54. package/dist/chunk-FK2XQSBF.js +0 -160
@@ -0,0 +1,958 @@
1
+ // src/queue/Job.ts
2
+ import { randomBytes } from "crypto";
3
+
4
+ // src/container/Container.ts
5
+ var globalContainer = null;
6
+ function getContainer() {
7
+ if (!globalContainer) {
8
+ throw new Error("Container not initialized. Call setContainer() first.");
9
+ }
10
+ return globalContainer;
11
+ }
12
+
13
+ // src/queue/Job.ts
14
+ var globalDriver = null;
15
+ function setQueueDriver(driver) {
16
+ globalDriver = driver;
17
+ }
18
+ function getQueueDriver() {
19
+ return globalDriver;
20
+ }
21
+ function generateJobId() {
22
+ return randomBytes(16).toString("hex");
23
+ }
24
+ var Job = class {
25
+ /**
26
+ * The queue this job should be dispatched to.
27
+ * @default 'default'
28
+ */
29
+ static queue = "default";
30
+ /**
31
+ * Maximum number of times the job should be attempted.
32
+ * @default 3
33
+ */
34
+ static maxAttempts = 3;
35
+ /**
36
+ * Backoff strategy for retries.
37
+ * - 'exponential': 2^attempt * 1000ms (1s, 2s, 4s, 8s, ...)
38
+ * - 'linear': attempt * 1000ms (1s, 2s, 3s, 4s, ...)
39
+ * - number: fixed delay in milliseconds
40
+ * @default 'exponential'
41
+ */
42
+ static backoff = "exponential";
43
+ make(key) {
44
+ return getContainer().make(key);
45
+ }
46
+ /**
47
+ * Dispatch the job to the queue.
48
+ *
49
+ * @param payload - Job payload data
50
+ * @param options - Optional dispatch options
51
+ */
52
+ static async dispatch(payload, options = {}) {
53
+ const driver = globalDriver;
54
+ if (!driver) {
55
+ throw new Error("Queue driver not configured. Call setQueueDriver() first.");
56
+ }
57
+ const jobId = generateJobId();
58
+ const now = /* @__PURE__ */ new Date();
59
+ const delay = options.delay ?? 0;
60
+ const job = {
61
+ id: jobId,
62
+ name: this.name,
63
+ payload,
64
+ queue: options.queue ?? this.queue,
65
+ attempts: 0,
66
+ maxAttempts: options.maxAttempts ?? this.maxAttempts,
67
+ availableAt: new Date(now.getTime() + delay),
68
+ createdAt: now,
69
+ reservedAt: null
70
+ };
71
+ await driver.push(job);
72
+ return jobId;
73
+ }
74
+ /**
75
+ * Dispatch the job after a delay.
76
+ *
77
+ * @param delayMs - Delay in milliseconds
78
+ * @param payload - Job payload data
79
+ * @param options - Optional dispatch options (delay is overridden)
80
+ */
81
+ static async dispatchAfter(delayMs, payload, options = {}) {
82
+ return this.dispatch(payload, { ...options, delay: delayMs });
83
+ }
84
+ /**
85
+ * Calculate the retry delay based on backoff strategy.
86
+ */
87
+ static calculateRetryDelay(attempts) {
88
+ if (typeof this.backoff === "number") {
89
+ return this.backoff;
90
+ }
91
+ if (this.backoff === "linear") {
92
+ return attempts * 1e3;
93
+ }
94
+ return Math.pow(2, attempts) * 1e3;
95
+ }
96
+ };
97
+ var jobRegistry = /* @__PURE__ */ new Map();
98
+ function registerJob(jobClass) {
99
+ jobRegistry.set(jobClass.name, jobClass);
100
+ }
101
+ function getJob(name) {
102
+ return jobRegistry.get(name);
103
+ }
104
+ function getRegisteredJobs() {
105
+ return new Map(jobRegistry);
106
+ }
107
+ function clearJobRegistry() {
108
+ jobRegistry.clear();
109
+ }
110
+
111
+ // src/queue/drivers/MemoryDriver.ts
112
+ var MemoryDriver = class {
113
+ jobs = /* @__PURE__ */ new Map();
114
+ failedJobs = /* @__PURE__ */ new Map();
115
+ /**
116
+ * Push a job onto the queue.
117
+ */
118
+ async push(job) {
119
+ this.jobs.set(job.id, { ...job });
120
+ }
121
+ /**
122
+ * Pop the next available job from the queue.
123
+ */
124
+ async pop(queue) {
125
+ const now = /* @__PURE__ */ new Date();
126
+ for (const [id, job] of this.jobs) {
127
+ if (job.queue === queue && job.availableAt <= now && job.reservedAt === null) {
128
+ job.reservedAt = now;
129
+ return { ...job };
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+ /**
135
+ * Release a job back onto the queue.
136
+ */
137
+ async release(job, delayMs = 0) {
138
+ const existing = this.jobs.get(job.id);
139
+ if (existing) {
140
+ existing.reservedAt = null;
141
+ existing.availableAt = new Date(Date.now() + delayMs);
142
+ existing.attempts = job.attempts;
143
+ if (job.lastError) {
144
+ existing.lastError = job.lastError;
145
+ }
146
+ }
147
+ }
148
+ /**
149
+ * Delete a job from the queue.
150
+ */
151
+ async delete(jobId) {
152
+ this.jobs.delete(jobId);
153
+ }
154
+ /**
155
+ * Mark a job as failed.
156
+ */
157
+ async fail(job, error) {
158
+ const failedJob = {
159
+ ...job,
160
+ failedAt: /* @__PURE__ */ new Date(),
161
+ error: error.message,
162
+ stack: error.stack
163
+ };
164
+ this.failedJobs.set(job.id, failedJob);
165
+ this.jobs.delete(job.id);
166
+ }
167
+ /**
168
+ * Get the number of jobs in a queue.
169
+ */
170
+ async size(queue) {
171
+ let count = 0;
172
+ for (const job of this.jobs.values()) {
173
+ if (job.queue === queue && job.reservedAt === null) {
174
+ count++;
175
+ }
176
+ }
177
+ return count;
178
+ }
179
+ /**
180
+ * Get failed jobs.
181
+ */
182
+ async getFailedJobs(queue) {
183
+ const jobs = [];
184
+ for (const job of this.failedJobs.values()) {
185
+ if (!queue || job.queue === queue) {
186
+ jobs.push({ ...job });
187
+ }
188
+ }
189
+ return jobs.sort((a, b) => b.failedAt.getTime() - a.failedAt.getTime());
190
+ }
191
+ /**
192
+ * Retry a failed job.
193
+ */
194
+ async retryFailedJob(jobId) {
195
+ const failedJob = this.failedJobs.get(jobId);
196
+ if (!failedJob) {
197
+ throw new Error(`Failed job not found: ${jobId}`);
198
+ }
199
+ const job = {
200
+ id: failedJob.id,
201
+ name: failedJob.name,
202
+ payload: failedJob.payload,
203
+ queue: failedJob.queue,
204
+ attempts: 0,
205
+ maxAttempts: failedJob.maxAttempts,
206
+ availableAt: /* @__PURE__ */ new Date(),
207
+ createdAt: /* @__PURE__ */ new Date(),
208
+ reservedAt: null
209
+ };
210
+ this.jobs.set(job.id, job);
211
+ this.failedJobs.delete(jobId);
212
+ }
213
+ /**
214
+ * Delete a failed job.
215
+ */
216
+ async deleteFailedJob(jobId) {
217
+ this.failedJobs.delete(jobId);
218
+ }
219
+ /**
220
+ * Clear all jobs (for testing).
221
+ */
222
+ async clear() {
223
+ this.jobs.clear();
224
+ this.failedJobs.clear();
225
+ }
226
+ /**
227
+ * Get all pending jobs (for testing/debugging).
228
+ */
229
+ getPendingJobs(queue) {
230
+ const jobs = [];
231
+ for (const job of this.jobs.values()) {
232
+ if (!queue || job.queue === queue) {
233
+ jobs.push({ ...job });
234
+ }
235
+ }
236
+ return jobs;
237
+ }
238
+ };
239
+
240
+ // src/queue/drivers/RedisDriver.ts
241
+ var RedisDriver = class {
242
+ constructor(redis, options = {}) {
243
+ this.redis = redis;
244
+ this.prefix = options.prefix ?? "queue:";
245
+ this.visibilityTimeout = options.visibilityTimeout ?? 6e4;
246
+ }
247
+ prefix;
248
+ visibilityTimeout;
249
+ pendingKey(queue) {
250
+ return `${this.prefix}${queue}:pending`;
251
+ }
252
+ reservedKey(queue) {
253
+ return `${this.prefix}${queue}:reserved`;
254
+ }
255
+ failedKey(queue) {
256
+ return `${this.prefix}${queue}:failed`;
257
+ }
258
+ jobKey(id) {
259
+ return `${this.prefix}job:${id}`;
260
+ }
261
+ /**
262
+ * Push a job onto the queue.
263
+ */
264
+ async push(job) {
265
+ const jobData = {
266
+ id: job.id,
267
+ name: job.name,
268
+ payload: JSON.stringify(job.payload),
269
+ queue: job.queue,
270
+ attempts: String(job.attempts),
271
+ maxAttempts: String(job.maxAttempts),
272
+ availableAt: job.availableAt.toISOString(),
273
+ createdAt: job.createdAt.toISOString()
274
+ };
275
+ const pipeline = this.redis.pipeline();
276
+ pipeline.hset(this.jobKey(job.id), jobData);
277
+ pipeline.zadd(this.pendingKey(job.queue), job.availableAt.getTime(), job.id);
278
+ await pipeline.exec();
279
+ }
280
+ /**
281
+ * Pop the next available job from the queue.
282
+ */
283
+ async pop(queue) {
284
+ const now = Date.now();
285
+ await this.releaseTimedOutJobs(queue);
286
+ const jobIds = await this.redis.zrangebyscore(
287
+ this.pendingKey(queue),
288
+ "-inf",
289
+ now,
290
+ "LIMIT",
291
+ 0,
292
+ 1
293
+ );
294
+ if (jobIds.length === 0) {
295
+ return null;
296
+ }
297
+ const jobId = jobIds[0];
298
+ const removed = await this.redis.zrem(this.pendingKey(queue), jobId);
299
+ if (removed === 0) {
300
+ return null;
301
+ }
302
+ const timeout = now + this.visibilityTimeout;
303
+ await this.redis.zadd(this.reservedKey(queue), timeout, jobId);
304
+ await this.redis.hset(this.jobKey(jobId), "reservedAt", (/* @__PURE__ */ new Date()).toISOString());
305
+ const jobData = await this.redis.hgetall(this.jobKey(jobId));
306
+ if (!jobData || !jobData.id) {
307
+ await this.redis.zrem(this.reservedKey(queue), jobId);
308
+ return null;
309
+ }
310
+ return this.parseJobData(jobData);
311
+ }
312
+ /**
313
+ * Release jobs that have exceeded visibility timeout.
314
+ */
315
+ async releaseTimedOutJobs(queue) {
316
+ const now = Date.now();
317
+ const timedOutIds = await this.redis.zrangebyscore(
318
+ this.reservedKey(queue),
319
+ "-inf",
320
+ now
321
+ );
322
+ for (const jobId of timedOutIds) {
323
+ const removed = await this.redis.zrem(this.reservedKey(queue), jobId);
324
+ if (removed > 0) {
325
+ await this.redis.zadd(this.pendingKey(queue), now, jobId);
326
+ await this.redis.hdel(this.jobKey(jobId), "reservedAt");
327
+ }
328
+ }
329
+ }
330
+ /**
331
+ * Release a job back onto the queue.
332
+ */
333
+ async release(job, delayMs = 0) {
334
+ const availableAt = Date.now() + delayMs;
335
+ await this.redis.zrem(this.reservedKey(job.queue), job.id);
336
+ const updates = {
337
+ attempts: String(job.attempts),
338
+ availableAt: new Date(availableAt).toISOString()
339
+ };
340
+ if (job.lastError) {
341
+ updates.lastError = job.lastError;
342
+ }
343
+ await this.redis.hset(this.jobKey(job.id), updates);
344
+ await this.redis.hdel(this.jobKey(job.id), "reservedAt");
345
+ await this.redis.zadd(this.pendingKey(job.queue), availableAt, job.id);
346
+ }
347
+ /**
348
+ * Delete a job from the queue.
349
+ */
350
+ async delete(jobId) {
351
+ const jobData = await this.redis.hgetall(this.jobKey(jobId));
352
+ if (jobData && jobData.queue) {
353
+ const pipeline = this.redis.pipeline();
354
+ pipeline.zrem(this.pendingKey(jobData.queue), jobId);
355
+ pipeline.zrem(this.reservedKey(jobData.queue), jobId);
356
+ pipeline.del(this.jobKey(jobId));
357
+ await pipeline.exec();
358
+ } else {
359
+ await this.redis.del(this.jobKey(jobId));
360
+ }
361
+ }
362
+ /**
363
+ * Mark a job as failed.
364
+ */
365
+ async fail(job, error) {
366
+ const failedData = {
367
+ failedAt: (/* @__PURE__ */ new Date()).toISOString(),
368
+ error: error.message,
369
+ stack: error.stack ?? ""
370
+ };
371
+ await this.redis.hset(this.jobKey(job.id), failedData);
372
+ const pipeline = this.redis.pipeline();
373
+ pipeline.zrem(this.reservedKey(job.queue), job.id);
374
+ pipeline.lpush(this.failedKey(job.queue), job.id);
375
+ await pipeline.exec();
376
+ }
377
+ /**
378
+ * Get the number of jobs in a queue.
379
+ */
380
+ async size(queue) {
381
+ return this.redis.zcard(this.pendingKey(queue));
382
+ }
383
+ /**
384
+ * Get failed jobs.
385
+ */
386
+ async getFailedJobs(queue) {
387
+ const jobs = [];
388
+ if (queue) {
389
+ jobs.push(...await this.getFailedJobsForQueue(queue));
390
+ } else {
391
+ const pattern = `${this.prefix}*:failed`;
392
+ let cursor = "0";
393
+ const queueKeys = [];
394
+ do {
395
+ const [newCursor, keys] = await this.redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
396
+ cursor = newCursor;
397
+ queueKeys.push(...keys);
398
+ } while (cursor !== "0");
399
+ for (const key of queueKeys) {
400
+ const queueName = key.replace(this.prefix, "").replace(":failed", "");
401
+ jobs.push(...await this.getFailedJobsForQueue(queueName));
402
+ }
403
+ }
404
+ return jobs.sort((a, b) => b.failedAt.getTime() - a.failedAt.getTime());
405
+ }
406
+ async getFailedJobsForQueue(queue) {
407
+ const jobIds = await this.redis.lrange(this.failedKey(queue), 0, -1);
408
+ const jobs = [];
409
+ for (const jobId of jobIds) {
410
+ const jobData = await this.redis.hgetall(this.jobKey(jobId));
411
+ if (jobData && jobData.id) {
412
+ const job = this.parseJobData(jobData);
413
+ jobs.push({
414
+ ...job,
415
+ failedAt: new Date(jobData.failedAt || Date.now()),
416
+ error: jobData.error || "Unknown error",
417
+ stack: jobData.stack
418
+ });
419
+ }
420
+ }
421
+ return jobs;
422
+ }
423
+ /**
424
+ * Retry a failed job.
425
+ */
426
+ async retryFailedJob(jobId) {
427
+ const jobData = await this.redis.hgetall(this.jobKey(jobId));
428
+ if (!jobData || !jobData.queue) {
429
+ throw new Error(`Failed job not found: ${jobId}`);
430
+ }
431
+ const queue = jobData.queue;
432
+ await this.redis.lrem(this.failedKey(queue), 1, jobId);
433
+ const now = /* @__PURE__ */ new Date();
434
+ await this.redis.hset(this.jobKey(jobId), {
435
+ attempts: "0",
436
+ availableAt: now.toISOString(),
437
+ createdAt: now.toISOString()
438
+ });
439
+ await this.redis.hdel(this.jobKey(jobId), "reservedAt", "failedAt", "error", "stack", "lastError");
440
+ await this.redis.zadd(this.pendingKey(queue), now.getTime(), jobId);
441
+ }
442
+ /**
443
+ * Delete a failed job.
444
+ */
445
+ async deleteFailedJob(jobId) {
446
+ const jobData = await this.redis.hgetall(this.jobKey(jobId));
447
+ if (jobData && jobData.queue) {
448
+ await this.redis.lrem(this.failedKey(jobData.queue), 1, jobId);
449
+ }
450
+ await this.redis.del(this.jobKey(jobId));
451
+ }
452
+ /**
453
+ * Clear all jobs (for testing).
454
+ */
455
+ async clear() {
456
+ const pattern = this.prefix + "*";
457
+ let cursor = "0";
458
+ const keys = [];
459
+ do {
460
+ const [newCursor, foundKeys] = await this.redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
461
+ cursor = newCursor;
462
+ keys.push(...foundKeys);
463
+ } while (cursor !== "0");
464
+ if (keys.length > 0) {
465
+ await this.redis.del(...keys);
466
+ }
467
+ }
468
+ /**
469
+ * Parse job data from Redis hash.
470
+ */
471
+ parseJobData(data) {
472
+ return {
473
+ id: data.id,
474
+ name: data.name,
475
+ payload: JSON.parse(data.payload || "{}"),
476
+ queue: data.queue,
477
+ attempts: parseInt(data.attempts || "0", 10),
478
+ maxAttempts: parseInt(data.maxAttempts || "3", 10),
479
+ availableAt: new Date(data.availableAt),
480
+ createdAt: new Date(data.createdAt),
481
+ reservedAt: data.reservedAt ? new Date(data.reservedAt) : null,
482
+ lastError: data.lastError
483
+ };
484
+ }
485
+ };
486
+
487
+ // src/queue/drivers/SqsDriver.ts
488
+ function createSqsAdapter(client) {
489
+ return {
490
+ async sendMessage(params) {
491
+ const { SendMessageCommand } = await importSqs();
492
+ const input = {
493
+ QueueUrl: params.queueUrl,
494
+ MessageBody: params.messageBody,
495
+ DelaySeconds: params.delaySeconds
496
+ };
497
+ if (params.messageGroupId) {
498
+ input.MessageGroupId = params.messageGroupId;
499
+ input.MessageDeduplicationId = params.messageDeduplicationId;
500
+ }
501
+ await client.send(new SendMessageCommand(input));
502
+ },
503
+ async receiveMessage(params) {
504
+ const { ReceiveMessageCommand } = await importSqs();
505
+ const result = await client.send(
506
+ new ReceiveMessageCommand({
507
+ QueueUrl: params.queueUrl,
508
+ MaxNumberOfMessages: 1,
509
+ WaitTimeSeconds: params.waitTimeSeconds ?? 5
510
+ })
511
+ );
512
+ const msg = result.Messages?.[0];
513
+ if (!msg?.Body || !msg.ReceiptHandle) return null;
514
+ return { body: msg.Body, receiptHandle: msg.ReceiptHandle };
515
+ },
516
+ async changeMessageVisibility(params) {
517
+ const { ChangeMessageVisibilityCommand } = await importSqs();
518
+ await client.send(
519
+ new ChangeMessageVisibilityCommand({
520
+ QueueUrl: params.queueUrl,
521
+ ReceiptHandle: params.receiptHandle,
522
+ VisibilityTimeout: params.visibilityTimeout
523
+ })
524
+ );
525
+ },
526
+ async getApproximateMessageCount(queueUrl) {
527
+ const { GetQueueAttributesCommand } = await importSqs();
528
+ const result = await client.send(
529
+ new GetQueueAttributesCommand({
530
+ QueueUrl: queueUrl,
531
+ AttributeNames: ["ApproximateNumberOfMessages"]
532
+ })
533
+ );
534
+ return parseInt(result.Attributes?.ApproximateNumberOfMessages ?? "0", 10);
535
+ }
536
+ };
537
+ }
538
+ var SQS_MODULE = "@aws-sdk/client-sqs";
539
+ async function importSqs() {
540
+ try {
541
+ return await import(SQS_MODULE);
542
+ } catch {
543
+ throw new Error(
544
+ `Missing optional dependency "${SQS_MODULE}". Install @aws-sdk/client-sqs to use the SQS driver or createSqsAdapter.`
545
+ );
546
+ }
547
+ }
548
+ var SqsDriver = class {
549
+ adapter;
550
+ options;
551
+ failedJobs = /* @__PURE__ */ new Map();
552
+ receiptHandles = /* @__PURE__ */ new Map();
553
+ constructor(adapter, options) {
554
+ this.adapter = adapter;
555
+ this.options = options;
556
+ }
557
+ async push(job) {
558
+ const delaySeconds = Math.min(
559
+ 900,
560
+ Math.max(0, Math.floor((job.availableAt.getTime() - Date.now()) / 1e3))
561
+ );
562
+ await this.adapter.sendMessage({
563
+ queueUrl: this.resolveQueueUrl(job.queue),
564
+ messageBody: JSON.stringify(job),
565
+ delaySeconds,
566
+ messageGroupId: this.options.messageGroupId,
567
+ messageDeduplicationId: this.options.messageGroupId ? job.id : void 0
568
+ });
569
+ }
570
+ async pop(queue) {
571
+ const result = await this.adapter.receiveMessage({
572
+ queueUrl: this.resolveQueueUrl(queue)
573
+ });
574
+ if (!result) return null;
575
+ const job = deserializeJob(result.body);
576
+ this.receiptHandles.set(job.id, result.receiptHandle);
577
+ job.reservedAt = /* @__PURE__ */ new Date();
578
+ return job;
579
+ }
580
+ async release(job, delayMs = 0) {
581
+ const receiptHandle = this.receiptHandles.get(job.id);
582
+ if (receiptHandle) {
583
+ await this.adapter.changeMessageVisibility({
584
+ queueUrl: this.resolveQueueUrl(job.queue),
585
+ receiptHandle,
586
+ visibilityTimeout: Math.ceil(delayMs / 1e3)
587
+ });
588
+ this.receiptHandles.delete(job.id);
589
+ } else {
590
+ job.reservedAt = null;
591
+ job.availableAt = new Date(Date.now() + delayMs);
592
+ await this.push(job);
593
+ }
594
+ }
595
+ async delete(jobId) {
596
+ this.receiptHandles.delete(jobId);
597
+ }
598
+ async fail(job, error) {
599
+ const failedJob = {
600
+ ...job,
601
+ failedAt: /* @__PURE__ */ new Date(),
602
+ error: error.message,
603
+ stack: error.stack
604
+ };
605
+ this.failedJobs.set(job.id, failedJob);
606
+ this.receiptHandles.delete(job.id);
607
+ }
608
+ async size(queue) {
609
+ return this.adapter.getApproximateMessageCount(this.resolveQueueUrl(queue));
610
+ }
611
+ async getFailedJobs(queue) {
612
+ const jobs = [];
613
+ for (const job of this.failedJobs.values()) {
614
+ if (!queue || job.queue === queue) {
615
+ jobs.push({ ...job });
616
+ }
617
+ }
618
+ return jobs.sort((a, b) => b.failedAt.getTime() - a.failedAt.getTime());
619
+ }
620
+ async retryFailedJob(jobId) {
621
+ const failedJob = this.failedJobs.get(jobId);
622
+ if (!failedJob) {
623
+ throw new Error(`Failed job not found: ${jobId}`);
624
+ }
625
+ const job = {
626
+ id: failedJob.id,
627
+ name: failedJob.name,
628
+ payload: failedJob.payload,
629
+ queue: failedJob.queue,
630
+ attempts: 0,
631
+ maxAttempts: failedJob.maxAttempts,
632
+ availableAt: /* @__PURE__ */ new Date(),
633
+ createdAt: /* @__PURE__ */ new Date(),
634
+ reservedAt: null
635
+ };
636
+ await this.push(job);
637
+ this.failedJobs.delete(jobId);
638
+ }
639
+ async deleteFailedJob(jobId) {
640
+ this.failedJobs.delete(jobId);
641
+ }
642
+ async clear() {
643
+ this.failedJobs.clear();
644
+ this.receiptHandles.clear();
645
+ }
646
+ resolveQueueUrl(queue) {
647
+ return this.options.queueUrls?.[queue] ?? this.options.queueUrl;
648
+ }
649
+ };
650
+ function deserializeJob(body) {
651
+ const raw = JSON.parse(body);
652
+ return {
653
+ ...raw,
654
+ availableAt: new Date(raw.availableAt),
655
+ createdAt: new Date(raw.createdAt),
656
+ reservedAt: raw.reservedAt ? new Date(raw.reservedAt) : null
657
+ };
658
+ }
659
+
660
+ // src/queue/Worker.ts
661
+ var Worker = class {
662
+ constructor(driver, options = {}, events = {}) {
663
+ this.driver = driver;
664
+ this.options = options;
665
+ this.events = events;
666
+ this.queues = options.queues ?? ["default"];
667
+ this.sleep = options.sleep ?? 1e3;
668
+ this.maxJobs = options.maxJobs ?? 0;
669
+ this.timeout = options.timeout ?? 6e4;
670
+ this.stopWhenEmpty = options.stopWhenEmpty ?? false;
671
+ }
672
+ running = false;
673
+ shouldStop = false;
674
+ processedJobs = 0;
675
+ queues;
676
+ sleep;
677
+ maxJobs;
678
+ timeout;
679
+ stopWhenEmpty;
680
+ currentJob = null;
681
+ /**
682
+ * Start the worker.
683
+ */
684
+ async start() {
685
+ if (this.running) {
686
+ return;
687
+ }
688
+ this.running = true;
689
+ this.shouldStop = false;
690
+ this.processedJobs = 0;
691
+ this.events.workerStarted?.();
692
+ while (!this.shouldStop) {
693
+ if (this.maxJobs > 0 && this.processedJobs >= this.maxJobs) {
694
+ break;
695
+ }
696
+ const job = await this.getNextJob();
697
+ if (job) {
698
+ await this.processJob(job);
699
+ this.processedJobs++;
700
+ } else {
701
+ if (this.stopWhenEmpty) {
702
+ break;
703
+ }
704
+ await this.sleepMs(this.sleep);
705
+ }
706
+ }
707
+ this.running = false;
708
+ this.events.workerStopped?.();
709
+ }
710
+ /**
711
+ * Stop the worker gracefully.
712
+ */
713
+ async stop() {
714
+ this.shouldStop = true;
715
+ const startWait = Date.now();
716
+ while (this.running && this.currentJob && Date.now() - startWait < this.timeout) {
717
+ await this.sleepMs(100);
718
+ }
719
+ }
720
+ /**
721
+ * Check if the worker is running.
722
+ */
723
+ isRunning() {
724
+ return this.running;
725
+ }
726
+ /**
727
+ * Get the number of processed jobs.
728
+ */
729
+ getProcessedJobsCount() {
730
+ return this.processedJobs;
731
+ }
732
+ /**
733
+ * Get the next available job from the queues.
734
+ */
735
+ async getNextJob() {
736
+ for (const queue of this.queues) {
737
+ const job = await this.driver.pop(queue);
738
+ if (job) {
739
+ return job;
740
+ }
741
+ }
742
+ return null;
743
+ }
744
+ /**
745
+ * Process a single job.
746
+ */
747
+ async processJob(job) {
748
+ this.currentJob = job;
749
+ job.attempts++;
750
+ const JobClass = getJob(job.name);
751
+ if (!JobClass) {
752
+ console.error(`Job class not found: ${job.name}`);
753
+ await this.driver.fail(job, new Error(`Job class not found: ${job.name}`));
754
+ this.currentJob = null;
755
+ return;
756
+ }
757
+ try {
758
+ const instance = new JobClass();
759
+ await this.executeWithTimeout(
760
+ async () => instance.handle(job.payload),
761
+ this.timeout
762
+ );
763
+ await this.driver.delete(job.id);
764
+ this.events.jobProcessed?.(job);
765
+ } catch (error) {
766
+ await this.handleFailedJob(job, error, JobClass);
767
+ } finally {
768
+ this.currentJob = null;
769
+ }
770
+ }
771
+ /**
772
+ * Handle a failed job.
773
+ */
774
+ async handleFailedJob(job, error, JobClass) {
775
+ const willRetry = job.attempts < job.maxAttempts;
776
+ if (willRetry) {
777
+ const delay = JobClass.calculateRetryDelay(job.attempts);
778
+ job.lastError = error.message;
779
+ await this.driver.release(job, delay);
780
+ } else {
781
+ await this.driver.fail(job, error);
782
+ try {
783
+ const instance = new JobClass();
784
+ if (instance.failed) {
785
+ await instance.failed(job.payload, error);
786
+ }
787
+ } catch (failedError) {
788
+ console.error(`Error in job.failed() handler:`, failedError);
789
+ }
790
+ }
791
+ console.error(JSON.stringify({
792
+ level: "error",
793
+ msg: `Job failed: ${job.name}`,
794
+ job: job.name,
795
+ queue: job.queue ?? "default",
796
+ attempt: job.attempts,
797
+ maxAttempts: job.maxAttempts,
798
+ willRetry,
799
+ error: error.message
800
+ }));
801
+ this.events.jobFailed?.(job, error, willRetry);
802
+ }
803
+ /**
804
+ * Execute a function with a timeout.
805
+ */
806
+ async executeWithTimeout(fn, timeoutMs) {
807
+ return new Promise((resolve, reject) => {
808
+ const timer = setTimeout(() => {
809
+ reject(new Error(`Job timed out after ${timeoutMs}ms`));
810
+ }, timeoutMs);
811
+ fn().then((result) => {
812
+ clearTimeout(timer);
813
+ resolve(result);
814
+ }).catch((error) => {
815
+ clearTimeout(timer);
816
+ reject(error);
817
+ });
818
+ });
819
+ }
820
+ /**
821
+ * Sleep for a given number of milliseconds.
822
+ */
823
+ sleepMs(ms) {
824
+ return new Promise((resolve) => setTimeout(resolve, ms));
825
+ }
826
+ };
827
+ async function processJob(driver, queue = "default") {
828
+ const worker = new Worker(driver, { queues: [queue], maxJobs: 1, stopWhenEmpty: true });
829
+ await worker.start();
830
+ return worker.getProcessedJobsCount() > 0;
831
+ }
832
+
833
+ // src/queue/FailedJobReporter.ts
834
+ function defaultLogHandler(info) {
835
+ console.error(JSON.stringify({
836
+ level: "error",
837
+ msg: `Job failed: ${info.jobName}`,
838
+ job: info.jobName,
839
+ queue: info.queue,
840
+ attempt: info.attempt,
841
+ maxAttempts: info.maxAttempts,
842
+ willRetry: info.willRetry,
843
+ error: info.error.message,
844
+ failedAt: info.failedAt.toISOString()
845
+ }));
846
+ }
847
+ var FailedJobReporter = class {
848
+ handlers = [];
849
+ constructor() {
850
+ this.handlers.push(defaultLogHandler);
851
+ }
852
+ /** Add a custom handler for failed jobs. */
853
+ onFailure(handler) {
854
+ this.handlers.push(handler);
855
+ return this;
856
+ }
857
+ /** Report a job failure to all registered handlers. */
858
+ async report(info) {
859
+ for (const handler of this.handlers) {
860
+ try {
861
+ await handler(info);
862
+ } catch {
863
+ }
864
+ }
865
+ }
866
+ };
867
+
868
+ // src/queue/QueueManager.ts
869
+ var QueueManager = class {
870
+ defaultDriver;
871
+ driverFactories = /* @__PURE__ */ new Map();
872
+ resolvedDrivers = /* @__PURE__ */ new Map();
873
+ constructor(config = {}) {
874
+ this.defaultDriver = config.default ?? "memory";
875
+ if (config.drivers) {
876
+ for (const [name, factory] of Object.entries(config.drivers)) {
877
+ this.driverFactories.set(name, factory);
878
+ }
879
+ }
880
+ }
881
+ /**
882
+ * Get a queue driver by name.
883
+ * Returns the default driver if no name is specified.
884
+ */
885
+ driver(name) {
886
+ const driverName = name ?? this.defaultDriver;
887
+ const cached = this.resolvedDrivers.get(driverName);
888
+ if (cached) {
889
+ return cached;
890
+ }
891
+ const factory = this.driverFactories.get(driverName);
892
+ if (!factory) {
893
+ throw new Error(`Queue driver not found: ${driverName}`);
894
+ }
895
+ const driver = factory();
896
+ this.resolvedDrivers.set(driverName, driver);
897
+ if (driverName === this.defaultDriver) {
898
+ setQueueDriver(driver);
899
+ }
900
+ return driver;
901
+ }
902
+ /**
903
+ * Register a custom driver factory.
904
+ */
905
+ registerDriver(name, factory) {
906
+ this.driverFactories.set(name, factory);
907
+ this.resolvedDrivers.delete(name);
908
+ }
909
+ /**
910
+ * Check if a driver is registered.
911
+ */
912
+ hasDriver(name) {
913
+ return this.driverFactories.has(name);
914
+ }
915
+ /**
916
+ * Get the default driver name.
917
+ */
918
+ getDefaultDriverName() {
919
+ return this.defaultDriver;
920
+ }
921
+ /**
922
+ * Get all registered driver names.
923
+ */
924
+ getDriverNames() {
925
+ return Array.from(this.driverFactories.keys());
926
+ }
927
+ /**
928
+ * Set the default driver and update global driver.
929
+ */
930
+ setDefaultDriver(name) {
931
+ if (!this.driverFactories.has(name)) {
932
+ throw new Error(`Queue driver not found: ${name}`);
933
+ }
934
+ const driver = this.driver(name);
935
+ setQueueDriver(driver);
936
+ }
937
+ };
938
+ function createQueueManager(config) {
939
+ return new QueueManager(config);
940
+ }
941
+ export {
942
+ FailedJobReporter,
943
+ Job,
944
+ MemoryDriver,
945
+ QueueManager,
946
+ RedisDriver,
947
+ SqsDriver,
948
+ Worker,
949
+ clearJobRegistry,
950
+ createQueueManager,
951
+ createSqsAdapter,
952
+ getJob,
953
+ getQueueDriver,
954
+ getRegisteredJobs,
955
+ processJob,
956
+ registerJob,
957
+ setQueueDriver
958
+ };