@cloudwerk/queue 0.0.1
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/LICENSE +21 -0
- package/dist/index.d.ts +723 -0
- package/dist/index.js +268 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Squirrelsoft Dev Tools
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
import { ZodType } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @cloudwerk/queue - Type Definitions
|
|
5
|
+
*
|
|
6
|
+
* Core types for queue producers and consumers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Type that can be either a value or a Promise of that value.
|
|
11
|
+
* Used throughout the queue package for async-friendly APIs.
|
|
12
|
+
*/
|
|
13
|
+
type Awaitable<T> = T | Promise<T>;
|
|
14
|
+
/**
|
|
15
|
+
* Represents a message received from a queue for processing.
|
|
16
|
+
*
|
|
17
|
+
* @typeParam T - The message body type
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* export default defineQueue<EmailMessage>({
|
|
22
|
+
* async process(message) {
|
|
23
|
+
* console.log(message.id) // Unique message ID
|
|
24
|
+
* console.log(message.body) // { to, subject, body }
|
|
25
|
+
* console.log(message.attempts) // Number of delivery attempts
|
|
26
|
+
*
|
|
27
|
+
* await sendEmail(message.body)
|
|
28
|
+
* message.ack() // Acknowledge successful processing
|
|
29
|
+
* }
|
|
30
|
+
* })
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
interface QueueMessage<T = unknown> {
|
|
34
|
+
/** Unique identifier for the message */
|
|
35
|
+
readonly id: string;
|
|
36
|
+
/** The message payload */
|
|
37
|
+
readonly body: T;
|
|
38
|
+
/** When the message was originally sent */
|
|
39
|
+
readonly timestamp: Date;
|
|
40
|
+
/** Number of delivery attempts for this message */
|
|
41
|
+
readonly attempts: number;
|
|
42
|
+
/**
|
|
43
|
+
* Acknowledge successful message processing.
|
|
44
|
+
* The message will be removed from the queue.
|
|
45
|
+
*/
|
|
46
|
+
ack(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Request retry of this message.
|
|
49
|
+
* The message will be requeued with optional delay.
|
|
50
|
+
*
|
|
51
|
+
* @param options - Retry options
|
|
52
|
+
* @param options.delaySeconds - Delay before retry (default: queue's retryDelay)
|
|
53
|
+
*/
|
|
54
|
+
retry(options?: {
|
|
55
|
+
delaySeconds?: number;
|
|
56
|
+
}): void;
|
|
57
|
+
/**
|
|
58
|
+
* Mark this message as failed and send to dead letter queue.
|
|
59
|
+
* Only works if DLQ is configured for this queue.
|
|
60
|
+
*
|
|
61
|
+
* @param reason - Reason for sending to DLQ
|
|
62
|
+
*/
|
|
63
|
+
deadLetter(reason?: string): void;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Message sent to a dead letter queue when processing fails.
|
|
67
|
+
*
|
|
68
|
+
* @typeParam T - The original message body type
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* export default defineQueue<DeadLetterMessage<EmailMessage>>({
|
|
73
|
+
* name: 'email-dlq',
|
|
74
|
+
* async process(message) {
|
|
75
|
+
* // Log failed message for manual inspection
|
|
76
|
+
* await logFailedMessage({
|
|
77
|
+
* originalQueue: message.body.originalQueue,
|
|
78
|
+
* originalMessage: message.body.originalMessage,
|
|
79
|
+
* error: message.body.error,
|
|
80
|
+
* attempts: message.body.attempts,
|
|
81
|
+
* })
|
|
82
|
+
* message.ack()
|
|
83
|
+
* }
|
|
84
|
+
* })
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
interface DeadLetterMessage<T = unknown> {
|
|
88
|
+
/** Name of the original queue that failed processing */
|
|
89
|
+
originalQueue: string;
|
|
90
|
+
/** The original message that failed */
|
|
91
|
+
originalMessage: T;
|
|
92
|
+
/** Error message from the last failure */
|
|
93
|
+
error: string;
|
|
94
|
+
/** Error stack trace (if available) */
|
|
95
|
+
stack?: string;
|
|
96
|
+
/** Number of processing attempts before DLQ */
|
|
97
|
+
attempts: number;
|
|
98
|
+
/** ISO timestamp when the message was sent to DLQ */
|
|
99
|
+
failedAt: string;
|
|
100
|
+
/** Original message ID */
|
|
101
|
+
originalMessageId: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Configuration for queue processing behavior.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* export default defineQueue({
|
|
109
|
+
* config: {
|
|
110
|
+
* batchSize: 10,
|
|
111
|
+
* maxRetries: 5,
|
|
112
|
+
* retryDelay: '2m',
|
|
113
|
+
* deadLetterQueue: 'my-dlq',
|
|
114
|
+
* batchTimeout: '30s',
|
|
115
|
+
* },
|
|
116
|
+
* async process(message) {
|
|
117
|
+
* // ...
|
|
118
|
+
* }
|
|
119
|
+
* })
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
interface QueueProcessingConfig {
|
|
123
|
+
/**
|
|
124
|
+
* Maximum number of messages to deliver in a batch.
|
|
125
|
+
* @default 10
|
|
126
|
+
*/
|
|
127
|
+
batchSize?: number;
|
|
128
|
+
/**
|
|
129
|
+
* Maximum number of retry attempts before sending to DLQ.
|
|
130
|
+
* @default 3
|
|
131
|
+
*/
|
|
132
|
+
maxRetries?: number;
|
|
133
|
+
/**
|
|
134
|
+
* Delay between retries. Supports duration strings like '1m', '30s', '1h'.
|
|
135
|
+
* @default '1m'
|
|
136
|
+
*/
|
|
137
|
+
retryDelay?: string | number;
|
|
138
|
+
/**
|
|
139
|
+
* Dead letter queue name for failed messages.
|
|
140
|
+
* Messages exceeding maxRetries will be sent here.
|
|
141
|
+
*/
|
|
142
|
+
deadLetterQueue?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Maximum time to wait for a batch to fill.
|
|
145
|
+
* Supports duration strings like '5s', '30s'.
|
|
146
|
+
* @default '5s'
|
|
147
|
+
*/
|
|
148
|
+
batchTimeout?: string | number;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Full configuration for defining a queue consumer.
|
|
152
|
+
*
|
|
153
|
+
* @typeParam T - The message body type
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* // Simple queue with single message processing
|
|
158
|
+
* export default defineQueue<EmailMessage>({
|
|
159
|
+
* async process(message) {
|
|
160
|
+
* await sendEmail(message.body)
|
|
161
|
+
* message.ack()
|
|
162
|
+
* }
|
|
163
|
+
* })
|
|
164
|
+
*
|
|
165
|
+
* // Queue with batch processing and configuration
|
|
166
|
+
* export default defineQueue<ImageJob>({
|
|
167
|
+
* config: {
|
|
168
|
+
* batchSize: 50,
|
|
169
|
+
* maxRetries: 5,
|
|
170
|
+
* deadLetterQueue: 'image-dlq',
|
|
171
|
+
* },
|
|
172
|
+
* async processBatch(messages) {
|
|
173
|
+
* const jobs = messages.map(m => m.body)
|
|
174
|
+
* await processImageBatch(jobs)
|
|
175
|
+
* messages.forEach(m => m.ack())
|
|
176
|
+
* }
|
|
177
|
+
* })
|
|
178
|
+
*
|
|
179
|
+
* // Queue with Zod schema validation
|
|
180
|
+
* export default defineQueue({
|
|
181
|
+
* schema: z.object({
|
|
182
|
+
* to: z.string().email(),
|
|
183
|
+
* subject: z.string(),
|
|
184
|
+
* body: z.string(),
|
|
185
|
+
* }),
|
|
186
|
+
* async process(message) {
|
|
187
|
+
* // message.body is validated and typed
|
|
188
|
+
* await sendEmail(message.body)
|
|
189
|
+
* message.ack()
|
|
190
|
+
* }
|
|
191
|
+
* })
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
interface QueueConfig<T = unknown> {
|
|
195
|
+
/**
|
|
196
|
+
* Optional queue name override.
|
|
197
|
+
* By default, the queue name is derived from the filename.
|
|
198
|
+
* - `app/queues/email.ts` -> `email`
|
|
199
|
+
* - `app/queues/image-processing.ts` -> `imageProcessing`
|
|
200
|
+
*/
|
|
201
|
+
name?: string;
|
|
202
|
+
/**
|
|
203
|
+
* Optional Zod schema for runtime validation of message bodies.
|
|
204
|
+
* If provided, messages that fail validation will be rejected.
|
|
205
|
+
*/
|
|
206
|
+
schema?: ZodType<T>;
|
|
207
|
+
/**
|
|
208
|
+
* Queue processing configuration.
|
|
209
|
+
*/
|
|
210
|
+
config?: QueueProcessingConfig;
|
|
211
|
+
/**
|
|
212
|
+
* Process a single message.
|
|
213
|
+
* Called for each message in the batch unless processBatch is defined.
|
|
214
|
+
*
|
|
215
|
+
* @param message - The message to process
|
|
216
|
+
*/
|
|
217
|
+
process?: (message: QueueMessage<T>) => Awaitable<void>;
|
|
218
|
+
/**
|
|
219
|
+
* Process a batch of messages.
|
|
220
|
+
* If defined, this is called instead of process() for the entire batch.
|
|
221
|
+
* More efficient for bulk operations.
|
|
222
|
+
*
|
|
223
|
+
* @param messages - Array of messages to process
|
|
224
|
+
*/
|
|
225
|
+
processBatch?: (messages: QueueMessage<T>[]) => Awaitable<void>;
|
|
226
|
+
/**
|
|
227
|
+
* Error handler for processing failures.
|
|
228
|
+
* Called when process() or processBatch() throws an error.
|
|
229
|
+
*
|
|
230
|
+
* @param error - The error that occurred
|
|
231
|
+
* @param message - The message that was being processed (or first message in batch)
|
|
232
|
+
*/
|
|
233
|
+
onError?: (error: Error, message: QueueMessage<T>) => Awaitable<void>;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* A defined queue consumer, returned by defineQueue().
|
|
237
|
+
*
|
|
238
|
+
* @typeParam T - The message body type
|
|
239
|
+
*/
|
|
240
|
+
interface QueueDefinition<T = unknown> {
|
|
241
|
+
/** Internal marker identifying this as a queue definition */
|
|
242
|
+
readonly __brand: 'cloudwerk-queue';
|
|
243
|
+
/** Queue name (derived from filename or explicitly set) */
|
|
244
|
+
readonly name: string | undefined;
|
|
245
|
+
/** Zod schema for validation (if provided) */
|
|
246
|
+
readonly schema: ZodType<T> | undefined;
|
|
247
|
+
/** Processing configuration */
|
|
248
|
+
readonly config: QueueProcessingConfig;
|
|
249
|
+
/** Single message processor */
|
|
250
|
+
readonly process: ((message: QueueMessage<T>) => Awaitable<void>) | undefined;
|
|
251
|
+
/** Batch message processor */
|
|
252
|
+
readonly processBatch: ((messages: QueueMessage<T>[]) => Awaitable<void>) | undefined;
|
|
253
|
+
/** Error handler */
|
|
254
|
+
readonly onError: ((error: Error, message: QueueMessage<T>) => Awaitable<void>) | undefined;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Options for sending messages to a queue.
|
|
258
|
+
*/
|
|
259
|
+
interface SendOptions {
|
|
260
|
+
/**
|
|
261
|
+
* Delay delivery of this message by the specified number of seconds.
|
|
262
|
+
* The message will not be available for processing until after this delay.
|
|
263
|
+
*/
|
|
264
|
+
delaySeconds?: number;
|
|
265
|
+
/**
|
|
266
|
+
* Content type of the message body.
|
|
267
|
+
* @default 'json'
|
|
268
|
+
*/
|
|
269
|
+
contentType?: 'json' | 'text' | 'bytes' | 'v8';
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* A typed queue producer for sending messages.
|
|
273
|
+
*
|
|
274
|
+
* @typeParam T - The message body type
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* import { queues } from '@cloudwerk/core/bindings'
|
|
279
|
+
*
|
|
280
|
+
* // Send a single message
|
|
281
|
+
* await queues.email.send({
|
|
282
|
+
* to: 'user@example.com',
|
|
283
|
+
* subject: 'Welcome!',
|
|
284
|
+
* body: 'Thanks for signing up.',
|
|
285
|
+
* })
|
|
286
|
+
*
|
|
287
|
+
* // Send with delay
|
|
288
|
+
* await queues.email.send(message, { delaySeconds: 60 })
|
|
289
|
+
*
|
|
290
|
+
* // Send a batch
|
|
291
|
+
* await queues.notifications.sendBatch([
|
|
292
|
+
* { userId: '1', event: 'login' },
|
|
293
|
+
* { userId: '2', event: 'purchase' },
|
|
294
|
+
* ])
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
interface Queue<T = unknown> {
|
|
298
|
+
/**
|
|
299
|
+
* Send a single message to the queue.
|
|
300
|
+
*
|
|
301
|
+
* @param message - The message body to send
|
|
302
|
+
* @param options - Optional send options
|
|
303
|
+
*/
|
|
304
|
+
send(message: T, options?: SendOptions): Promise<void>;
|
|
305
|
+
/**
|
|
306
|
+
* Send multiple messages to the queue in a single operation.
|
|
307
|
+
*
|
|
308
|
+
* @param messages - Array of message bodies to send
|
|
309
|
+
* @param options - Optional send options (applied to all messages)
|
|
310
|
+
*/
|
|
311
|
+
sendBatch(messages: T[], options?: SendOptions): Promise<void>;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* A scanned queue file from the app/queues/ directory.
|
|
315
|
+
*/
|
|
316
|
+
interface ScannedQueue {
|
|
317
|
+
/** Relative path from app/queues/ (e.g., 'email.ts') */
|
|
318
|
+
relativePath: string;
|
|
319
|
+
/** Absolute filesystem path */
|
|
320
|
+
absolutePath: string;
|
|
321
|
+
/** File name without extension (e.g., 'email') */
|
|
322
|
+
name: string;
|
|
323
|
+
/** File extension (e.g., '.ts') */
|
|
324
|
+
extension: string;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Result of scanning the app/queues/ directory.
|
|
328
|
+
*/
|
|
329
|
+
interface QueueScanResult {
|
|
330
|
+
/** All discovered queue files */
|
|
331
|
+
queues: ScannedQueue[];
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* A compiled queue entry in the manifest.
|
|
335
|
+
*/
|
|
336
|
+
interface QueueEntry {
|
|
337
|
+
/** Queue name derived from filename (e.g., 'email', 'imageProcessing') */
|
|
338
|
+
name: string;
|
|
339
|
+
/** Binding name for wrangler.toml (e.g., 'EMAIL_QUEUE') */
|
|
340
|
+
bindingName: string;
|
|
341
|
+
/** Actual queue name in Cloudflare (e.g., 'cloudwerk-email') */
|
|
342
|
+
queueName: string;
|
|
343
|
+
/** Relative path to the queue definition file */
|
|
344
|
+
filePath: string;
|
|
345
|
+
/** Absolute path to the queue definition file */
|
|
346
|
+
absolutePath: string;
|
|
347
|
+
/** Processing configuration from the queue definition */
|
|
348
|
+
config: QueueProcessingConfig;
|
|
349
|
+
/** Whether processBatch is defined */
|
|
350
|
+
hasProcessBatch: boolean;
|
|
351
|
+
/** Whether onError handler is defined */
|
|
352
|
+
hasOnError: boolean;
|
|
353
|
+
/** TypeScript type name for the message body (if extractable) */
|
|
354
|
+
messageType?: string;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Validation error for a queue definition.
|
|
358
|
+
*/
|
|
359
|
+
interface QueueValidationError$1 {
|
|
360
|
+
/** Queue file path */
|
|
361
|
+
file: string;
|
|
362
|
+
/** Error message */
|
|
363
|
+
message: string;
|
|
364
|
+
/** Error code for programmatic handling */
|
|
365
|
+
code: 'NO_HANDLER' | 'INVALID_CONFIG' | 'DUPLICATE_NAME' | 'INVALID_NAME';
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Validation warning for a queue definition.
|
|
369
|
+
*/
|
|
370
|
+
interface QueueValidationWarning {
|
|
371
|
+
/** Queue file path */
|
|
372
|
+
file: string;
|
|
373
|
+
/** Warning message */
|
|
374
|
+
message: string;
|
|
375
|
+
/** Warning code */
|
|
376
|
+
code: 'NO_DLQ' | 'LOW_RETRIES' | 'MISSING_ERROR_HANDLER';
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Complete queue manifest generated during build.
|
|
380
|
+
*/
|
|
381
|
+
interface QueueManifest {
|
|
382
|
+
/** All compiled queue entries */
|
|
383
|
+
queues: QueueEntry[];
|
|
384
|
+
/** Validation errors (queue won't be registered) */
|
|
385
|
+
errors: QueueValidationError$1[];
|
|
386
|
+
/** Validation warnings (queue will be registered with warning) */
|
|
387
|
+
warnings: QueueValidationWarning[];
|
|
388
|
+
/** When the manifest was generated */
|
|
389
|
+
generatedAt: Date;
|
|
390
|
+
/** Root directory of the app */
|
|
391
|
+
rootDir: string;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @cloudwerk/queue - Error Classes
|
|
396
|
+
*
|
|
397
|
+
* Custom error classes for queue processing.
|
|
398
|
+
*/
|
|
399
|
+
/**
|
|
400
|
+
* Base error class for queue-related errors.
|
|
401
|
+
*/
|
|
402
|
+
declare class QueueError extends Error {
|
|
403
|
+
/** Error code for programmatic handling */
|
|
404
|
+
readonly code: string;
|
|
405
|
+
constructor(code: string, message: string, options?: ErrorOptions);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Error thrown when a queue message fails schema validation.
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* ```typescript
|
|
412
|
+
* export default defineQueue({
|
|
413
|
+
* schema: z.object({ email: z.string().email() }),
|
|
414
|
+
* async process(message) {
|
|
415
|
+
* // If message.body doesn't match schema, QueueValidationError is thrown
|
|
416
|
+
* }
|
|
417
|
+
* })
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
declare class QueueValidationError extends QueueError {
|
|
421
|
+
/** The validation errors from Zod */
|
|
422
|
+
readonly validationErrors: unknown[];
|
|
423
|
+
constructor(message: string, validationErrors: unknown[]);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Error thrown when queue message processing fails.
|
|
427
|
+
*/
|
|
428
|
+
declare class QueueProcessingError extends QueueError {
|
|
429
|
+
/** The message ID that failed processing */
|
|
430
|
+
readonly messageId: string;
|
|
431
|
+
/** Number of attempts made */
|
|
432
|
+
readonly attempts: number;
|
|
433
|
+
constructor(message: string, messageId: string, attempts: number, options?: ErrorOptions);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Error thrown when max retries are exceeded.
|
|
437
|
+
*/
|
|
438
|
+
declare class QueueMaxRetriesError extends QueueError {
|
|
439
|
+
/** The message ID that exceeded retries */
|
|
440
|
+
readonly messageId: string;
|
|
441
|
+
/** Maximum retries configured */
|
|
442
|
+
readonly maxRetries: number;
|
|
443
|
+
constructor(messageId: string, maxRetries: number);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Error thrown when queue configuration is invalid.
|
|
447
|
+
*/
|
|
448
|
+
declare class QueueConfigError extends QueueError {
|
|
449
|
+
/** The configuration field that is invalid */
|
|
450
|
+
readonly field?: string;
|
|
451
|
+
constructor(message: string, field?: string);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Error thrown when no message handler is defined.
|
|
455
|
+
*/
|
|
456
|
+
declare class QueueNoHandlerError extends QueueError {
|
|
457
|
+
constructor(queueName: string);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Error thrown when accessing a queue outside of request context.
|
|
461
|
+
*/
|
|
462
|
+
declare class QueueContextError extends QueueError {
|
|
463
|
+
constructor();
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Error thrown when a queue binding is not found.
|
|
467
|
+
*/
|
|
468
|
+
declare class QueueNotFoundError extends QueueError {
|
|
469
|
+
/** The queue name that was not found */
|
|
470
|
+
readonly queueName: string;
|
|
471
|
+
/** Available queue names */
|
|
472
|
+
readonly availableQueues: string[];
|
|
473
|
+
constructor(queueName: string, availableQueues: string[]);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* @cloudwerk/queue - defineQueue()
|
|
478
|
+
*
|
|
479
|
+
* Factory function for creating queue consumer definitions.
|
|
480
|
+
*/
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Parse a duration string into seconds.
|
|
484
|
+
*
|
|
485
|
+
* Supports formats like:
|
|
486
|
+
* - '30s' - 30 seconds
|
|
487
|
+
* - '5m' - 5 minutes
|
|
488
|
+
* - '1h' - 1 hour
|
|
489
|
+
* - 60 - number of seconds
|
|
490
|
+
*
|
|
491
|
+
* @param duration - Duration string or number
|
|
492
|
+
* @returns Duration in seconds
|
|
493
|
+
*/
|
|
494
|
+
declare function parseDuration(duration: string | number): number;
|
|
495
|
+
/**
|
|
496
|
+
* Define a queue consumer.
|
|
497
|
+
*
|
|
498
|
+
* This function creates a queue definition that will be automatically
|
|
499
|
+
* discovered and registered by Cloudwerk during build.
|
|
500
|
+
*
|
|
501
|
+
* @typeParam T - The message body type
|
|
502
|
+
* @param config - Queue configuration
|
|
503
|
+
* @returns Queue definition
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* ```typescript
|
|
507
|
+
* // app/queues/email.ts
|
|
508
|
+
* import { defineQueue } from '@cloudwerk/queue'
|
|
509
|
+
*
|
|
510
|
+
* interface EmailMessage {
|
|
511
|
+
* to: string
|
|
512
|
+
* subject: string
|
|
513
|
+
* body: string
|
|
514
|
+
* }
|
|
515
|
+
*
|
|
516
|
+
* export default defineQueue<EmailMessage>({
|
|
517
|
+
* async process(message) {
|
|
518
|
+
* await sendEmail(message.body)
|
|
519
|
+
* message.ack()
|
|
520
|
+
* }
|
|
521
|
+
* })
|
|
522
|
+
* ```
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* ```typescript
|
|
526
|
+
* // With Zod schema validation
|
|
527
|
+
* import { defineQueue } from '@cloudwerk/queue'
|
|
528
|
+
* import { z } from 'zod'
|
|
529
|
+
*
|
|
530
|
+
* const EmailSchema = z.object({
|
|
531
|
+
* to: z.string().email(),
|
|
532
|
+
* subject: z.string().min(1),
|
|
533
|
+
* body: z.string(),
|
|
534
|
+
* })
|
|
535
|
+
*
|
|
536
|
+
* export default defineQueue({
|
|
537
|
+
* schema: EmailSchema,
|
|
538
|
+
* config: {
|
|
539
|
+
* maxRetries: 5,
|
|
540
|
+
* deadLetterQueue: 'email-dlq',
|
|
541
|
+
* },
|
|
542
|
+
* async process(message) {
|
|
543
|
+
* // message.body is validated and typed as { to: string, subject: string, body: string }
|
|
544
|
+
* await sendEmail(message.body)
|
|
545
|
+
* message.ack()
|
|
546
|
+
* },
|
|
547
|
+
* async onError(error, message) {
|
|
548
|
+
* console.error(`Failed to send email to ${message.body.to}:`, error)
|
|
549
|
+
* }
|
|
550
|
+
* })
|
|
551
|
+
* ```
|
|
552
|
+
*
|
|
553
|
+
* @example
|
|
554
|
+
* ```typescript
|
|
555
|
+
* // Batch processing
|
|
556
|
+
* import { defineQueue } from '@cloudwerk/queue'
|
|
557
|
+
*
|
|
558
|
+
* interface ImageJob {
|
|
559
|
+
* imageId: string
|
|
560
|
+
* operation: 'resize' | 'crop' | 'compress'
|
|
561
|
+
* params: Record<string, unknown>
|
|
562
|
+
* }
|
|
563
|
+
*
|
|
564
|
+
* export default defineQueue<ImageJob>({
|
|
565
|
+
* config: {
|
|
566
|
+
* batchSize: 50,
|
|
567
|
+
* batchTimeout: '30s',
|
|
568
|
+
* },
|
|
569
|
+
* async processBatch(messages) {
|
|
570
|
+
* // Process all images in parallel for efficiency
|
|
571
|
+
* await Promise.all(
|
|
572
|
+
* messages.map(async (msg) => {
|
|
573
|
+
* await processImage(msg.body)
|
|
574
|
+
* msg.ack()
|
|
575
|
+
* })
|
|
576
|
+
* )
|
|
577
|
+
* }
|
|
578
|
+
* })
|
|
579
|
+
* ```
|
|
580
|
+
*/
|
|
581
|
+
declare function defineQueue<T = unknown>(config: QueueConfig<T>): QueueDefinition<T>;
|
|
582
|
+
/**
|
|
583
|
+
* Check if a value is a queue definition created by defineQueue().
|
|
584
|
+
*
|
|
585
|
+
* @param value - Value to check
|
|
586
|
+
* @returns true if value is a QueueDefinition
|
|
587
|
+
*/
|
|
588
|
+
declare function isQueueDefinition(value: unknown): value is QueueDefinition;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* @cloudwerk/queue - Dead Letter Queue Utilities
|
|
592
|
+
*
|
|
593
|
+
* Utilities for working with dead letter queues.
|
|
594
|
+
*/
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Create a dead letter message from a failed queue message.
|
|
598
|
+
*
|
|
599
|
+
* @param originalMessage - The original message that failed processing
|
|
600
|
+
* @param originalQueue - Name of the queue where the message failed
|
|
601
|
+
* @param error - The error that caused the failure
|
|
602
|
+
* @returns Dead letter message ready to be sent to DLQ
|
|
603
|
+
*
|
|
604
|
+
* @example
|
|
605
|
+
* ```typescript
|
|
606
|
+
* import { createDeadLetterMessage } from '@cloudwerk/queue'
|
|
607
|
+
*
|
|
608
|
+
* export default defineQueue<EmailMessage>({
|
|
609
|
+
* config: {
|
|
610
|
+
* deadLetterQueue: 'email-dlq',
|
|
611
|
+
* },
|
|
612
|
+
* async process(message) {
|
|
613
|
+
* try {
|
|
614
|
+
* await sendEmail(message.body)
|
|
615
|
+
* message.ack()
|
|
616
|
+
* } catch (error) {
|
|
617
|
+
* // Message will automatically go to DLQ after max retries
|
|
618
|
+
* // Or manually send to DLQ:
|
|
619
|
+
* message.deadLetter(error.message)
|
|
620
|
+
* }
|
|
621
|
+
* }
|
|
622
|
+
* })
|
|
623
|
+
* ```
|
|
624
|
+
*/
|
|
625
|
+
declare function createDeadLetterMessage<T>(originalMessage: QueueMessage<T>, originalQueue: string, error: Error): DeadLetterMessage<T>;
|
|
626
|
+
/**
|
|
627
|
+
* Configuration for dead letter queue handling.
|
|
628
|
+
*/
|
|
629
|
+
interface DLQConfig {
|
|
630
|
+
/**
|
|
631
|
+
* Name of the dead letter queue.
|
|
632
|
+
*/
|
|
633
|
+
queueName: string;
|
|
634
|
+
/**
|
|
635
|
+
* Maximum retries before sending to DLQ.
|
|
636
|
+
* @default 3
|
|
637
|
+
*/
|
|
638
|
+
maxRetries?: number;
|
|
639
|
+
/**
|
|
640
|
+
* Custom handler called before sending to DLQ.
|
|
641
|
+
* Return false to prevent the message from being sent to DLQ.
|
|
642
|
+
*/
|
|
643
|
+
beforeDLQ?: <T>(message: QueueMessage<T>, error: Error) => Awaitable<boolean | void>;
|
|
644
|
+
/**
|
|
645
|
+
* Custom handler called after sending to DLQ.
|
|
646
|
+
*/
|
|
647
|
+
afterDLQ?: <T>(dlqMessage: DeadLetterMessage<T>) => Awaitable<void>;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Create DLQ configuration with defaults.
|
|
651
|
+
*
|
|
652
|
+
* @param queueName - Name of the dead letter queue
|
|
653
|
+
* @param options - Optional configuration overrides
|
|
654
|
+
* @returns Complete DLQ configuration
|
|
655
|
+
*/
|
|
656
|
+
declare function createDLQConfig(queueName: string, options?: Partial<Omit<DLQConfig, 'queueName'>>): DLQConfig;
|
|
657
|
+
/**
|
|
658
|
+
* Validate DLQ configuration.
|
|
659
|
+
*
|
|
660
|
+
* @param config - DLQ configuration to validate
|
|
661
|
+
* @returns Array of validation error messages (empty if valid)
|
|
662
|
+
*/
|
|
663
|
+
declare function validateDLQConfig(config: DLQConfig): string[];
|
|
664
|
+
/**
|
|
665
|
+
* Check if a message should be sent to DLQ based on attempts.
|
|
666
|
+
*
|
|
667
|
+
* @param message - The queue message
|
|
668
|
+
* @param maxRetries - Maximum retry attempts (default: 3)
|
|
669
|
+
* @returns true if message has exceeded max retries
|
|
670
|
+
*/
|
|
671
|
+
declare function shouldSendToDLQ<T>(message: QueueMessage<T>, maxRetries?: number): boolean;
|
|
672
|
+
/**
|
|
673
|
+
* Extract original message from a DLQ message.
|
|
674
|
+
*
|
|
675
|
+
* @param dlqMessage - Dead letter message
|
|
676
|
+
* @returns The original message body
|
|
677
|
+
*/
|
|
678
|
+
declare function extractOriginalMessage<T>(dlqMessage: DeadLetterMessage<T>): T;
|
|
679
|
+
/**
|
|
680
|
+
* Check if a message is a dead letter message.
|
|
681
|
+
*
|
|
682
|
+
* @param message - Message to check
|
|
683
|
+
* @returns true if message is a DeadLetterMessage
|
|
684
|
+
*/
|
|
685
|
+
declare function isDeadLetterMessage(message: unknown): message is DeadLetterMessage;
|
|
686
|
+
/**
|
|
687
|
+
* Create a DLQ consumer configuration.
|
|
688
|
+
*
|
|
689
|
+
* This is a convenience helper for defining DLQ consumers with appropriate
|
|
690
|
+
* defaults and error handling.
|
|
691
|
+
*
|
|
692
|
+
* @param handler - Handler for processing dead letter messages
|
|
693
|
+
* @returns Queue config for the DLQ consumer
|
|
694
|
+
*
|
|
695
|
+
* @example
|
|
696
|
+
* ```typescript
|
|
697
|
+
* // app/queues/email-dlq.ts
|
|
698
|
+
* import { defineDLQConsumer } from '@cloudwerk/queue'
|
|
699
|
+
*
|
|
700
|
+
* export default defineDLQConsumer<EmailMessage>(async (dlqMessage) => {
|
|
701
|
+
* // Log failed message for manual inspection
|
|
702
|
+
* await logFailedMessage({
|
|
703
|
+
* queue: dlqMessage.originalQueue,
|
|
704
|
+
* error: dlqMessage.error,
|
|
705
|
+
* attempts: dlqMessage.attempts,
|
|
706
|
+
* message: dlqMessage.originalMessage,
|
|
707
|
+
* })
|
|
708
|
+
*
|
|
709
|
+
* // Optionally alert on critical failures
|
|
710
|
+
* if (dlqMessage.attempts > 10) {
|
|
711
|
+
* await sendAlert(`Critical: ${dlqMessage.originalQueue} message failed`)
|
|
712
|
+
* }
|
|
713
|
+
* })
|
|
714
|
+
* ```
|
|
715
|
+
*/
|
|
716
|
+
declare function defineDLQConsumer<T>(handler: (dlqMessage: DeadLetterMessage<T>) => Awaitable<void>): {
|
|
717
|
+
config: {
|
|
718
|
+
maxRetries: number;
|
|
719
|
+
};
|
|
720
|
+
process: (message: QueueMessage<DeadLetterMessage<T>>) => Promise<void>;
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
export { type Awaitable, type DLQConfig, type DeadLetterMessage, type Queue, type QueueConfig, QueueConfigError, QueueContextError, type QueueDefinition, type QueueEntry, QueueError, type QueueManifest, QueueMaxRetriesError, type QueueMessage, QueueNoHandlerError, QueueNotFoundError, type QueueProcessingConfig, QueueProcessingError, type QueueScanResult, QueueValidationError, type QueueValidationWarning, type ScannedQueue, type SendOptions, createDLQConfig, createDeadLetterMessage, defineDLQConsumer, defineQueue, extractOriginalMessage, isDeadLetterMessage, isQueueDefinition, parseDuration, shouldSendToDLQ, validateDLQConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var QueueError = class extends Error {
|
|
3
|
+
/** Error code for programmatic handling */
|
|
4
|
+
code;
|
|
5
|
+
constructor(code, message, options) {
|
|
6
|
+
super(message, options);
|
|
7
|
+
this.name = "QueueError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var QueueValidationError = class extends QueueError {
|
|
12
|
+
/** The validation errors from Zod */
|
|
13
|
+
validationErrors;
|
|
14
|
+
constructor(message, validationErrors) {
|
|
15
|
+
super("VALIDATION_ERROR", message);
|
|
16
|
+
this.name = "QueueValidationError";
|
|
17
|
+
this.validationErrors = validationErrors;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var QueueProcessingError = class extends QueueError {
|
|
21
|
+
/** The message ID that failed processing */
|
|
22
|
+
messageId;
|
|
23
|
+
/** Number of attempts made */
|
|
24
|
+
attempts;
|
|
25
|
+
constructor(message, messageId, attempts, options) {
|
|
26
|
+
super("PROCESSING_ERROR", message, options);
|
|
27
|
+
this.name = "QueueProcessingError";
|
|
28
|
+
this.messageId = messageId;
|
|
29
|
+
this.attempts = attempts;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var QueueMaxRetriesError = class extends QueueError {
|
|
33
|
+
/** The message ID that exceeded retries */
|
|
34
|
+
messageId;
|
|
35
|
+
/** Maximum retries configured */
|
|
36
|
+
maxRetries;
|
|
37
|
+
constructor(messageId, maxRetries) {
|
|
38
|
+
super(
|
|
39
|
+
"MAX_RETRIES_EXCEEDED",
|
|
40
|
+
`Message ${messageId} exceeded maximum retries (${maxRetries})`
|
|
41
|
+
);
|
|
42
|
+
this.name = "QueueMaxRetriesError";
|
|
43
|
+
this.messageId = messageId;
|
|
44
|
+
this.maxRetries = maxRetries;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var QueueConfigError = class extends QueueError {
|
|
48
|
+
/** The configuration field that is invalid */
|
|
49
|
+
field;
|
|
50
|
+
constructor(message, field) {
|
|
51
|
+
super("CONFIG_ERROR", message);
|
|
52
|
+
this.name = "QueueConfigError";
|
|
53
|
+
this.field = field;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var QueueNoHandlerError = class extends QueueError {
|
|
57
|
+
constructor(queueName) {
|
|
58
|
+
super(
|
|
59
|
+
"NO_HANDLER",
|
|
60
|
+
`Queue '${queueName}' must define either process() or processBatch()`
|
|
61
|
+
);
|
|
62
|
+
this.name = "QueueNoHandlerError";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var QueueContextError = class extends QueueError {
|
|
66
|
+
constructor() {
|
|
67
|
+
super(
|
|
68
|
+
"CONTEXT_ERROR",
|
|
69
|
+
"Queue accessed outside of request handler. Queues can only be accessed during request handling."
|
|
70
|
+
);
|
|
71
|
+
this.name = "QueueContextError";
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var QueueNotFoundError = class extends QueueError {
|
|
75
|
+
/** The queue name that was not found */
|
|
76
|
+
queueName;
|
|
77
|
+
/** Available queue names */
|
|
78
|
+
availableQueues;
|
|
79
|
+
constructor(queueName, availableQueues) {
|
|
80
|
+
const available = availableQueues.length > 0 ? `Available queues: ${availableQueues.join(", ")}` : "No queues are configured";
|
|
81
|
+
super(
|
|
82
|
+
"QUEUE_NOT_FOUND",
|
|
83
|
+
`Queue '${queueName}' not found in environment. ${available}`
|
|
84
|
+
);
|
|
85
|
+
this.name = "QueueNotFoundError";
|
|
86
|
+
this.queueName = queueName;
|
|
87
|
+
this.availableQueues = availableQueues;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/define-queue.ts
|
|
92
|
+
var DEFAULT_CONFIG = {
|
|
93
|
+
batchSize: 10,
|
|
94
|
+
maxRetries: 3,
|
|
95
|
+
retryDelay: "1m",
|
|
96
|
+
deadLetterQueue: "",
|
|
97
|
+
batchTimeout: "5s"
|
|
98
|
+
};
|
|
99
|
+
function parseDuration(duration) {
|
|
100
|
+
if (typeof duration === "number") {
|
|
101
|
+
return duration;
|
|
102
|
+
}
|
|
103
|
+
const match = duration.match(/^(\d+)(s|m|h)$/);
|
|
104
|
+
if (!match) {
|
|
105
|
+
throw new QueueConfigError(
|
|
106
|
+
`Invalid duration format: '${duration}'. Expected format like '30s', '5m', or '1h'`,
|
|
107
|
+
"retryDelay"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const value = parseInt(match[1], 10);
|
|
111
|
+
const unit = match[2];
|
|
112
|
+
switch (unit) {
|
|
113
|
+
case "s":
|
|
114
|
+
return value;
|
|
115
|
+
case "m":
|
|
116
|
+
return value * 60;
|
|
117
|
+
case "h":
|
|
118
|
+
return value * 3600;
|
|
119
|
+
default:
|
|
120
|
+
throw new QueueConfigError(`Unknown duration unit: ${unit}`, "retryDelay");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function validateConfig(config) {
|
|
124
|
+
if (!config.process && !config.processBatch) {
|
|
125
|
+
throw new QueueNoHandlerError(config.name || "unknown");
|
|
126
|
+
}
|
|
127
|
+
if (config.config) {
|
|
128
|
+
const { batchSize, maxRetries, retryDelay, batchTimeout } = config.config;
|
|
129
|
+
if (batchSize !== void 0) {
|
|
130
|
+
if (!Number.isInteger(batchSize) || batchSize < 1 || batchSize > 100) {
|
|
131
|
+
throw new QueueConfigError(
|
|
132
|
+
"batchSize must be an integer between 1 and 100",
|
|
133
|
+
"batchSize"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (maxRetries !== void 0) {
|
|
138
|
+
if (!Number.isInteger(maxRetries) || maxRetries < 0 || maxRetries > 100) {
|
|
139
|
+
throw new QueueConfigError(
|
|
140
|
+
"maxRetries must be an integer between 0 and 100",
|
|
141
|
+
"maxRetries"
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (retryDelay !== void 0) {
|
|
146
|
+
parseDuration(retryDelay);
|
|
147
|
+
}
|
|
148
|
+
if (batchTimeout !== void 0) {
|
|
149
|
+
parseDuration(batchTimeout);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (config.name !== void 0) {
|
|
153
|
+
if (typeof config.name !== "string" || config.name.length === 0) {
|
|
154
|
+
throw new QueueConfigError("name must be a non-empty string", "name");
|
|
155
|
+
}
|
|
156
|
+
if (!/^[a-z][a-z0-9-]*$/.test(config.name)) {
|
|
157
|
+
throw new QueueConfigError(
|
|
158
|
+
"name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens",
|
|
159
|
+
"name"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function defineQueue(config) {
|
|
165
|
+
validateConfig(config);
|
|
166
|
+
const mergedConfig = {
|
|
167
|
+
...DEFAULT_CONFIG,
|
|
168
|
+
...config.config
|
|
169
|
+
};
|
|
170
|
+
const definition = {
|
|
171
|
+
__brand: "cloudwerk-queue",
|
|
172
|
+
name: config.name,
|
|
173
|
+
schema: config.schema,
|
|
174
|
+
config: mergedConfig,
|
|
175
|
+
process: config.process,
|
|
176
|
+
processBatch: config.processBatch,
|
|
177
|
+
onError: config.onError
|
|
178
|
+
};
|
|
179
|
+
return definition;
|
|
180
|
+
}
|
|
181
|
+
function isQueueDefinition(value) {
|
|
182
|
+
return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "cloudwerk-queue";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/dlq.ts
|
|
186
|
+
function createDeadLetterMessage(originalMessage, originalQueue, error) {
|
|
187
|
+
return {
|
|
188
|
+
originalQueue,
|
|
189
|
+
originalMessage: originalMessage.body,
|
|
190
|
+
error: error.message,
|
|
191
|
+
stack: error.stack,
|
|
192
|
+
attempts: originalMessage.attempts,
|
|
193
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
194
|
+
originalMessageId: originalMessage.id
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function createDLQConfig(queueName, options) {
|
|
198
|
+
return {
|
|
199
|
+
queueName,
|
|
200
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
201
|
+
beforeDLQ: options?.beforeDLQ,
|
|
202
|
+
afterDLQ: options?.afterDLQ
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function validateDLQConfig(config) {
|
|
206
|
+
const errors = [];
|
|
207
|
+
if (!config.queueName || config.queueName.trim() === "") {
|
|
208
|
+
errors.push("DLQ queue name is required");
|
|
209
|
+
}
|
|
210
|
+
if (config.queueName && !/^[a-z][a-z0-9-]*$/.test(config.queueName)) {
|
|
211
|
+
errors.push(
|
|
212
|
+
"DLQ queue name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (config.maxRetries !== void 0) {
|
|
216
|
+
if (!Number.isInteger(config.maxRetries) || config.maxRetries < 0) {
|
|
217
|
+
errors.push("maxRetries must be a non-negative integer");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return errors;
|
|
221
|
+
}
|
|
222
|
+
function shouldSendToDLQ(message, maxRetries = 3) {
|
|
223
|
+
return message.attempts > maxRetries;
|
|
224
|
+
}
|
|
225
|
+
function extractOriginalMessage(dlqMessage) {
|
|
226
|
+
return dlqMessage.originalMessage;
|
|
227
|
+
}
|
|
228
|
+
function isDeadLetterMessage(message) {
|
|
229
|
+
if (typeof message !== "object" || message === null) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
const dlm = message;
|
|
233
|
+
return typeof dlm.originalQueue === "string" && "originalMessage" in dlm && typeof dlm.error === "string" && typeof dlm.attempts === "number" && typeof dlm.failedAt === "string" && typeof dlm.originalMessageId === "string";
|
|
234
|
+
}
|
|
235
|
+
function defineDLQConsumer(handler) {
|
|
236
|
+
return {
|
|
237
|
+
config: {
|
|
238
|
+
// DLQ consumers typically shouldn't retry much
|
|
239
|
+
// to avoid infinite loops
|
|
240
|
+
maxRetries: 1
|
|
241
|
+
},
|
|
242
|
+
async process(message) {
|
|
243
|
+
await handler(message.body);
|
|
244
|
+
message.ack();
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
export {
|
|
249
|
+
QueueConfigError,
|
|
250
|
+
QueueContextError,
|
|
251
|
+
QueueError,
|
|
252
|
+
QueueMaxRetriesError,
|
|
253
|
+
QueueNoHandlerError,
|
|
254
|
+
QueueNotFoundError,
|
|
255
|
+
QueueProcessingError,
|
|
256
|
+
QueueValidationError,
|
|
257
|
+
createDLQConfig,
|
|
258
|
+
createDeadLetterMessage,
|
|
259
|
+
defineDLQConsumer,
|
|
260
|
+
defineQueue,
|
|
261
|
+
extractOriginalMessage,
|
|
262
|
+
isDeadLetterMessage,
|
|
263
|
+
isQueueDefinition,
|
|
264
|
+
parseDuration,
|
|
265
|
+
shouldSendToDLQ,
|
|
266
|
+
validateDLQConfig
|
|
267
|
+
};
|
|
268
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/define-queue.ts","../src/dlq.ts"],"sourcesContent":["/**\n * @cloudwerk/queue - Error Classes\n *\n * Custom error classes for queue processing.\n */\n\n// ============================================================================\n// Base Error\n// ============================================================================\n\n/**\n * Base error class for queue-related errors.\n */\nexport class QueueError extends Error {\n /** Error code for programmatic handling */\n readonly code: string\n\n constructor(code: string, message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'QueueError'\n this.code = code\n }\n}\n\n// ============================================================================\n// Validation Errors\n// ============================================================================\n\n/**\n * Error thrown when a queue message fails schema validation.\n *\n * @example\n * ```typescript\n * export default defineQueue({\n * schema: z.object({ email: z.string().email() }),\n * async process(message) {\n * // If message.body doesn't match schema, QueueValidationError is thrown\n * }\n * })\n * ```\n */\nexport class QueueValidationError extends QueueError {\n /** The validation errors from Zod */\n readonly validationErrors: unknown[]\n\n constructor(message: string, validationErrors: unknown[]) {\n super('VALIDATION_ERROR', message)\n this.name = 'QueueValidationError'\n this.validationErrors = validationErrors\n }\n}\n\n// ============================================================================\n// Processing Errors\n// ============================================================================\n\n/**\n * Error thrown when queue message processing fails.\n */\nexport class QueueProcessingError extends QueueError {\n /** The message ID that failed processing */\n readonly messageId: string\n\n /** Number of attempts made */\n readonly attempts: number\n\n constructor(\n message: string,\n messageId: string,\n attempts: number,\n options?: ErrorOptions\n ) {\n super('PROCESSING_ERROR', message, options)\n this.name = 'QueueProcessingError'\n this.messageId = messageId\n this.attempts = attempts\n }\n}\n\n/**\n * Error thrown when max retries are exceeded.\n */\nexport class QueueMaxRetriesError extends QueueError {\n /** The message ID that exceeded retries */\n readonly messageId: string\n\n /** Maximum retries configured */\n readonly maxRetries: number\n\n constructor(messageId: string, maxRetries: number) {\n super(\n 'MAX_RETRIES_EXCEEDED',\n `Message ${messageId} exceeded maximum retries (${maxRetries})`\n )\n this.name = 'QueueMaxRetriesError'\n this.messageId = messageId\n this.maxRetries = maxRetries\n }\n}\n\n// ============================================================================\n// Configuration Errors\n// ============================================================================\n\n/**\n * Error thrown when queue configuration is invalid.\n */\nexport class QueueConfigError extends QueueError {\n /** The configuration field that is invalid */\n readonly field?: string\n\n constructor(message: string, field?: string) {\n super('CONFIG_ERROR', message)\n this.name = 'QueueConfigError'\n this.field = field\n }\n}\n\n/**\n * Error thrown when no message handler is defined.\n */\nexport class QueueNoHandlerError extends QueueError {\n constructor(queueName: string) {\n super(\n 'NO_HANDLER',\n `Queue '${queueName}' must define either process() or processBatch()`\n )\n this.name = 'QueueNoHandlerError'\n }\n}\n\n// ============================================================================\n// Runtime Errors\n// ============================================================================\n\n/**\n * Error thrown when accessing a queue outside of request context.\n */\nexport class QueueContextError extends QueueError {\n constructor() {\n super(\n 'CONTEXT_ERROR',\n 'Queue accessed outside of request handler. Queues can only be accessed during request handling.'\n )\n this.name = 'QueueContextError'\n }\n}\n\n/**\n * Error thrown when a queue binding is not found.\n */\nexport class QueueNotFoundError extends QueueError {\n /** The queue name that was not found */\n readonly queueName: string\n\n /** Available queue names */\n readonly availableQueues: string[]\n\n constructor(queueName: string, availableQueues: string[]) {\n const available =\n availableQueues.length > 0\n ? `Available queues: ${availableQueues.join(', ')}`\n : 'No queues are configured'\n\n super(\n 'QUEUE_NOT_FOUND',\n `Queue '${queueName}' not found in environment. ${available}`\n )\n this.name = 'QueueNotFoundError'\n this.queueName = queueName\n this.availableQueues = availableQueues\n }\n}\n","/**\n * @cloudwerk/queue - defineQueue()\n *\n * Factory function for creating queue consumer definitions.\n */\n\nimport type {\n QueueConfig,\n QueueDefinition,\n QueueProcessingConfig,\n} from './types.js'\nimport { QueueConfigError, QueueNoHandlerError } from './errors.js'\n\n// ============================================================================\n// Default Configuration\n// ============================================================================\n\nconst DEFAULT_CONFIG: Required<QueueProcessingConfig> = {\n batchSize: 10,\n maxRetries: 3,\n retryDelay: '1m',\n deadLetterQueue: '',\n batchTimeout: '5s',\n}\n\n// ============================================================================\n// Validation\n// ============================================================================\n\n/**\n * Parse a duration string into seconds.\n *\n * Supports formats like:\n * - '30s' - 30 seconds\n * - '5m' - 5 minutes\n * - '1h' - 1 hour\n * - 60 - number of seconds\n *\n * @param duration - Duration string or number\n * @returns Duration in seconds\n */\nexport function parseDuration(duration: string | number): number {\n if (typeof duration === 'number') {\n return duration\n }\n\n const match = duration.match(/^(\\d+)(s|m|h)$/)\n if (!match) {\n throw new QueueConfigError(\n `Invalid duration format: '${duration}'. Expected format like '30s', '5m', or '1h'`,\n 'retryDelay'\n )\n }\n\n const value = parseInt(match[1], 10)\n const unit = match[2]\n\n switch (unit) {\n case 's':\n return value\n case 'm':\n return value * 60\n case 'h':\n return value * 3600\n default:\n throw new QueueConfigError(`Unknown duration unit: ${unit}`, 'retryDelay')\n }\n}\n\n/**\n * Validate queue configuration.\n *\n * @param config - Queue configuration to validate\n * @throws QueueConfigError if configuration is invalid\n * @throws QueueNoHandlerError if no handler is defined\n */\nfunction validateConfig<T>(config: QueueConfig<T>): void {\n // Must have either process or processBatch\n if (!config.process && !config.processBatch) {\n throw new QueueNoHandlerError(config.name || 'unknown')\n }\n\n // Validate processing config\n if (config.config) {\n const { batchSize, maxRetries, retryDelay, batchTimeout } = config.config\n\n if (batchSize !== undefined) {\n if (!Number.isInteger(batchSize) || batchSize < 1 || batchSize > 100) {\n throw new QueueConfigError(\n 'batchSize must be an integer between 1 and 100',\n 'batchSize'\n )\n }\n }\n\n if (maxRetries !== undefined) {\n if (!Number.isInteger(maxRetries) || maxRetries < 0 || maxRetries > 100) {\n throw new QueueConfigError(\n 'maxRetries must be an integer between 0 and 100',\n 'maxRetries'\n )\n }\n }\n\n if (retryDelay !== undefined) {\n // This will throw if invalid\n parseDuration(retryDelay)\n }\n\n if (batchTimeout !== undefined) {\n // This will throw if invalid\n parseDuration(batchTimeout)\n }\n }\n\n // Validate name if provided\n if (config.name !== undefined) {\n if (typeof config.name !== 'string' || config.name.length === 0) {\n throw new QueueConfigError('name must be a non-empty string', 'name')\n }\n\n // Queue names should be lowercase alphanumeric with hyphens\n if (!/^[a-z][a-z0-9-]*$/.test(config.name)) {\n throw new QueueConfigError(\n 'name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens',\n 'name'\n )\n }\n }\n}\n\n// ============================================================================\n// defineQueue()\n// ============================================================================\n\n/**\n * Define a queue consumer.\n *\n * This function creates a queue definition that will be automatically\n * discovered and registered by Cloudwerk during build.\n *\n * @typeParam T - The message body type\n * @param config - Queue configuration\n * @returns Queue definition\n *\n * @example\n * ```typescript\n * // app/queues/email.ts\n * import { defineQueue } from '@cloudwerk/queue'\n *\n * interface EmailMessage {\n * to: string\n * subject: string\n * body: string\n * }\n *\n * export default defineQueue<EmailMessage>({\n * async process(message) {\n * await sendEmail(message.body)\n * message.ack()\n * }\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With Zod schema validation\n * import { defineQueue } from '@cloudwerk/queue'\n * import { z } from 'zod'\n *\n * const EmailSchema = z.object({\n * to: z.string().email(),\n * subject: z.string().min(1),\n * body: z.string(),\n * })\n *\n * export default defineQueue({\n * schema: EmailSchema,\n * config: {\n * maxRetries: 5,\n * deadLetterQueue: 'email-dlq',\n * },\n * async process(message) {\n * // message.body is validated and typed as { to: string, subject: string, body: string }\n * await sendEmail(message.body)\n * message.ack()\n * },\n * async onError(error, message) {\n * console.error(`Failed to send email to ${message.body.to}:`, error)\n * }\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Batch processing\n * import { defineQueue } from '@cloudwerk/queue'\n *\n * interface ImageJob {\n * imageId: string\n * operation: 'resize' | 'crop' | 'compress'\n * params: Record<string, unknown>\n * }\n *\n * export default defineQueue<ImageJob>({\n * config: {\n * batchSize: 50,\n * batchTimeout: '30s',\n * },\n * async processBatch(messages) {\n * // Process all images in parallel for efficiency\n * await Promise.all(\n * messages.map(async (msg) => {\n * await processImage(msg.body)\n * msg.ack()\n * })\n * )\n * }\n * })\n * ```\n */\nexport function defineQueue<T = unknown>(\n config: QueueConfig<T>\n): QueueDefinition<T> {\n // Validate configuration\n validateConfig(config)\n\n // Merge with defaults\n const mergedConfig: QueueProcessingConfig = {\n ...DEFAULT_CONFIG,\n ...config.config,\n }\n\n // Create the definition object\n const definition: QueueDefinition<T> = {\n __brand: 'cloudwerk-queue',\n name: config.name,\n schema: config.schema,\n config: mergedConfig,\n process: config.process,\n processBatch: config.processBatch,\n onError: config.onError,\n }\n\n return definition\n}\n\n/**\n * Check if a value is a queue definition created by defineQueue().\n *\n * @param value - Value to check\n * @returns true if value is a QueueDefinition\n */\nexport function isQueueDefinition(value: unknown): value is QueueDefinition {\n return (\n typeof value === 'object' &&\n value !== null &&\n '__brand' in value &&\n (value as QueueDefinition).__brand === 'cloudwerk-queue'\n )\n}\n","/**\n * @cloudwerk/queue - Dead Letter Queue Utilities\n *\n * Utilities for working with dead letter queues.\n */\n\nimport type { DeadLetterMessage, QueueMessage, Awaitable } from './types.js'\n\n// ============================================================================\n// DLQ Message Creation\n// ============================================================================\n\n/**\n * Create a dead letter message from a failed queue message.\n *\n * @param originalMessage - The original message that failed processing\n * @param originalQueue - Name of the queue where the message failed\n * @param error - The error that caused the failure\n * @returns Dead letter message ready to be sent to DLQ\n *\n * @example\n * ```typescript\n * import { createDeadLetterMessage } from '@cloudwerk/queue'\n *\n * export default defineQueue<EmailMessage>({\n * config: {\n * deadLetterQueue: 'email-dlq',\n * },\n * async process(message) {\n * try {\n * await sendEmail(message.body)\n * message.ack()\n * } catch (error) {\n * // Message will automatically go to DLQ after max retries\n * // Or manually send to DLQ:\n * message.deadLetter(error.message)\n * }\n * }\n * })\n * ```\n */\nexport function createDeadLetterMessage<T>(\n originalMessage: QueueMessage<T>,\n originalQueue: string,\n error: Error\n): DeadLetterMessage<T> {\n return {\n originalQueue,\n originalMessage: originalMessage.body,\n error: error.message,\n stack: error.stack,\n attempts: originalMessage.attempts,\n failedAt: new Date().toISOString(),\n originalMessageId: originalMessage.id,\n }\n}\n\n// ============================================================================\n// DLQ Configuration\n// ============================================================================\n\n/**\n * Configuration for dead letter queue handling.\n */\nexport interface DLQConfig {\n /**\n * Name of the dead letter queue.\n */\n queueName: string\n\n /**\n * Maximum retries before sending to DLQ.\n * @default 3\n */\n maxRetries?: number\n\n /**\n * Custom handler called before sending to DLQ.\n * Return false to prevent the message from being sent to DLQ.\n */\n beforeDLQ?: <T>(\n message: QueueMessage<T>,\n error: Error\n ) => Awaitable<boolean | void>\n\n /**\n * Custom handler called after sending to DLQ.\n */\n afterDLQ?: <T>(\n dlqMessage: DeadLetterMessage<T>\n ) => Awaitable<void>\n}\n\n/**\n * Create DLQ configuration with defaults.\n *\n * @param queueName - Name of the dead letter queue\n * @param options - Optional configuration overrides\n * @returns Complete DLQ configuration\n */\nexport function createDLQConfig(\n queueName: string,\n options?: Partial<Omit<DLQConfig, 'queueName'>>\n): DLQConfig {\n return {\n queueName,\n maxRetries: options?.maxRetries ?? 3,\n beforeDLQ: options?.beforeDLQ,\n afterDLQ: options?.afterDLQ,\n }\n}\n\n// ============================================================================\n// DLQ Validation\n// ============================================================================\n\n/**\n * Validate DLQ configuration.\n *\n * @param config - DLQ configuration to validate\n * @returns Array of validation error messages (empty if valid)\n */\nexport function validateDLQConfig(config: DLQConfig): string[] {\n const errors: string[] = []\n\n if (!config.queueName || config.queueName.trim() === '') {\n errors.push('DLQ queue name is required')\n }\n\n if (config.queueName && !/^[a-z][a-z0-9-]*$/.test(config.queueName)) {\n errors.push(\n 'DLQ queue name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens'\n )\n }\n\n if (config.maxRetries !== undefined) {\n if (!Number.isInteger(config.maxRetries) || config.maxRetries < 0) {\n errors.push('maxRetries must be a non-negative integer')\n }\n }\n\n return errors\n}\n\n// ============================================================================\n// DLQ Processing Helpers\n// ============================================================================\n\n/**\n * Check if a message should be sent to DLQ based on attempts.\n *\n * @param message - The queue message\n * @param maxRetries - Maximum retry attempts (default: 3)\n * @returns true if message has exceeded max retries\n */\nexport function shouldSendToDLQ<T>(\n message: QueueMessage<T>,\n maxRetries: number = 3\n): boolean {\n return message.attempts > maxRetries\n}\n\n/**\n * Extract original message from a DLQ message.\n *\n * @param dlqMessage - Dead letter message\n * @returns The original message body\n */\nexport function extractOriginalMessage<T>(\n dlqMessage: DeadLetterMessage<T>\n): T {\n return dlqMessage.originalMessage\n}\n\n/**\n * Check if a message is a dead letter message.\n *\n * @param message - Message to check\n * @returns true if message is a DeadLetterMessage\n */\nexport function isDeadLetterMessage(\n message: unknown\n): message is DeadLetterMessage {\n if (typeof message !== 'object' || message === null) {\n return false\n }\n\n const dlm = message as Record<string, unknown>\n return (\n typeof dlm.originalQueue === 'string' &&\n 'originalMessage' in dlm &&\n typeof dlm.error === 'string' &&\n typeof dlm.attempts === 'number' &&\n typeof dlm.failedAt === 'string' &&\n typeof dlm.originalMessageId === 'string'\n )\n}\n\n// ============================================================================\n// DLQ Queue Definition Helper\n// ============================================================================\n\n/**\n * Create a DLQ consumer configuration.\n *\n * This is a convenience helper for defining DLQ consumers with appropriate\n * defaults and error handling.\n *\n * @param handler - Handler for processing dead letter messages\n * @returns Queue config for the DLQ consumer\n *\n * @example\n * ```typescript\n * // app/queues/email-dlq.ts\n * import { defineDLQConsumer } from '@cloudwerk/queue'\n *\n * export default defineDLQConsumer<EmailMessage>(async (dlqMessage) => {\n * // Log failed message for manual inspection\n * await logFailedMessage({\n * queue: dlqMessage.originalQueue,\n * error: dlqMessage.error,\n * attempts: dlqMessage.attempts,\n * message: dlqMessage.originalMessage,\n * })\n *\n * // Optionally alert on critical failures\n * if (dlqMessage.attempts > 10) {\n * await sendAlert(`Critical: ${dlqMessage.originalQueue} message failed`)\n * }\n * })\n * ```\n */\nexport function defineDLQConsumer<T>(\n handler: (dlqMessage: DeadLetterMessage<T>) => Awaitable<void>\n): {\n config: { maxRetries: number }\n process: (message: QueueMessage<DeadLetterMessage<T>>) => Promise<void>\n} {\n return {\n config: {\n // DLQ consumers typically shouldn't retry much\n // to avoid infinite loops\n maxRetries: 1,\n },\n async process(message: QueueMessage<DeadLetterMessage<T>>) {\n await handler(message.body)\n message.ack()\n },\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cloudwerk/queue",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Queue producers and consumers for Cloudwerk",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/squirrelsoft-dev/cloudwerk.git",
|
|
8
|
+
"directory": "packages/queue"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@cloudwerk/core": "0.12.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.4.0",
|
|
25
|
+
"vitest": "^1.0.0",
|
|
26
|
+
"tsup": "^8.0.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"typescript": "^5.0.0",
|
|
30
|
+
"zod": "^3.0.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"zod": {
|
|
34
|
+
"optional": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"test": "vitest --run",
|
|
40
|
+
"test:watch": "vitest"
|
|
41
|
+
}
|
|
42
|
+
}
|