@parsrun/queue 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,884 @@
1
+ // src/types.ts
2
+ import {
3
+ type,
4
+ jobStatus,
5
+ job,
6
+ jobOptions,
7
+ addJobRequest,
8
+ jobProgressUpdate,
9
+ queueStats,
10
+ queueListOptions,
11
+ redisQueueConfig,
12
+ workerOptions,
13
+ queueConfig
14
+ } from "@parsrun/types";
15
+ var QueueError = class extends Error {
16
+ constructor(message, code, cause) {
17
+ super(message);
18
+ this.code = code;
19
+ this.cause = cause;
20
+ this.name = "QueueError";
21
+ }
22
+ };
23
+ var QueueErrorCodes = {
24
+ SEND_FAILED: "SEND_FAILED",
25
+ RECEIVE_FAILED: "RECEIVE_FAILED",
26
+ ACK_FAILED: "ACK_FAILED",
27
+ INVALID_CONFIG: "INVALID_CONFIG",
28
+ QUEUE_FULL: "QUEUE_FULL",
29
+ MESSAGE_NOT_FOUND: "MESSAGE_NOT_FOUND",
30
+ NOT_IMPLEMENTED: "NOT_IMPLEMENTED"
31
+ };
32
+
33
+ // src/adapters/memory.ts
34
+ var MemoryQueueAdapter = class {
35
+ type = "memory";
36
+ name;
37
+ messages = [];
38
+ inFlight = /* @__PURE__ */ new Map();
39
+ processedIds = /* @__PURE__ */ new Set();
40
+ maxSize;
41
+ visibilityTimeout;
42
+ messageCounter = 0;
43
+ isConsuming = false;
44
+ consumeInterval = null;
45
+ constructor(config) {
46
+ this.name = config.name;
47
+ this.maxSize = config.maxSize ?? Infinity;
48
+ this.visibilityTimeout = config.visibilityTimeout ?? 30;
49
+ }
50
+ generateId() {
51
+ this.messageCounter++;
52
+ return `msg-${Date.now()}-${this.messageCounter}`;
53
+ }
54
+ async send(body, options) {
55
+ if (this.messages.length >= this.maxSize) {
56
+ throw new QueueError(
57
+ `Queue ${this.name} is full`,
58
+ QueueErrorCodes.QUEUE_FULL
59
+ );
60
+ }
61
+ if (options?.deduplicationId && this.processedIds.has(options.deduplicationId)) {
62
+ return `dedup-${options.deduplicationId}`;
63
+ }
64
+ const id = this.generateId();
65
+ const now = Date.now();
66
+ const visibleAt = options?.delaySeconds ? now + options.delaySeconds * 1e3 : now;
67
+ const message = {
68
+ id,
69
+ body,
70
+ timestamp: /* @__PURE__ */ new Date(),
71
+ attempts: 0,
72
+ visibleAt,
73
+ deduplicationId: options?.deduplicationId,
74
+ metadata: options?.metadata
75
+ };
76
+ if (options?.priority !== void 0 && options.priority > 0) {
77
+ const insertIndex = this.messages.findIndex(
78
+ (m) => m.metadata?.["priority"] ?? 0 < (options.priority ?? 0)
79
+ );
80
+ if (insertIndex === -1) {
81
+ this.messages.push(message);
82
+ } else {
83
+ this.messages.splice(insertIndex, 0, message);
84
+ }
85
+ } else {
86
+ this.messages.push(message);
87
+ }
88
+ if (options?.deduplicationId) {
89
+ this.processedIds.add(options.deduplicationId);
90
+ }
91
+ return id;
92
+ }
93
+ async sendBatch(messages) {
94
+ const messageIds = [];
95
+ const errors = [];
96
+ let successful = 0;
97
+ let failed = 0;
98
+ for (let i = 0; i < messages.length; i++) {
99
+ const msg = messages[i];
100
+ if (!msg) continue;
101
+ try {
102
+ const id = await this.send(msg.body, msg.options);
103
+ messageIds.push(id);
104
+ successful++;
105
+ } catch (err) {
106
+ failed++;
107
+ errors.push({
108
+ index: i,
109
+ error: err instanceof Error ? err.message : "Unknown error"
110
+ });
111
+ }
112
+ }
113
+ return {
114
+ total: messages.length,
115
+ successful,
116
+ failed,
117
+ messageIds,
118
+ errors
119
+ };
120
+ }
121
+ async receive(maxMessages = 10, visibilityTimeoutOverride) {
122
+ const now = Date.now();
123
+ const timeout = (visibilityTimeoutOverride ?? this.visibilityTimeout) * 1e3;
124
+ const result = [];
125
+ const visibleMessages = [];
126
+ const remainingMessages = [];
127
+ for (const msg of this.messages) {
128
+ if (msg.visibleAt <= now && visibleMessages.length < maxMessages) {
129
+ visibleMessages.push(msg);
130
+ } else {
131
+ remainingMessages.push(msg);
132
+ }
133
+ }
134
+ this.messages = remainingMessages;
135
+ for (const msg of visibleMessages) {
136
+ msg.attempts++;
137
+ msg.visibleAt = now + timeout;
138
+ this.inFlight.set(msg.id, msg);
139
+ result.push({
140
+ id: msg.id,
141
+ body: msg.body,
142
+ timestamp: msg.timestamp,
143
+ attempts: msg.attempts,
144
+ metadata: msg.metadata
145
+ });
146
+ }
147
+ return result;
148
+ }
149
+ async ack(messageId) {
150
+ const message = this.inFlight.get(messageId);
151
+ if (!message) {
152
+ throw new QueueError(
153
+ `Message ${messageId} not found in flight`,
154
+ QueueErrorCodes.MESSAGE_NOT_FOUND
155
+ );
156
+ }
157
+ this.inFlight.delete(messageId);
158
+ }
159
+ async ackBatch(messageIds) {
160
+ for (const id of messageIds) {
161
+ this.inFlight.delete(id);
162
+ }
163
+ }
164
+ async nack(messageId, delaySeconds) {
165
+ const message = this.inFlight.get(messageId);
166
+ if (!message) {
167
+ throw new QueueError(
168
+ `Message ${messageId} not found in flight`,
169
+ QueueErrorCodes.MESSAGE_NOT_FOUND
170
+ );
171
+ }
172
+ this.inFlight.delete(messageId);
173
+ const now = Date.now();
174
+ message.visibleAt = delaySeconds ? now + delaySeconds * 1e3 : now;
175
+ this.messages.push(message);
176
+ }
177
+ async consume(handler, options) {
178
+ if (this.isConsuming) {
179
+ return;
180
+ }
181
+ this.isConsuming = true;
182
+ const batchSize = options?.batchSize ?? 10;
183
+ const pollingInterval = options?.pollingInterval ?? 1e3;
184
+ const visibilityTimeout = options?.visibilityTimeout ?? this.visibilityTimeout;
185
+ const maxRetries = options?.maxRetries ?? 3;
186
+ const concurrency = options?.concurrency ?? 1;
187
+ const processMessages = async () => {
188
+ if (!this.isConsuming) return;
189
+ const messages = await this.receive(batchSize, visibilityTimeout);
190
+ for (let i = 0; i < messages.length; i += concurrency) {
191
+ const batch = messages.slice(i, i + concurrency);
192
+ await Promise.all(
193
+ batch.map(async (msg) => {
194
+ try {
195
+ await handler(msg);
196
+ await this.ack(msg.id);
197
+ } catch (err) {
198
+ if (msg.attempts >= maxRetries) {
199
+ await this.ack(msg.id);
200
+ console.error(
201
+ `[Queue ${this.name}] Message ${msg.id} exceeded max retries, dropped`
202
+ );
203
+ } else {
204
+ await this.nack(msg.id, 5);
205
+ }
206
+ }
207
+ })
208
+ );
209
+ }
210
+ };
211
+ this.consumeInterval = setInterval(processMessages, pollingInterval);
212
+ await processMessages();
213
+ }
214
+ async stopConsuming() {
215
+ this.isConsuming = false;
216
+ if (this.consumeInterval) {
217
+ clearInterval(this.consumeInterval);
218
+ this.consumeInterval = null;
219
+ }
220
+ }
221
+ async getStats() {
222
+ const now = Date.now();
223
+ for (const [id, msg] of this.inFlight) {
224
+ if (msg.visibleAt <= now) {
225
+ this.inFlight.delete(id);
226
+ this.messages.push(msg);
227
+ }
228
+ }
229
+ return {
230
+ messageCount: this.messages.length,
231
+ inFlightCount: this.inFlight.size
232
+ };
233
+ }
234
+ async purge() {
235
+ this.messages = [];
236
+ this.inFlight.clear();
237
+ }
238
+ async close() {
239
+ await this.stopConsuming();
240
+ await this.purge();
241
+ this.processedIds.clear();
242
+ }
243
+ };
244
+ function createMemoryQueueAdapter(config) {
245
+ return new MemoryQueueAdapter(config);
246
+ }
247
+
248
+ // src/adapters/cloudflare.ts
249
+ var CloudflareQueueAdapter = class {
250
+ type = "cloudflare";
251
+ name = "cloudflare-queue";
252
+ queue;
253
+ constructor(config) {
254
+ this.queue = config.queue;
255
+ }
256
+ async send(body, options) {
257
+ try {
258
+ await this.queue.send(body, {
259
+ delaySeconds: options?.delaySeconds
260
+ });
261
+ return `cf-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
262
+ } catch (err) {
263
+ throw new QueueError(
264
+ `Cloudflare Queue send failed: ${err instanceof Error ? err.message : "Unknown error"}`,
265
+ QueueErrorCodes.SEND_FAILED,
266
+ err
267
+ );
268
+ }
269
+ }
270
+ async sendBatch(messages) {
271
+ try {
272
+ const batchMessages = messages.map((m) => ({
273
+ body: m.body,
274
+ delaySeconds: m.options?.delaySeconds
275
+ }));
276
+ await this.queue.sendBatch(batchMessages);
277
+ const messageIds = messages.map(
278
+ () => `cf-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
279
+ );
280
+ return {
281
+ total: messages.length,
282
+ successful: messages.length,
283
+ failed: 0,
284
+ messageIds,
285
+ errors: []
286
+ };
287
+ } catch (err) {
288
+ throw new QueueError(
289
+ `Cloudflare Queue batch send failed: ${err instanceof Error ? err.message : "Unknown error"}`,
290
+ QueueErrorCodes.SEND_FAILED,
291
+ err
292
+ );
293
+ }
294
+ }
295
+ // Cloudflare Queues are push-based, so receive is not applicable
296
+ // Messages are delivered to queue handlers via Workers
297
+ };
298
+ var CloudflareQueueProcessor = class {
299
+ /**
300
+ * Process a batch of messages from Cloudflare Queues
301
+ */
302
+ async processBatch(batch, handler, options) {
303
+ let processed = 0;
304
+ let failed = 0;
305
+ for (const msg of batch.messages) {
306
+ try {
307
+ const queueMessage = {
308
+ id: msg.id,
309
+ body: msg.body,
310
+ timestamp: msg.timestamp,
311
+ attempts: msg.attempts
312
+ };
313
+ await handler(queueMessage);
314
+ if (!options?.ackAll) {
315
+ msg.ack();
316
+ }
317
+ processed++;
318
+ } catch (err) {
319
+ failed++;
320
+ if (options?.retryAllOnFailure) {
321
+ batch.retryAll();
322
+ return { processed, failed: batch.messages.length };
323
+ }
324
+ msg.retry();
325
+ }
326
+ }
327
+ if (options?.ackAll && failed === 0) {
328
+ batch.ackAll();
329
+ }
330
+ return { processed, failed };
331
+ }
332
+ /**
333
+ * Convert a Cloudflare message to QueueMessage format
334
+ */
335
+ toQueueMessage(msg) {
336
+ return {
337
+ id: msg.id,
338
+ body: msg.body,
339
+ timestamp: msg.timestamp,
340
+ attempts: msg.attempts
341
+ };
342
+ }
343
+ };
344
+ function createCloudflareQueueAdapter(config) {
345
+ return new CloudflareQueueAdapter(config);
346
+ }
347
+ function createCloudflareQueueProcessor() {
348
+ return new CloudflareQueueProcessor();
349
+ }
350
+
351
+ // src/adapters/qstash.ts
352
+ var QStashAdapter = class {
353
+ type = "qstash";
354
+ name = "qstash";
355
+ token;
356
+ destinationUrl;
357
+ baseUrl = "https://qstash.upstash.io/v2";
358
+ constructor(config) {
359
+ this.token = config.token;
360
+ this.destinationUrl = config.destinationUrl;
361
+ }
362
+ async send(body, options) {
363
+ try {
364
+ const headers = {
365
+ Authorization: `Bearer ${this.token}`,
366
+ "Content-Type": "application/json"
367
+ };
368
+ if (options?.delaySeconds) {
369
+ headers["Upstash-Delay"] = `${options.delaySeconds}s`;
370
+ }
371
+ if (options?.deduplicationId) {
372
+ headers["Upstash-Deduplication-Id"] = options.deduplicationId;
373
+ }
374
+ if (options?.metadata) {
375
+ for (const [key, value] of Object.entries(options.metadata)) {
376
+ if (typeof value === "string") {
377
+ headers[`Upstash-Forward-${key}`] = value;
378
+ }
379
+ }
380
+ }
381
+ const response = await fetch(
382
+ `${this.baseUrl}/publish/${encodeURIComponent(this.destinationUrl)}`,
383
+ {
384
+ method: "POST",
385
+ headers,
386
+ body: JSON.stringify(body)
387
+ }
388
+ );
389
+ if (!response.ok) {
390
+ const error = await response.text();
391
+ throw new Error(`QStash API error: ${error}`);
392
+ }
393
+ const result = await response.json();
394
+ return result.messageId;
395
+ } catch (err) {
396
+ throw new QueueError(
397
+ `QStash send failed: ${err instanceof Error ? err.message : "Unknown error"}`,
398
+ QueueErrorCodes.SEND_FAILED,
399
+ err
400
+ );
401
+ }
402
+ }
403
+ async sendBatch(messages) {
404
+ try {
405
+ const batchMessages = messages.map((m) => {
406
+ const headers = {
407
+ "Content-Type": "application/json"
408
+ };
409
+ if (m.options?.delaySeconds) {
410
+ headers["Upstash-Delay"] = `${m.options.delaySeconds}s`;
411
+ }
412
+ if (m.options?.deduplicationId) {
413
+ headers["Upstash-Deduplication-Id"] = m.options.deduplicationId;
414
+ }
415
+ return {
416
+ destination: this.destinationUrl,
417
+ headers,
418
+ body: JSON.stringify(m.body)
419
+ };
420
+ });
421
+ const response = await fetch(`${this.baseUrl}/batch`, {
422
+ method: "POST",
423
+ headers: {
424
+ Authorization: `Bearer ${this.token}`,
425
+ "Content-Type": "application/json"
426
+ },
427
+ body: JSON.stringify(batchMessages)
428
+ });
429
+ if (!response.ok) {
430
+ const error = await response.text();
431
+ throw new Error(`QStash API error: ${error}`);
432
+ }
433
+ const results = await response.json();
434
+ const messageIds = [];
435
+ const errors = [];
436
+ let successful = 0;
437
+ let failed = 0;
438
+ for (let i = 0; i < results.length; i++) {
439
+ const result = results[i];
440
+ if (result?.messageId) {
441
+ messageIds.push(result.messageId);
442
+ successful++;
443
+ } else {
444
+ failed++;
445
+ errors.push({
446
+ index: i,
447
+ error: result?.error ?? "Unknown error"
448
+ });
449
+ }
450
+ }
451
+ return {
452
+ total: messages.length,
453
+ successful,
454
+ failed,
455
+ messageIds,
456
+ errors
457
+ };
458
+ } catch (err) {
459
+ throw new QueueError(
460
+ `QStash batch send failed: ${err instanceof Error ? err.message : "Unknown error"}`,
461
+ QueueErrorCodes.SEND_FAILED,
462
+ err
463
+ );
464
+ }
465
+ }
466
+ /**
467
+ * Schedule a message for future delivery
468
+ */
469
+ async schedule(body, cronExpression, options) {
470
+ try {
471
+ const headers = {
472
+ Authorization: `Bearer ${this.token}`,
473
+ "Content-Type": "application/json",
474
+ "Upstash-Cron": cronExpression
475
+ };
476
+ if (options?.deduplicationId) {
477
+ headers["Upstash-Deduplication-Id"] = options.deduplicationId;
478
+ }
479
+ const response = await fetch(
480
+ `${this.baseUrl}/schedules/${encodeURIComponent(this.destinationUrl)}`,
481
+ {
482
+ method: "POST",
483
+ headers,
484
+ body: JSON.stringify(body)
485
+ }
486
+ );
487
+ if (!response.ok) {
488
+ const error = await response.text();
489
+ throw new Error(`QStash API error: ${error}`);
490
+ }
491
+ const result = await response.json();
492
+ return result.scheduleId;
493
+ } catch (err) {
494
+ throw new QueueError(
495
+ `QStash schedule failed: ${err instanceof Error ? err.message : "Unknown error"}`,
496
+ QueueErrorCodes.SEND_FAILED,
497
+ err
498
+ );
499
+ }
500
+ }
501
+ /**
502
+ * Delete a scheduled message
503
+ */
504
+ async deleteSchedule(scheduleId) {
505
+ try {
506
+ const response = await fetch(`${this.baseUrl}/schedules/${scheduleId}`, {
507
+ method: "DELETE",
508
+ headers: {
509
+ Authorization: `Bearer ${this.token}`
510
+ }
511
+ });
512
+ if (!response.ok) {
513
+ const error = await response.text();
514
+ throw new Error(`QStash API error: ${error}`);
515
+ }
516
+ } catch (err) {
517
+ throw new QueueError(
518
+ `QStash delete schedule failed: ${err instanceof Error ? err.message : "Unknown error"}`,
519
+ QueueErrorCodes.SEND_FAILED,
520
+ err
521
+ );
522
+ }
523
+ }
524
+ /**
525
+ * List all schedules
526
+ */
527
+ async listSchedules() {
528
+ try {
529
+ const response = await fetch(`${this.baseUrl}/schedules`, {
530
+ headers: {
531
+ Authorization: `Bearer ${this.token}`
532
+ }
533
+ });
534
+ if (!response.ok) {
535
+ const error = await response.text();
536
+ throw new Error(`QStash API error: ${error}`);
537
+ }
538
+ return await response.json();
539
+ } catch (err) {
540
+ throw new QueueError(
541
+ `QStash list schedules failed: ${err instanceof Error ? err.message : "Unknown error"}`,
542
+ QueueErrorCodes.SEND_FAILED,
543
+ err
544
+ );
545
+ }
546
+ }
547
+ };
548
+ var QStashReceiver = class {
549
+ currentSigningKey;
550
+ nextSigningKey;
551
+ constructor(config) {
552
+ this.currentSigningKey = config.currentSigningKey;
553
+ this.nextSigningKey = config.nextSigningKey;
554
+ }
555
+ /**
556
+ * Verify a request from QStash and extract the message
557
+ */
558
+ async verify(request) {
559
+ const signature = request.headers.get("Upstash-Signature");
560
+ if (!signature) {
561
+ return null;
562
+ }
563
+ const body = await request.text();
564
+ const isValid = await this.verifySignature(body, signature, this.currentSigningKey) || await this.verifySignature(body, signature, this.nextSigningKey);
565
+ if (!isValid) {
566
+ return null;
567
+ }
568
+ const messageId = request.headers.get("Upstash-Message-Id") ?? `qstash-${Date.now()}`;
569
+ const retryCount = parseInt(
570
+ request.headers.get("Upstash-Retried") ?? "0",
571
+ 10
572
+ );
573
+ let parsedBody;
574
+ try {
575
+ parsedBody = JSON.parse(body);
576
+ } catch {
577
+ parsedBody = body;
578
+ }
579
+ return {
580
+ id: messageId,
581
+ body: parsedBody,
582
+ timestamp: /* @__PURE__ */ new Date(),
583
+ attempts: retryCount + 1
584
+ };
585
+ }
586
+ async verifySignature(body, signature, key) {
587
+ try {
588
+ const parts = signature.split(".");
589
+ if (parts.length !== 3) {
590
+ return false;
591
+ }
592
+ const [headerB64, payloadB64, signatureB64] = parts;
593
+ if (!headerB64 || !payloadB64 || !signatureB64) {
594
+ return false;
595
+ }
596
+ const encoder = new TextEncoder();
597
+ const keyData = encoder.encode(key);
598
+ const cryptoKey = await crypto.subtle.importKey(
599
+ "raw",
600
+ keyData,
601
+ { name: "HMAC", hash: "SHA-256" },
602
+ false,
603
+ ["verify"]
604
+ );
605
+ const signatureData = this.base64UrlDecode(signatureB64);
606
+ const dataToVerify = encoder.encode(`${headerB64}.${payloadB64}`);
607
+ const isValid = await crypto.subtle.verify(
608
+ "HMAC",
609
+ cryptoKey,
610
+ signatureData,
611
+ dataToVerify
612
+ );
613
+ if (!isValid) {
614
+ return false;
615
+ }
616
+ const payload = JSON.parse(atob(payloadB64));
617
+ const now = Math.floor(Date.now() / 1e3);
618
+ if (payload.exp && payload.exp < now) {
619
+ return false;
620
+ }
621
+ if (payload.nbf && payload.nbf > now) {
622
+ return false;
623
+ }
624
+ if (payload.body) {
625
+ const bodyHash = await this.sha256(body);
626
+ const expectedHash = this.base64UrlEncode(
627
+ new Uint8Array(
628
+ atob(payload.body).split("").map((c) => c.charCodeAt(0))
629
+ )
630
+ );
631
+ if (bodyHash !== expectedHash && payload.body !== bodyHash) {
632
+ const directHash = await this.sha256Base64(body);
633
+ if (directHash !== payload.body) {
634
+ return false;
635
+ }
636
+ }
637
+ }
638
+ return true;
639
+ } catch {
640
+ return false;
641
+ }
642
+ }
643
+ base64UrlDecode(str) {
644
+ const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
645
+ const padding = (4 - base64.length % 4) % 4;
646
+ const padded = base64 + "=".repeat(padding);
647
+ const binary = atob(padded);
648
+ const bytes = new Uint8Array(binary.length);
649
+ for (let i = 0; i < binary.length; i++) {
650
+ bytes[i] = binary.charCodeAt(i);
651
+ }
652
+ return bytes;
653
+ }
654
+ base64UrlEncode(data) {
655
+ let binary = "";
656
+ for (let i = 0; i < data.length; i++) {
657
+ const byte = data[i];
658
+ if (byte !== void 0) {
659
+ binary += String.fromCharCode(byte);
660
+ }
661
+ }
662
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
663
+ }
664
+ async sha256(message) {
665
+ const encoder = new TextEncoder();
666
+ const data = encoder.encode(message);
667
+ const hash = await crypto.subtle.digest("SHA-256", data);
668
+ return this.base64UrlEncode(new Uint8Array(hash));
669
+ }
670
+ async sha256Base64(message) {
671
+ const encoder = new TextEncoder();
672
+ const data = encoder.encode(message);
673
+ const hash = await crypto.subtle.digest("SHA-256", data);
674
+ let binary = "";
675
+ const bytes = new Uint8Array(hash);
676
+ for (let i = 0; i < bytes.length; i++) {
677
+ const byte = bytes[i];
678
+ if (byte !== void 0) {
679
+ binary += String.fromCharCode(byte);
680
+ }
681
+ }
682
+ return btoa(binary);
683
+ }
684
+ };
685
+ function createQStashAdapter(config) {
686
+ return new QStashAdapter(config);
687
+ }
688
+ function createQStashReceiver(config) {
689
+ return new QStashReceiver(config);
690
+ }
691
+
692
+ // src/index.ts
693
+ var QueueService = class {
694
+ adapter;
695
+ debug;
696
+ constructor(config) {
697
+ this.adapter = config.adapter;
698
+ this.debug = config.debug ?? false;
699
+ }
700
+ /**
701
+ * Get adapter type
702
+ */
703
+ get adapterType() {
704
+ return this.adapter.type;
705
+ }
706
+ /**
707
+ * Get queue name
708
+ */
709
+ get name() {
710
+ return this.adapter.name;
711
+ }
712
+ /**
713
+ * Send a message to the queue
714
+ */
715
+ async send(body, options) {
716
+ if (this.debug) {
717
+ console.log(`[Queue ${this.name}] Sending message:`, body);
718
+ }
719
+ const messageId = await this.adapter.send(body, options);
720
+ if (this.debug) {
721
+ console.log(`[Queue ${this.name}] Message sent: ${messageId}`);
722
+ }
723
+ return messageId;
724
+ }
725
+ /**
726
+ * Send multiple messages at once
727
+ */
728
+ async sendBatch(messages) {
729
+ if (this.debug) {
730
+ console.log(`[Queue ${this.name}] Sending batch of ${messages.length} messages`);
731
+ }
732
+ if (this.adapter.sendBatch) {
733
+ return this.adapter.sendBatch(messages);
734
+ }
735
+ const messageIds = [];
736
+ const errors = [];
737
+ let successful = 0;
738
+ let failed = 0;
739
+ for (let i = 0; i < messages.length; i++) {
740
+ const msg = messages[i];
741
+ if (!msg) continue;
742
+ try {
743
+ const id = await this.send(msg.body, msg.options);
744
+ messageIds.push(id);
745
+ successful++;
746
+ } catch (err) {
747
+ failed++;
748
+ errors.push({
749
+ index: i,
750
+ error: err instanceof Error ? err.message : "Unknown error"
751
+ });
752
+ }
753
+ }
754
+ return {
755
+ total: messages.length,
756
+ successful,
757
+ failed,
758
+ messageIds,
759
+ errors
760
+ };
761
+ }
762
+ /**
763
+ * Receive messages from the queue (pull-based)
764
+ */
765
+ async receive(maxMessages, visibilityTimeout) {
766
+ if (!this.adapter.receive) {
767
+ throw new Error(`${this.adapter.type} adapter does not support receive()`);
768
+ }
769
+ return this.adapter.receive(maxMessages, visibilityTimeout);
770
+ }
771
+ /**
772
+ * Acknowledge message processing (mark as complete)
773
+ */
774
+ async ack(messageId) {
775
+ if (!this.adapter.ack) {
776
+ throw new Error(`${this.adapter.type} adapter does not support ack()`);
777
+ }
778
+ await this.adapter.ack(messageId);
779
+ }
780
+ /**
781
+ * Acknowledge multiple messages
782
+ */
783
+ async ackBatch(messageIds) {
784
+ if (this.adapter.ackBatch) {
785
+ await this.adapter.ackBatch(messageIds);
786
+ return;
787
+ }
788
+ if (this.adapter.ack) {
789
+ await Promise.all(messageIds.map((id) => this.adapter.ack(id)));
790
+ return;
791
+ }
792
+ throw new Error(`${this.adapter.type} adapter does not support ack()`);
793
+ }
794
+ /**
795
+ * Return message to queue (negative acknowledgement)
796
+ */
797
+ async nack(messageId, delaySeconds) {
798
+ if (!this.adapter.nack) {
799
+ throw new Error(`${this.adapter.type} adapter does not support nack()`);
800
+ }
801
+ await this.adapter.nack(messageId, delaySeconds);
802
+ }
803
+ /**
804
+ * Start consuming messages (push-based)
805
+ */
806
+ async consume(handler, options) {
807
+ if (!this.adapter.consume) {
808
+ throw new Error(`${this.adapter.type} adapter does not support consume()`);
809
+ }
810
+ if (this.debug) {
811
+ console.log(`[Queue ${this.name}] Starting consumer`);
812
+ }
813
+ await this.adapter.consume(handler, options);
814
+ }
815
+ /**
816
+ * Stop consuming messages
817
+ */
818
+ async stopConsuming() {
819
+ if (this.adapter.stopConsuming) {
820
+ await this.adapter.stopConsuming();
821
+ }
822
+ }
823
+ /**
824
+ * Get queue statistics
825
+ */
826
+ async getStats() {
827
+ if (this.adapter.getStats) {
828
+ return this.adapter.getStats();
829
+ }
830
+ return { messageCount: -1 };
831
+ }
832
+ /**
833
+ * Purge all messages from queue
834
+ */
835
+ async purge() {
836
+ if (this.adapter.purge) {
837
+ await this.adapter.purge();
838
+ }
839
+ }
840
+ /**
841
+ * Close/cleanup queue resources
842
+ */
843
+ async close() {
844
+ if (this.adapter.close) {
845
+ await this.adapter.close();
846
+ }
847
+ }
848
+ };
849
+ function createQueueService(config) {
850
+ return new QueueService(config);
851
+ }
852
+ var index_default = {
853
+ QueueService,
854
+ createQueueService
855
+ };
856
+ export {
857
+ CloudflareQueueAdapter,
858
+ CloudflareQueueProcessor,
859
+ MemoryQueueAdapter,
860
+ QStashAdapter,
861
+ QStashReceiver,
862
+ QueueError,
863
+ QueueErrorCodes,
864
+ QueueService,
865
+ addJobRequest,
866
+ createCloudflareQueueAdapter,
867
+ createCloudflareQueueProcessor,
868
+ createMemoryQueueAdapter,
869
+ createQStashAdapter,
870
+ createQStashReceiver,
871
+ createQueueService,
872
+ index_default as default,
873
+ job,
874
+ jobOptions,
875
+ jobProgressUpdate,
876
+ jobStatus,
877
+ queueStats as parsQueueStats,
878
+ queueConfig,
879
+ queueListOptions,
880
+ redisQueueConfig,
881
+ type,
882
+ workerOptions
883
+ };
884
+ //# sourceMappingURL=index.js.map