@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/README.md +158 -0
- package/dist/adapters/cloudflare.d.ts +94 -0
- package/dist/adapters/cloudflare.js +141 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.js +703 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/memory.d.ts +67 -0
- package/dist/adapters/memory.js +251 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/qstash.d.ts +110 -0
- package/dist/adapters/qstash.js +379 -0
- package/dist/adapters/qstash.js.map +1 -0
- package/dist/index.d.ts +123 -0
- package/dist/index.js +884 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +246 -0
- package/dist/types.js +47 -0
- package/dist/types.js.map +1 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# @parsrun/queue
|
|
2
|
+
|
|
3
|
+
Edge-compatible message queues for Pars with multiple adapter support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multi-Adapter**: Memory, Cloudflare Queues, Upstash QStash
|
|
8
|
+
- **Edge-Compatible**: Works on all runtimes
|
|
9
|
+
- **Delayed Jobs**: Schedule jobs for later
|
|
10
|
+
- **Retries**: Automatic retry with backoff
|
|
11
|
+
- **Dead Letter Queue**: Failed job handling
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @parsrun/queue
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { createQueue } from '@parsrun/queue';
|
|
23
|
+
|
|
24
|
+
const queue = createQueue({
|
|
25
|
+
adapter: 'memory', // or 'cloudflare', 'qstash'
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Publish message
|
|
29
|
+
await queue.publish('email:send', {
|
|
30
|
+
to: 'user@example.com',
|
|
31
|
+
subject: 'Hello',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Subscribe to messages
|
|
35
|
+
queue.subscribe('email:send', async (message) => {
|
|
36
|
+
await sendEmail(message.data);
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API Overview
|
|
41
|
+
|
|
42
|
+
### Adapters
|
|
43
|
+
|
|
44
|
+
#### Memory (Development)
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { createMemoryQueue } from '@parsrun/queue/adapters/memory';
|
|
48
|
+
|
|
49
|
+
const queue = createMemoryQueue({
|
|
50
|
+
concurrency: 5,
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Cloudflare Queues
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { createCloudflareQueue } from '@parsrun/queue/adapters/cloudflare';
|
|
58
|
+
|
|
59
|
+
// In Cloudflare Worker
|
|
60
|
+
const queue = createCloudflareQueue({
|
|
61
|
+
queue: env.MY_QUEUE,
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Upstash QStash
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { createQStashQueue } from '@parsrun/queue/adapters/qstash';
|
|
69
|
+
|
|
70
|
+
const queue = createQStashQueue({
|
|
71
|
+
token: process.env.QSTASH_TOKEN,
|
|
72
|
+
currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY,
|
|
73
|
+
nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY,
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Publishing Messages
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Simple publish
|
|
81
|
+
await queue.publish('topic', { data: 'value' });
|
|
82
|
+
|
|
83
|
+
// With options
|
|
84
|
+
await queue.publish('topic', data, {
|
|
85
|
+
delay: 60, // Delay in seconds
|
|
86
|
+
retries: 3, // Max retries
|
|
87
|
+
deduplicationId: 'unique-id',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Batch publish
|
|
91
|
+
await queue.publishBatch('topic', [
|
|
92
|
+
{ data: 'item1' },
|
|
93
|
+
{ data: 'item2' },
|
|
94
|
+
]);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Subscribing to Messages
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
queue.subscribe('topic', async (message) => {
|
|
101
|
+
console.log(message.id, message.data);
|
|
102
|
+
|
|
103
|
+
// Throw to retry
|
|
104
|
+
if (shouldRetry) {
|
|
105
|
+
throw new Error('Retry later');
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// With options
|
|
110
|
+
queue.subscribe('topic', handler, {
|
|
111
|
+
concurrency: 5,
|
|
112
|
+
maxRetries: 3,
|
|
113
|
+
retryDelay: 1000,
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Scheduled Jobs
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// Schedule for specific time
|
|
121
|
+
await queue.schedule('cleanup', { type: 'daily' }, {
|
|
122
|
+
runAt: new Date('2024-01-01T00:00:00Z'),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Cron-like scheduling (QStash)
|
|
126
|
+
await queue.schedule('reports', { type: 'weekly' }, {
|
|
127
|
+
cron: '0 9 * * 1', // Every Monday at 9am
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Dead Letter Queue
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
const queue = createQueue({
|
|
135
|
+
adapter: 'memory',
|
|
136
|
+
deadLetterQueue: {
|
|
137
|
+
enabled: true,
|
|
138
|
+
maxRetries: 5,
|
|
139
|
+
onDeadLetter: async (message) => {
|
|
140
|
+
// Handle permanently failed message
|
|
141
|
+
await notifyAdmin(message);
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Exports
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { ... } from '@parsrun/queue'; // Main exports
|
|
151
|
+
import { ... } from '@parsrun/queue/adapters/memory'; // Memory adapter
|
|
152
|
+
import { ... } from '@parsrun/queue/adapters/cloudflare'; // Cloudflare Queues
|
|
153
|
+
import { ... } from '@parsrun/queue/adapters/qstash'; // Upstash QStash
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { QueueAdapter, CloudflareQueueConfig, SendMessageOptions, BatchSendResult, CloudflareMessageBatch, QueueMessage, CloudflareMessage } from '../types.js';
|
|
2
|
+
import '@parsrun/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @parsrun/queue - Cloudflare Queues Adapter
|
|
6
|
+
* Adapter for Cloudflare Workers Queues
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Cloudflare Queue Adapter
|
|
11
|
+
* Uses Cloudflare Workers Queues for serverless message processing
|
|
12
|
+
*
|
|
13
|
+
* Cloudflare Queues uses a push-based model where messages are delivered
|
|
14
|
+
* to queue consumers via Workers.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // In your Worker
|
|
19
|
+
* export default {
|
|
20
|
+
* async fetch(request, env) {
|
|
21
|
+
* const queue = new CloudflareQueueAdapter({
|
|
22
|
+
* queue: env.MY_QUEUE,
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* await queue.send({ userId: '123', action: 'welcome-email' });
|
|
26
|
+
* return new Response('Queued');
|
|
27
|
+
* },
|
|
28
|
+
*
|
|
29
|
+
* async queue(batch, env) {
|
|
30
|
+
* // Process messages delivered by Cloudflare
|
|
31
|
+
* const processor = new CloudflareQueueProcessor();
|
|
32
|
+
* await processor.processBatch(batch, async (msg) => {
|
|
33
|
+
* console.log('Processing:', msg.body);
|
|
34
|
+
* });
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare class CloudflareQueueAdapter<T = unknown> implements QueueAdapter<T> {
|
|
40
|
+
readonly type: "cloudflare";
|
|
41
|
+
readonly name = "cloudflare-queue";
|
|
42
|
+
private queue;
|
|
43
|
+
constructor(config: CloudflareQueueConfig);
|
|
44
|
+
send(body: T, options?: SendMessageOptions): Promise<string>;
|
|
45
|
+
sendBatch(messages: Array<{
|
|
46
|
+
body: T;
|
|
47
|
+
options?: SendMessageOptions;
|
|
48
|
+
}>): Promise<BatchSendResult>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Cloudflare Queue Processor
|
|
52
|
+
* Helper for processing queue batches in Workers queue handlers
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* export default {
|
|
57
|
+
* async queue(batch, env) {
|
|
58
|
+
* const processor = new CloudflareQueueProcessor<MyMessageType>();
|
|
59
|
+
* await processor.processBatch(batch, async (msg) => {
|
|
60
|
+
* // Process each message
|
|
61
|
+
* await handleMessage(msg.body);
|
|
62
|
+
* });
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
declare class CloudflareQueueProcessor<T = unknown> {
|
|
68
|
+
/**
|
|
69
|
+
* Process a batch of messages from Cloudflare Queues
|
|
70
|
+
*/
|
|
71
|
+
processBatch(batch: CloudflareMessageBatch<T>, handler: (message: QueueMessage<T>) => void | Promise<void>, options?: {
|
|
72
|
+
/** Whether to ack all messages at once (default: false - ack individually) */
|
|
73
|
+
ackAll?: boolean;
|
|
74
|
+
/** Whether to retry all on any failure (default: false) */
|
|
75
|
+
retryAllOnFailure?: boolean;
|
|
76
|
+
}): Promise<{
|
|
77
|
+
processed: number;
|
|
78
|
+
failed: number;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* Convert a Cloudflare message to QueueMessage format
|
|
82
|
+
*/
|
|
83
|
+
toQueueMessage(msg: CloudflareMessage<T>): QueueMessage<T>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Create a Cloudflare Queue adapter
|
|
87
|
+
*/
|
|
88
|
+
declare function createCloudflareQueueAdapter<T = unknown>(config: CloudflareQueueConfig): CloudflareQueueAdapter<T>;
|
|
89
|
+
/**
|
|
90
|
+
* Create a Cloudflare Queue processor
|
|
91
|
+
*/
|
|
92
|
+
declare function createCloudflareQueueProcessor<T = unknown>(): CloudflareQueueProcessor<T>;
|
|
93
|
+
|
|
94
|
+
export { CloudflareQueueAdapter, CloudflareQueueProcessor, createCloudflareQueueAdapter, createCloudflareQueueProcessor };
|
|
@@ -0,0 +1,141 @@
|
|
|
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/cloudflare.ts
|
|
34
|
+
var CloudflareQueueAdapter = class {
|
|
35
|
+
type = "cloudflare";
|
|
36
|
+
name = "cloudflare-queue";
|
|
37
|
+
queue;
|
|
38
|
+
constructor(config) {
|
|
39
|
+
this.queue = config.queue;
|
|
40
|
+
}
|
|
41
|
+
async send(body, options) {
|
|
42
|
+
try {
|
|
43
|
+
await this.queue.send(body, {
|
|
44
|
+
delaySeconds: options?.delaySeconds
|
|
45
|
+
});
|
|
46
|
+
return `cf-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new QueueError(
|
|
49
|
+
`Cloudflare Queue send failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
50
|
+
QueueErrorCodes.SEND_FAILED,
|
|
51
|
+
err
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async sendBatch(messages) {
|
|
56
|
+
try {
|
|
57
|
+
const batchMessages = messages.map((m) => ({
|
|
58
|
+
body: m.body,
|
|
59
|
+
delaySeconds: m.options?.delaySeconds
|
|
60
|
+
}));
|
|
61
|
+
await this.queue.sendBatch(batchMessages);
|
|
62
|
+
const messageIds = messages.map(
|
|
63
|
+
() => `cf-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
|
|
64
|
+
);
|
|
65
|
+
return {
|
|
66
|
+
total: messages.length,
|
|
67
|
+
successful: messages.length,
|
|
68
|
+
failed: 0,
|
|
69
|
+
messageIds,
|
|
70
|
+
errors: []
|
|
71
|
+
};
|
|
72
|
+
} catch (err) {
|
|
73
|
+
throw new QueueError(
|
|
74
|
+
`Cloudflare Queue batch send failed: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
75
|
+
QueueErrorCodes.SEND_FAILED,
|
|
76
|
+
err
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Cloudflare Queues are push-based, so receive is not applicable
|
|
81
|
+
// Messages are delivered to queue handlers via Workers
|
|
82
|
+
};
|
|
83
|
+
var CloudflareQueueProcessor = class {
|
|
84
|
+
/**
|
|
85
|
+
* Process a batch of messages from Cloudflare Queues
|
|
86
|
+
*/
|
|
87
|
+
async processBatch(batch, handler, options) {
|
|
88
|
+
let processed = 0;
|
|
89
|
+
let failed = 0;
|
|
90
|
+
for (const msg of batch.messages) {
|
|
91
|
+
try {
|
|
92
|
+
const queueMessage = {
|
|
93
|
+
id: msg.id,
|
|
94
|
+
body: msg.body,
|
|
95
|
+
timestamp: msg.timestamp,
|
|
96
|
+
attempts: msg.attempts
|
|
97
|
+
};
|
|
98
|
+
await handler(queueMessage);
|
|
99
|
+
if (!options?.ackAll) {
|
|
100
|
+
msg.ack();
|
|
101
|
+
}
|
|
102
|
+
processed++;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
failed++;
|
|
105
|
+
if (options?.retryAllOnFailure) {
|
|
106
|
+
batch.retryAll();
|
|
107
|
+
return { processed, failed: batch.messages.length };
|
|
108
|
+
}
|
|
109
|
+
msg.retry();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (options?.ackAll && failed === 0) {
|
|
113
|
+
batch.ackAll();
|
|
114
|
+
}
|
|
115
|
+
return { processed, failed };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Convert a Cloudflare message to QueueMessage format
|
|
119
|
+
*/
|
|
120
|
+
toQueueMessage(msg) {
|
|
121
|
+
return {
|
|
122
|
+
id: msg.id,
|
|
123
|
+
body: msg.body,
|
|
124
|
+
timestamp: msg.timestamp,
|
|
125
|
+
attempts: msg.attempts
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
function createCloudflareQueueAdapter(config) {
|
|
130
|
+
return new CloudflareQueueAdapter(config);
|
|
131
|
+
}
|
|
132
|
+
function createCloudflareQueueProcessor() {
|
|
133
|
+
return new CloudflareQueueProcessor();
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
CloudflareQueueAdapter,
|
|
137
|
+
CloudflareQueueProcessor,
|
|
138
|
+
createCloudflareQueueAdapter,
|
|
139
|
+
createCloudflareQueueProcessor
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/types.ts","../../src/adapters/cloudflare.ts"],"sourcesContent":["/**\n * @parsrun/queue - Type Definitions\n * Queue types and interfaces\n */\n\n// Re-export types from @parsrun/types for convenience\nexport {\n type,\n jobStatus,\n job,\n jobOptions,\n addJobRequest,\n jobProgressUpdate,\n queueStats as parsQueueStats,\n queueListOptions,\n redisQueueConfig,\n workerOptions,\n queueConfig,\n type JobStatus,\n type Job,\n type JobOptions,\n type AddJobRequest,\n type JobProgressUpdate,\n type QueueStats as ParsQueueStats,\n type QueueListOptions,\n type RedisQueueConfig,\n type WorkerOptions,\n type QueueConfig,\n} from \"@parsrun/types\";\n\n/**\n * Queue adapter type\n */\nexport type QueueAdapterType = \"memory\" | \"cloudflare\" | \"qstash\";\n\n/**\n * Message payload\n */\nexport interface QueueMessage<T = unknown> {\n /** Unique message ID */\n id: string;\n /** Message payload */\n body: T;\n /** Message timestamp */\n timestamp: Date;\n /** Number of delivery attempts */\n attempts: number;\n /** Optional delay before processing (seconds) */\n delaySeconds?: number | undefined;\n /** Optional deduplication ID */\n deduplicationId?: string | undefined;\n /** Custom metadata */\n metadata?: Record<string, unknown> | undefined;\n}\n\n/**\n * Send message options\n */\nexport interface SendMessageOptions {\n /** Delay before message is available (seconds) */\n delaySeconds?: number | undefined;\n /** Deduplication ID (prevents duplicate processing) */\n deduplicationId?: string | undefined;\n /** Custom metadata */\n metadata?: Record<string, unknown> | undefined;\n /** Priority (higher = more important) */\n priority?: number | undefined;\n}\n\n/**\n * Batch send result\n */\nexport interface BatchSendResult {\n /** Total messages sent */\n total: number;\n /** Successfully sent */\n successful: number;\n /** Failed to send */\n failed: number;\n /** Individual message IDs */\n messageIds: string[];\n /** Failed messages with errors */\n errors: Array<{ index: number; error: string }>;\n}\n\n/**\n * Message handler function\n */\nexport type MessageHandler<T = unknown> = (\n message: QueueMessage<T>\n) => void | Promise<void>;\n\n/**\n * Consumer options\n */\nexport interface ConsumerOptions {\n /** Maximum messages to process per batch */\n batchSize?: number | undefined;\n /** Visibility timeout (seconds) - how long a message is hidden while processing */\n visibilityTimeout?: number | undefined;\n /** Polling interval (ms) for pull-based queues */\n pollingInterval?: number | undefined;\n /** Maximum retries before dead-letter */\n maxRetries?: number | undefined;\n /** Concurrency - how many messages to process in parallel */\n concurrency?: number | undefined;\n}\n\n/**\n * Queue adapter interface\n */\nexport interface QueueAdapter<T = unknown> {\n /** Adapter type */\n readonly type: QueueAdapterType;\n\n /** Queue name */\n readonly name: string;\n\n /**\n * Send a message to the queue\n */\n send(body: T, options?: SendMessageOptions): Promise<string>;\n\n /**\n * Send multiple messages at once\n */\n sendBatch?(messages: Array<{ body: T; options?: SendMessageOptions }>): Promise<BatchSendResult>;\n\n /**\n * Receive messages from the queue (pull-based)\n * Used for manual message processing\n */\n receive?(maxMessages?: number, visibilityTimeout?: number): Promise<QueueMessage<T>[]>;\n\n /**\n * Acknowledge message processing (mark as complete)\n */\n ack?(messageId: string): Promise<void>;\n\n /**\n * Acknowledge multiple messages\n */\n ackBatch?(messageIds: string[]): Promise<void>;\n\n /**\n * Return message to queue (negative acknowledgement)\n * Optionally with delay\n */\n nack?(messageId: string, delaySeconds?: number): Promise<void>;\n\n /**\n * Start consuming messages (push-based)\n * For adapters that support push-based processing\n */\n consume?(handler: MessageHandler<T>, options?: ConsumerOptions): Promise<void>;\n\n /**\n * Stop consuming messages\n */\n stopConsuming?(): Promise<void>;\n\n /**\n * Get queue statistics\n */\n getStats?(): Promise<QueueStats>;\n\n /**\n * Purge all messages from queue\n */\n purge?(): Promise<void>;\n\n /**\n * Close/cleanup adapter resources\n */\n close?(): Promise<void>;\n}\n\n/**\n * Queue statistics\n */\nexport interface QueueStats {\n /** Approximate number of messages in queue */\n messageCount: number;\n /** Messages currently being processed */\n inFlightCount?: number | undefined;\n /** Messages in dead-letter queue */\n deadLetterCount?: number | undefined;\n}\n\n/**\n * Queue service configuration\n */\nexport interface QueueServiceConfig<T = unknown> {\n /** Queue adapter to use */\n adapter: QueueAdapter<T>;\n /** Enable debug logging */\n debug?: boolean | undefined;\n}\n\n/**\n * Memory queue configuration\n */\nexport interface MemoryQueueConfig {\n /** Queue name */\n name: string;\n /** Maximum queue size (default: unlimited) */\n maxSize?: number | undefined;\n /** Default visibility timeout (seconds) */\n visibilityTimeout?: number | undefined;\n}\n\n/**\n * Cloudflare Queue configuration\n */\nexport interface CloudflareQueueConfig {\n /** Queue binding from environment */\n queue: CloudflareQueue;\n}\n\n/**\n * Cloudflare Queue interface (from Workers runtime)\n */\nexport interface CloudflareQueue<T = unknown> {\n send(message: T, options?: { delaySeconds?: number | undefined; contentType?: string | undefined }): Promise<void>;\n sendBatch(messages: Array<{ body: T; delaySeconds?: number | undefined; contentType?: string | undefined }>): Promise<void>;\n}\n\n/**\n * Cloudflare Queue batch\n */\nexport interface CloudflareMessageBatch<T = unknown> {\n readonly queue: string;\n readonly messages: Array<CloudflareMessage<T>>;\n ackAll(): void;\n retryAll(): void;\n}\n\n/**\n * Cloudflare Queue message\n */\nexport interface CloudflareMessage<T = unknown> {\n readonly id: string;\n readonly timestamp: Date;\n readonly body: T;\n readonly attempts: number;\n ack(): void;\n retry(): void;\n}\n\n/**\n * QStash configuration\n */\nexport interface QStashConfig {\n /** QStash token */\n token: string;\n /** Destination URL for message delivery */\n destinationUrl: string;\n /** Current request URL (for signature verification) */\n currentSigningKey?: string | undefined;\n /** Next signing key (for signature verification) */\n nextSigningKey?: string | undefined;\n}\n\n/**\n * Queue error\n */\nexport class QueueError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = \"QueueError\";\n }\n}\n\n/**\n * Common queue error codes\n */\nexport const QueueErrorCodes = {\n SEND_FAILED: \"SEND_FAILED\",\n RECEIVE_FAILED: \"RECEIVE_FAILED\",\n ACK_FAILED: \"ACK_FAILED\",\n INVALID_CONFIG: \"INVALID_CONFIG\",\n QUEUE_FULL: \"QUEUE_FULL\",\n MESSAGE_NOT_FOUND: \"MESSAGE_NOT_FOUND\",\n NOT_IMPLEMENTED: \"NOT_IMPLEMENTED\",\n} as const;\n","/**\n * @parsrun/queue - Cloudflare Queues Adapter\n * Adapter for Cloudflare Workers Queues\n */\n\nimport type {\n BatchSendResult,\n CloudflareMessage,\n CloudflareMessageBatch,\n CloudflareQueue,\n CloudflareQueueConfig,\n QueueAdapter,\n QueueMessage,\n SendMessageOptions,\n} from \"../types.js\";\nimport { QueueError, QueueErrorCodes } from \"../types.js\";\n\n/**\n * Cloudflare Queue Adapter\n * Uses Cloudflare Workers Queues for serverless message processing\n *\n * Cloudflare Queues uses a push-based model where messages are delivered\n * to queue consumers via Workers.\n *\n * @example\n * ```typescript\n * // In your Worker\n * export default {\n * async fetch(request, env) {\n * const queue = new CloudflareQueueAdapter({\n * queue: env.MY_QUEUE,\n * });\n *\n * await queue.send({ userId: '123', action: 'welcome-email' });\n * return new Response('Queued');\n * },\n *\n * async queue(batch, env) {\n * // Process messages delivered by Cloudflare\n * const processor = new CloudflareQueueProcessor();\n * await processor.processBatch(batch, async (msg) => {\n * console.log('Processing:', msg.body);\n * });\n * }\n * }\n * ```\n */\nexport class CloudflareQueueAdapter<T = unknown> implements QueueAdapter<T> {\n readonly type = \"cloudflare\" as const;\n readonly name = \"cloudflare-queue\";\n\n private queue: CloudflareQueue<T>;\n\n constructor(config: CloudflareQueueConfig) {\n this.queue = config.queue as CloudflareQueue<T>;\n }\n\n async send(body: T, options?: SendMessageOptions): Promise<string> {\n try {\n await this.queue.send(body, {\n delaySeconds: options?.delaySeconds,\n });\n\n // Cloudflare Queues don't return message IDs on send\n // Generate a client-side ID for tracking\n return `cf-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n } catch (err) {\n throw new QueueError(\n `Cloudflare Queue send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n QueueErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n async sendBatch(\n messages: Array<{ body: T; options?: SendMessageOptions }>\n ): Promise<BatchSendResult> {\n try {\n const batchMessages = messages.map((m) => ({\n body: m.body,\n delaySeconds: m.options?.delaySeconds,\n }));\n\n await this.queue.sendBatch(batchMessages);\n\n // Generate client-side IDs\n const messageIds = messages.map(\n () => `cf-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n );\n\n return {\n total: messages.length,\n successful: messages.length,\n failed: 0,\n messageIds,\n errors: [],\n };\n } catch (err) {\n throw new QueueError(\n `Cloudflare Queue batch send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n QueueErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n // Cloudflare Queues are push-based, so receive is not applicable\n // Messages are delivered to queue handlers via Workers\n}\n\n/**\n * Cloudflare Queue Processor\n * Helper for processing queue batches in Workers queue handlers\n *\n * @example\n * ```typescript\n * export default {\n * async queue(batch, env) {\n * const processor = new CloudflareQueueProcessor<MyMessageType>();\n * await processor.processBatch(batch, async (msg) => {\n * // Process each message\n * await handleMessage(msg.body);\n * });\n * }\n * }\n * ```\n */\nexport class CloudflareQueueProcessor<T = unknown> {\n /**\n * Process a batch of messages from Cloudflare Queues\n */\n async processBatch(\n batch: CloudflareMessageBatch<T>,\n handler: (message: QueueMessage<T>) => void | Promise<void>,\n options?: {\n /** Whether to ack all messages at once (default: false - ack individually) */\n ackAll?: boolean;\n /** Whether to retry all on any failure (default: false) */\n retryAllOnFailure?: boolean;\n }\n ): Promise<{ processed: number; failed: number }> {\n let processed = 0;\n let failed = 0;\n\n for (const msg of batch.messages) {\n try {\n const queueMessage: QueueMessage<T> = {\n id: msg.id,\n body: msg.body,\n timestamp: msg.timestamp,\n attempts: msg.attempts,\n };\n\n await handler(queueMessage);\n\n if (!options?.ackAll) {\n msg.ack();\n }\n processed++;\n } catch (err) {\n failed++;\n\n if (options?.retryAllOnFailure) {\n batch.retryAll();\n return { processed, failed: batch.messages.length };\n }\n\n // Retry individual message\n msg.retry();\n }\n }\n\n if (options?.ackAll && failed === 0) {\n batch.ackAll();\n }\n\n return { processed, failed };\n }\n\n /**\n * Convert a Cloudflare message to QueueMessage format\n */\n toQueueMessage(msg: CloudflareMessage<T>): QueueMessage<T> {\n return {\n id: msg.id,\n body: msg.body,\n timestamp: msg.timestamp,\n attempts: msg.attempts,\n };\n }\n}\n\n/**\n * Create a Cloudflare Queue adapter\n */\nexport function createCloudflareQueueAdapter<T = unknown>(\n config: CloudflareQueueConfig\n): CloudflareQueueAdapter<T> {\n return new CloudflareQueueAdapter<T>(config);\n}\n\n/**\n * Create a Cloudflare Queue processor\n */\nexport function createCloudflareQueueProcessor<T = unknown>(): CloudflareQueueProcessor<T> {\n return new CloudflareQueueProcessor<T>();\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { MemoryQueueAdapter, createMemoryQueueAdapter } from './memory.js';
|
|
2
|
+
export { CloudflareQueueAdapter, CloudflareQueueProcessor, createCloudflareQueueAdapter, createCloudflareQueueProcessor } from './cloudflare.js';
|
|
3
|
+
export { QStashAdapter, QStashReceiver, createQStashAdapter, createQStashReceiver } from './qstash.js';
|
|
4
|
+
import '../types.js';
|
|
5
|
+
import '@parsrun/types';
|