@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.
@@ -0,0 +1,379 @@
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/qstash.ts
34
+ var QStashAdapter = class {
35
+ type = "qstash";
36
+ name = "qstash";
37
+ token;
38
+ destinationUrl;
39
+ baseUrl = "https://qstash.upstash.io/v2";
40
+ constructor(config) {
41
+ this.token = config.token;
42
+ this.destinationUrl = config.destinationUrl;
43
+ }
44
+ async send(body, options) {
45
+ try {
46
+ const headers = {
47
+ Authorization: `Bearer ${this.token}`,
48
+ "Content-Type": "application/json"
49
+ };
50
+ if (options?.delaySeconds) {
51
+ headers["Upstash-Delay"] = `${options.delaySeconds}s`;
52
+ }
53
+ if (options?.deduplicationId) {
54
+ headers["Upstash-Deduplication-Id"] = options.deduplicationId;
55
+ }
56
+ if (options?.metadata) {
57
+ for (const [key, value] of Object.entries(options.metadata)) {
58
+ if (typeof value === "string") {
59
+ headers[`Upstash-Forward-${key}`] = value;
60
+ }
61
+ }
62
+ }
63
+ const response = await fetch(
64
+ `${this.baseUrl}/publish/${encodeURIComponent(this.destinationUrl)}`,
65
+ {
66
+ method: "POST",
67
+ headers,
68
+ body: JSON.stringify(body)
69
+ }
70
+ );
71
+ if (!response.ok) {
72
+ const error = await response.text();
73
+ throw new Error(`QStash API error: ${error}`);
74
+ }
75
+ const result = await response.json();
76
+ return result.messageId;
77
+ } catch (err) {
78
+ throw new QueueError(
79
+ `QStash send failed: ${err instanceof Error ? err.message : "Unknown error"}`,
80
+ QueueErrorCodes.SEND_FAILED,
81
+ err
82
+ );
83
+ }
84
+ }
85
+ async sendBatch(messages) {
86
+ try {
87
+ const batchMessages = messages.map((m) => {
88
+ const headers = {
89
+ "Content-Type": "application/json"
90
+ };
91
+ if (m.options?.delaySeconds) {
92
+ headers["Upstash-Delay"] = `${m.options.delaySeconds}s`;
93
+ }
94
+ if (m.options?.deduplicationId) {
95
+ headers["Upstash-Deduplication-Id"] = m.options.deduplicationId;
96
+ }
97
+ return {
98
+ destination: this.destinationUrl,
99
+ headers,
100
+ body: JSON.stringify(m.body)
101
+ };
102
+ });
103
+ const response = await fetch(`${this.baseUrl}/batch`, {
104
+ method: "POST",
105
+ headers: {
106
+ Authorization: `Bearer ${this.token}`,
107
+ "Content-Type": "application/json"
108
+ },
109
+ body: JSON.stringify(batchMessages)
110
+ });
111
+ if (!response.ok) {
112
+ const error = await response.text();
113
+ throw new Error(`QStash API error: ${error}`);
114
+ }
115
+ const results = await response.json();
116
+ const messageIds = [];
117
+ const errors = [];
118
+ let successful = 0;
119
+ let failed = 0;
120
+ for (let i = 0; i < results.length; i++) {
121
+ const result = results[i];
122
+ if (result?.messageId) {
123
+ messageIds.push(result.messageId);
124
+ successful++;
125
+ } else {
126
+ failed++;
127
+ errors.push({
128
+ index: i,
129
+ error: result?.error ?? "Unknown error"
130
+ });
131
+ }
132
+ }
133
+ return {
134
+ total: messages.length,
135
+ successful,
136
+ failed,
137
+ messageIds,
138
+ errors
139
+ };
140
+ } catch (err) {
141
+ throw new QueueError(
142
+ `QStash batch send failed: ${err instanceof Error ? err.message : "Unknown error"}`,
143
+ QueueErrorCodes.SEND_FAILED,
144
+ err
145
+ );
146
+ }
147
+ }
148
+ /**
149
+ * Schedule a message for future delivery
150
+ */
151
+ async schedule(body, cronExpression, options) {
152
+ try {
153
+ const headers = {
154
+ Authorization: `Bearer ${this.token}`,
155
+ "Content-Type": "application/json",
156
+ "Upstash-Cron": cronExpression
157
+ };
158
+ if (options?.deduplicationId) {
159
+ headers["Upstash-Deduplication-Id"] = options.deduplicationId;
160
+ }
161
+ const response = await fetch(
162
+ `${this.baseUrl}/schedules/${encodeURIComponent(this.destinationUrl)}`,
163
+ {
164
+ method: "POST",
165
+ headers,
166
+ body: JSON.stringify(body)
167
+ }
168
+ );
169
+ if (!response.ok) {
170
+ const error = await response.text();
171
+ throw new Error(`QStash API error: ${error}`);
172
+ }
173
+ const result = await response.json();
174
+ return result.scheduleId;
175
+ } catch (err) {
176
+ throw new QueueError(
177
+ `QStash schedule failed: ${err instanceof Error ? err.message : "Unknown error"}`,
178
+ QueueErrorCodes.SEND_FAILED,
179
+ err
180
+ );
181
+ }
182
+ }
183
+ /**
184
+ * Delete a scheduled message
185
+ */
186
+ async deleteSchedule(scheduleId) {
187
+ try {
188
+ const response = await fetch(`${this.baseUrl}/schedules/${scheduleId}`, {
189
+ method: "DELETE",
190
+ headers: {
191
+ Authorization: `Bearer ${this.token}`
192
+ }
193
+ });
194
+ if (!response.ok) {
195
+ const error = await response.text();
196
+ throw new Error(`QStash API error: ${error}`);
197
+ }
198
+ } catch (err) {
199
+ throw new QueueError(
200
+ `QStash delete schedule failed: ${err instanceof Error ? err.message : "Unknown error"}`,
201
+ QueueErrorCodes.SEND_FAILED,
202
+ err
203
+ );
204
+ }
205
+ }
206
+ /**
207
+ * List all schedules
208
+ */
209
+ async listSchedules() {
210
+ try {
211
+ const response = await fetch(`${this.baseUrl}/schedules`, {
212
+ headers: {
213
+ Authorization: `Bearer ${this.token}`
214
+ }
215
+ });
216
+ if (!response.ok) {
217
+ const error = await response.text();
218
+ throw new Error(`QStash API error: ${error}`);
219
+ }
220
+ return await response.json();
221
+ } catch (err) {
222
+ throw new QueueError(
223
+ `QStash list schedules failed: ${err instanceof Error ? err.message : "Unknown error"}`,
224
+ QueueErrorCodes.SEND_FAILED,
225
+ err
226
+ );
227
+ }
228
+ }
229
+ };
230
+ var QStashReceiver = class {
231
+ currentSigningKey;
232
+ nextSigningKey;
233
+ constructor(config) {
234
+ this.currentSigningKey = config.currentSigningKey;
235
+ this.nextSigningKey = config.nextSigningKey;
236
+ }
237
+ /**
238
+ * Verify a request from QStash and extract the message
239
+ */
240
+ async verify(request) {
241
+ const signature = request.headers.get("Upstash-Signature");
242
+ if (!signature) {
243
+ return null;
244
+ }
245
+ const body = await request.text();
246
+ const isValid = await this.verifySignature(body, signature, this.currentSigningKey) || await this.verifySignature(body, signature, this.nextSigningKey);
247
+ if (!isValid) {
248
+ return null;
249
+ }
250
+ const messageId = request.headers.get("Upstash-Message-Id") ?? `qstash-${Date.now()}`;
251
+ const retryCount = parseInt(
252
+ request.headers.get("Upstash-Retried") ?? "0",
253
+ 10
254
+ );
255
+ let parsedBody;
256
+ try {
257
+ parsedBody = JSON.parse(body);
258
+ } catch {
259
+ parsedBody = body;
260
+ }
261
+ return {
262
+ id: messageId,
263
+ body: parsedBody,
264
+ timestamp: /* @__PURE__ */ new Date(),
265
+ attempts: retryCount + 1
266
+ };
267
+ }
268
+ async verifySignature(body, signature, key) {
269
+ try {
270
+ const parts = signature.split(".");
271
+ if (parts.length !== 3) {
272
+ return false;
273
+ }
274
+ const [headerB64, payloadB64, signatureB64] = parts;
275
+ if (!headerB64 || !payloadB64 || !signatureB64) {
276
+ return false;
277
+ }
278
+ const encoder = new TextEncoder();
279
+ const keyData = encoder.encode(key);
280
+ const cryptoKey = await crypto.subtle.importKey(
281
+ "raw",
282
+ keyData,
283
+ { name: "HMAC", hash: "SHA-256" },
284
+ false,
285
+ ["verify"]
286
+ );
287
+ const signatureData = this.base64UrlDecode(signatureB64);
288
+ const dataToVerify = encoder.encode(`${headerB64}.${payloadB64}`);
289
+ const isValid = await crypto.subtle.verify(
290
+ "HMAC",
291
+ cryptoKey,
292
+ signatureData,
293
+ dataToVerify
294
+ );
295
+ if (!isValid) {
296
+ return false;
297
+ }
298
+ const payload = JSON.parse(atob(payloadB64));
299
+ const now = Math.floor(Date.now() / 1e3);
300
+ if (payload.exp && payload.exp < now) {
301
+ return false;
302
+ }
303
+ if (payload.nbf && payload.nbf > now) {
304
+ return false;
305
+ }
306
+ if (payload.body) {
307
+ const bodyHash = await this.sha256(body);
308
+ const expectedHash = this.base64UrlEncode(
309
+ new Uint8Array(
310
+ atob(payload.body).split("").map((c) => c.charCodeAt(0))
311
+ )
312
+ );
313
+ if (bodyHash !== expectedHash && payload.body !== bodyHash) {
314
+ const directHash = await this.sha256Base64(body);
315
+ if (directHash !== payload.body) {
316
+ return false;
317
+ }
318
+ }
319
+ }
320
+ return true;
321
+ } catch {
322
+ return false;
323
+ }
324
+ }
325
+ base64UrlDecode(str) {
326
+ const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
327
+ const padding = (4 - base64.length % 4) % 4;
328
+ const padded = base64 + "=".repeat(padding);
329
+ const binary = atob(padded);
330
+ const bytes = new Uint8Array(binary.length);
331
+ for (let i = 0; i < binary.length; i++) {
332
+ bytes[i] = binary.charCodeAt(i);
333
+ }
334
+ return bytes;
335
+ }
336
+ base64UrlEncode(data) {
337
+ let binary = "";
338
+ for (let i = 0; i < data.length; i++) {
339
+ const byte = data[i];
340
+ if (byte !== void 0) {
341
+ binary += String.fromCharCode(byte);
342
+ }
343
+ }
344
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
345
+ }
346
+ async sha256(message) {
347
+ const encoder = new TextEncoder();
348
+ const data = encoder.encode(message);
349
+ const hash = await crypto.subtle.digest("SHA-256", data);
350
+ return this.base64UrlEncode(new Uint8Array(hash));
351
+ }
352
+ async sha256Base64(message) {
353
+ const encoder = new TextEncoder();
354
+ const data = encoder.encode(message);
355
+ const hash = await crypto.subtle.digest("SHA-256", data);
356
+ let binary = "";
357
+ const bytes = new Uint8Array(hash);
358
+ for (let i = 0; i < bytes.length; i++) {
359
+ const byte = bytes[i];
360
+ if (byte !== void 0) {
361
+ binary += String.fromCharCode(byte);
362
+ }
363
+ }
364
+ return btoa(binary);
365
+ }
366
+ };
367
+ function createQStashAdapter(config) {
368
+ return new QStashAdapter(config);
369
+ }
370
+ function createQStashReceiver(config) {
371
+ return new QStashReceiver(config);
372
+ }
373
+ export {
374
+ QStashAdapter,
375
+ QStashReceiver,
376
+ createQStashAdapter,
377
+ createQStashReceiver
378
+ };
379
+ //# sourceMappingURL=qstash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types.ts","../../src/adapters/qstash.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 - QStash Adapter\n * Edge-compatible Upstash QStash adapter using fetch API\n */\n\nimport type {\n BatchSendResult,\n QStashConfig,\n QueueAdapter,\n QueueMessage,\n SendMessageOptions,\n} from \"../types.js\";\nimport { QueueError, QueueErrorCodes } from \"../types.js\";\n\n/**\n * QStash Queue Adapter\n * Edge-compatible using HTTP API\n *\n * QStash is a serverless message queue that delivers messages via HTTP webhooks.\n * Messages are sent to QStash which then delivers them to your endpoint.\n *\n * @example\n * ```typescript\n * const queue = new QStashAdapter({\n * token: process.env.QSTASH_TOKEN,\n * destinationUrl: 'https://myapp.com/api/webhooks/queue',\n * });\n *\n * await queue.send({ userId: '123', action: 'welcome-email' });\n * ```\n */\nexport class QStashAdapter<T = unknown> implements QueueAdapter<T> {\n readonly type = \"qstash\" as const;\n readonly name = \"qstash\";\n\n private token: string;\n private destinationUrl: string;\n private baseUrl = \"https://qstash.upstash.io/v2\";\n\n constructor(config: QStashConfig) {\n this.token = config.token;\n this.destinationUrl = config.destinationUrl;\n }\n\n async send(body: T, options?: SendMessageOptions): Promise<string> {\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n \"Content-Type\": \"application/json\",\n };\n\n // Add delay\n if (options?.delaySeconds) {\n headers[\"Upstash-Delay\"] = `${options.delaySeconds}s`;\n }\n\n // Add deduplication\n if (options?.deduplicationId) {\n headers[\"Upstash-Deduplication-Id\"] = options.deduplicationId;\n }\n\n // Add custom headers from metadata\n if (options?.metadata) {\n for (const [key, value] of Object.entries(options.metadata)) {\n if (typeof value === \"string\") {\n headers[`Upstash-Forward-${key}`] = value;\n }\n }\n }\n\n const response = await fetch(\n `${this.baseUrl}/publish/${encodeURIComponent(this.destinationUrl)}`,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n }\n );\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`QStash API error: ${error}`);\n }\n\n const result = await response.json() as { messageId: string };\n return result.messageId;\n } catch (err) {\n throw new QueueError(\n `QStash 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 const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (m.options?.delaySeconds) {\n headers[\"Upstash-Delay\"] = `${m.options.delaySeconds}s`;\n }\n\n if (m.options?.deduplicationId) {\n headers[\"Upstash-Deduplication-Id\"] = m.options.deduplicationId;\n }\n\n return {\n destination: this.destinationUrl,\n headers,\n body: JSON.stringify(m.body),\n };\n });\n\n const response = await fetch(`${this.baseUrl}/batch`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(batchMessages),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`QStash API error: ${error}`);\n }\n\n const results = await response.json() as Array<{\n messageId?: string;\n error?: string;\n }>;\n\n const messageIds: string[] = [];\n const errors: Array<{ index: number; error: string }> = [];\n let successful = 0;\n let failed = 0;\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n if (result?.messageId) {\n messageIds.push(result.messageId);\n successful++;\n } else {\n failed++;\n errors.push({\n index: i,\n error: result?.error ?? \"Unknown error\",\n });\n }\n }\n\n return {\n total: messages.length,\n successful,\n failed,\n messageIds,\n errors,\n };\n } catch (err) {\n throw new QueueError(\n `QStash batch send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n QueueErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n /**\n * Schedule a message for future delivery\n */\n async schedule(\n body: T,\n cronExpression: string,\n options?: Omit<SendMessageOptions, \"delaySeconds\">\n ): Promise<string> {\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n \"Content-Type\": \"application/json\",\n \"Upstash-Cron\": cronExpression,\n };\n\n if (options?.deduplicationId) {\n headers[\"Upstash-Deduplication-Id\"] = options.deduplicationId;\n }\n\n const response = await fetch(\n `${this.baseUrl}/schedules/${encodeURIComponent(this.destinationUrl)}`,\n {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n }\n );\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`QStash API error: ${error}`);\n }\n\n const result = await response.json() as { scheduleId: string };\n return result.scheduleId;\n } catch (err) {\n throw new QueueError(\n `QStash schedule failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n QueueErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n /**\n * Delete a scheduled message\n */\n async deleteSchedule(scheduleId: string): Promise<void> {\n try {\n const response = await fetch(`${this.baseUrl}/schedules/${scheduleId}`, {\n method: \"DELETE\",\n headers: {\n Authorization: `Bearer ${this.token}`,\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`QStash API error: ${error}`);\n }\n } catch (err) {\n throw new QueueError(\n `QStash delete schedule failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n QueueErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n /**\n * List all schedules\n */\n async listSchedules(): Promise<\n Array<{\n scheduleId: string;\n cron: string;\n destination: string;\n body?: string;\n createdAt: number;\n }>\n > {\n try {\n const response = await fetch(`${this.baseUrl}/schedules`, {\n headers: {\n Authorization: `Bearer ${this.token}`,\n },\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`QStash API error: ${error}`);\n }\n\n return await response.json() as Array<{\n scheduleId: string;\n cron: string;\n destination: string;\n body?: string;\n createdAt: number;\n }>;\n } catch (err) {\n throw new QueueError(\n `QStash list schedules failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n QueueErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n}\n\n/**\n * QStash Message Receiver\n * Helper for receiving and verifying messages in your webhook endpoint\n *\n * @example\n * ```typescript\n * // In your API route\n * const receiver = new QStashReceiver({\n * currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY,\n * nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY,\n * });\n *\n * export async function POST(request: Request) {\n * const message = await receiver.verify(request);\n * if (!message) {\n * return new Response('Invalid signature', { status: 401 });\n * }\n *\n * // Process the message\n * console.log(message.body);\n * return new Response('OK');\n * }\n * ```\n */\nexport class QStashReceiver<T = unknown> {\n private currentSigningKey: string;\n private nextSigningKey: string;\n\n constructor(config: { currentSigningKey: string; nextSigningKey: string }) {\n this.currentSigningKey = config.currentSigningKey;\n this.nextSigningKey = config.nextSigningKey;\n }\n\n /**\n * Verify a request from QStash and extract the message\n */\n async verify(request: Request): Promise<QueueMessage<T> | null> {\n const signature = request.headers.get(\"Upstash-Signature\");\n\n if (!signature) {\n return null;\n }\n\n const body = await request.text();\n\n // Try current key first, then next key\n const isValid =\n (await this.verifySignature(body, signature, this.currentSigningKey)) ||\n (await this.verifySignature(body, signature, this.nextSigningKey));\n\n if (!isValid) {\n return null;\n }\n\n const messageId = request.headers.get(\"Upstash-Message-Id\") ?? `qstash-${Date.now()}`;\n const retryCount = parseInt(\n request.headers.get(\"Upstash-Retried\") ?? \"0\",\n 10\n );\n\n let parsedBody: T;\n try {\n parsedBody = JSON.parse(body) as T;\n } catch {\n parsedBody = body as T;\n }\n\n return {\n id: messageId,\n body: parsedBody,\n timestamp: new Date(),\n attempts: retryCount + 1,\n };\n }\n\n private async verifySignature(\n body: string,\n signature: string,\n key: string\n ): Promise<boolean> {\n try {\n // Parse JWT-like signature\n const parts = signature.split(\".\");\n if (parts.length !== 3) {\n return false;\n }\n\n const [headerB64, payloadB64, signatureB64] = parts;\n if (!headerB64 || !payloadB64 || !signatureB64) {\n return false;\n }\n\n // Verify signature using Web Crypto API\n const encoder = new TextEncoder();\n const keyData = encoder.encode(key);\n\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n keyData,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"verify\"]\n );\n\n const signatureData = this.base64UrlDecode(signatureB64);\n const dataToVerify = encoder.encode(`${headerB64}.${payloadB64}`);\n\n const isValid = await crypto.subtle.verify(\n \"HMAC\",\n cryptoKey,\n signatureData,\n dataToVerify\n );\n\n if (!isValid) {\n return false;\n }\n\n // Verify payload contains correct body hash\n const payload = JSON.parse(atob(payloadB64)) as {\n body?: string;\n iss?: string;\n sub?: string;\n exp?: number;\n nbf?: number;\n iat?: number;\n };\n\n // Check expiration\n const now = Math.floor(Date.now() / 1000);\n if (payload.exp && payload.exp < now) {\n return false;\n }\n\n // Check not before\n if (payload.nbf && payload.nbf > now) {\n return false;\n }\n\n // Verify body hash\n if (payload.body) {\n const bodyHash = await this.sha256(body);\n const expectedHash = this.base64UrlEncode(\n new Uint8Array(\n atob(payload.body)\n .split(\"\")\n .map((c) => c.charCodeAt(0))\n )\n );\n\n if (bodyHash !== expectedHash && payload.body !== bodyHash) {\n // Also try direct comparison\n const directHash = await this.sha256Base64(body);\n if (directHash !== payload.body) {\n return false;\n }\n }\n }\n\n return true;\n } catch {\n return false;\n }\n }\n\n private base64UrlDecode(str: string): Uint8Array {\n const base64 = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padding = (4 - (base64.length % 4)) % 4;\n const padded = base64 + \"=\".repeat(padding);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n }\n\n private base64UrlEncode(data: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n if (byte !== undefined) {\n binary += String.fromCharCode(byte);\n }\n }\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n }\n\n private async sha256(message: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n return this.base64UrlEncode(new Uint8Array(hash));\n }\n\n private async sha256Base64(message: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(message);\n const hash = await crypto.subtle.digest(\"SHA-256\", data);\n let binary = \"\";\n const bytes = new Uint8Array(hash);\n for (let i = 0; i < bytes.length; i++) {\n const byte = bytes[i];\n if (byte !== undefined) {\n binary += String.fromCharCode(byte);\n }\n }\n return btoa(binary);\n }\n}\n\n/**\n * Create a QStash queue adapter\n */\nexport function createQStashAdapter<T = unknown>(\n config: QStashConfig\n): QStashAdapter<T> {\n return new QStashAdapter<T>(config);\n}\n\n/**\n * Create a QStash receiver for webhook verification\n */\nexport function createQStashReceiver<T = unknown>(config: {\n currentSigningKey: string;\n nextSigningKey: string;\n}): QStashReceiver<T> {\n return new QStashReceiver<T>(config);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,123 @@
1
+ import { QueueServiceConfig, SendMessageOptions, BatchSendResult, QueueMessage, MessageHandler, ConsumerOptions, QueueStats } from './types.js';
2
+ export { CloudflareMessage, CloudflareMessageBatch, CloudflareQueue, CloudflareQueueConfig, MemoryQueueConfig, QStashConfig, QueueAdapter, QueueAdapterType, QueueError, QueueErrorCodes } from './types.js';
3
+ export { MemoryQueueAdapter, createMemoryQueueAdapter } from './adapters/memory.js';
4
+ export { CloudflareQueueAdapter, CloudflareQueueProcessor, createCloudflareQueueAdapter, createCloudflareQueueProcessor } from './adapters/cloudflare.js';
5
+ export { QStashAdapter, QStashReceiver, createQStashAdapter, createQStashReceiver } from './adapters/qstash.js';
6
+ export { AddJobRequest, Job, JobOptions, JobProgressUpdate, JobStatus, QueueStats as ParsQueueStats, QueueConfig, QueueListOptions, RedisQueueConfig, WorkerOptions, addJobRequest, job, jobOptions, jobProgressUpdate, jobStatus, queueStats as parsQueueStats, queueConfig, queueListOptions, redisQueueConfig, type, workerOptions } from '@parsrun/types';
7
+
8
+ /**
9
+ * @parsrun/queue
10
+ * Edge-compatible message queues for Pars
11
+ *
12
+ * Supports multiple adapters:
13
+ * - Memory (development)
14
+ * - Cloudflare Queues (Workers)
15
+ * - Upstash QStash (Edge/Serverless)
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { createQueueService, createMemoryQueueAdapter } from '@parsrun/queue';
20
+ *
21
+ * const queue = createQueueService({
22
+ * adapter: createMemoryQueueAdapter({ name: 'tasks' }),
23
+ * });
24
+ *
25
+ * await queue.send({ userId: '123', action: 'send-email' });
26
+ * ```
27
+ */
28
+
29
+ /**
30
+ * Queue Service
31
+ * High-level queue service with adapter abstraction
32
+ */
33
+ declare class QueueService<T = unknown> {
34
+ private adapter;
35
+ private debug;
36
+ constructor(config: QueueServiceConfig<T>);
37
+ /**
38
+ * Get adapter type
39
+ */
40
+ get adapterType(): string;
41
+ /**
42
+ * Get queue name
43
+ */
44
+ get name(): string;
45
+ /**
46
+ * Send a message to the queue
47
+ */
48
+ send(body: T, options?: SendMessageOptions): Promise<string>;
49
+ /**
50
+ * Send multiple messages at once
51
+ */
52
+ sendBatch(messages: Array<{
53
+ body: T;
54
+ options?: SendMessageOptions;
55
+ }>): Promise<BatchSendResult>;
56
+ /**
57
+ * Receive messages from the queue (pull-based)
58
+ */
59
+ receive(maxMessages?: number, visibilityTimeout?: number): Promise<QueueMessage<T>[]>;
60
+ /**
61
+ * Acknowledge message processing (mark as complete)
62
+ */
63
+ ack(messageId: string): Promise<void>;
64
+ /**
65
+ * Acknowledge multiple messages
66
+ */
67
+ ackBatch(messageIds: string[]): Promise<void>;
68
+ /**
69
+ * Return message to queue (negative acknowledgement)
70
+ */
71
+ nack(messageId: string, delaySeconds?: number): Promise<void>;
72
+ /**
73
+ * Start consuming messages (push-based)
74
+ */
75
+ consume(handler: MessageHandler<T>, options?: ConsumerOptions): Promise<void>;
76
+ /**
77
+ * Stop consuming messages
78
+ */
79
+ stopConsuming(): Promise<void>;
80
+ /**
81
+ * Get queue statistics
82
+ */
83
+ getStats(): Promise<QueueStats>;
84
+ /**
85
+ * Purge all messages from queue
86
+ */
87
+ purge(): Promise<void>;
88
+ /**
89
+ * Close/cleanup queue resources
90
+ */
91
+ close(): Promise<void>;
92
+ }
93
+ /**
94
+ * Create a queue service
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * // With Memory (development)
99
+ * const queue = createQueueService({
100
+ * adapter: createMemoryQueueAdapter({ name: 'tasks' }),
101
+ * });
102
+ *
103
+ * // With Cloudflare Queues (Workers)
104
+ * const queue = createQueueService({
105
+ * adapter: createCloudflareQueueAdapter({ queue: env.MY_QUEUE }),
106
+ * });
107
+ *
108
+ * // With QStash (Edge)
109
+ * const queue = createQueueService({
110
+ * adapter: createQStashAdapter({
111
+ * token: process.env.QSTASH_TOKEN,
112
+ * destinationUrl: 'https://myapp.com/api/queue',
113
+ * }),
114
+ * });
115
+ * ```
116
+ */
117
+ declare function createQueueService<T = unknown>(config: QueueServiceConfig<T>): QueueService<T>;
118
+ declare const _default: {
119
+ QueueService: typeof QueueService;
120
+ createQueueService: typeof createQueueService;
121
+ };
122
+
123
+ export { BatchSendResult, ConsumerOptions, MessageHandler, QueueMessage, QueueService, QueueServiceConfig, QueueStats, SendMessageOptions, createQueueService, _default as default };