@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 @@
1
+ {"version":3,"sources":["../../src/types.ts","../../src/adapters/memory.ts","../../src/adapters/cloudflare.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 - Memory Adapter\n * In-memory queue adapter for development and testing\n */\n\nimport type {\n BatchSendResult,\n ConsumerOptions,\n MemoryQueueConfig,\n MessageHandler,\n QueueAdapter,\n QueueMessage,\n QueueStats,\n SendMessageOptions,\n} from \"../types.js\";\nimport { QueueError, QueueErrorCodes } from \"../types.js\";\n\ninterface InternalMessage<T> {\n id: string;\n body: T;\n timestamp: Date;\n attempts: number;\n visibleAt: number;\n deduplicationId?: string | undefined;\n metadata?: Record<string, unknown> | undefined;\n}\n\n/**\n * Memory Queue Adapter\n * Uses in-memory storage for development and testing\n *\n * @example\n * ```typescript\n * const queue = new MemoryQueueAdapter<{ userId: string }>({\n * name: 'user-events',\n * });\n *\n * await queue.send({ userId: '123' });\n *\n * // Pull-based processing\n * const messages = await queue.receive(10);\n * for (const msg of messages) {\n * console.log(msg.body);\n * await queue.ack(msg.id);\n * }\n *\n * // Or push-based processing\n * await queue.consume(async (msg) => {\n * console.log(msg.body);\n * });\n * ```\n */\nexport class MemoryQueueAdapter<T = unknown> implements QueueAdapter<T> {\n readonly type = \"memory\" as const;\n readonly name: string;\n\n private messages: InternalMessage<T>[] = [];\n private inFlight = new Map<string, InternalMessage<T>>();\n private processedIds = new Set<string>();\n private maxSize: number;\n private visibilityTimeout: number;\n private messageCounter = 0;\n private isConsuming = false;\n private consumeInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(config: MemoryQueueConfig) {\n this.name = config.name;\n this.maxSize = config.maxSize ?? Infinity;\n this.visibilityTimeout = config.visibilityTimeout ?? 30;\n }\n\n private generateId(): string {\n this.messageCounter++;\n return `msg-${Date.now()}-${this.messageCounter}`;\n }\n\n async send(body: T, options?: SendMessageOptions): Promise<string> {\n // Check max size\n if (this.messages.length >= this.maxSize) {\n throw new QueueError(\n `Queue ${this.name} is full`,\n QueueErrorCodes.QUEUE_FULL\n );\n }\n\n // Check deduplication\n if (options?.deduplicationId && this.processedIds.has(options.deduplicationId)) {\n // Return existing message ID for deduplicated messages\n return `dedup-${options.deduplicationId}`;\n }\n\n const id = this.generateId();\n const now = Date.now();\n const visibleAt = options?.delaySeconds\n ? now + options.delaySeconds * 1000\n : now;\n\n const message: InternalMessage<T> = {\n id,\n body,\n timestamp: new Date(),\n attempts: 0,\n visibleAt,\n deduplicationId: options?.deduplicationId,\n metadata: options?.metadata,\n };\n\n // Insert based on priority (higher priority = earlier in queue)\n if (options?.priority !== undefined && options.priority > 0) {\n // Find position for priority message\n const insertIndex = this.messages.findIndex(\n (m) => (m.metadata?.[\"priority\"] as number | undefined) ?? 0 < (options.priority ?? 0)\n );\n if (insertIndex === -1) {\n this.messages.push(message);\n } else {\n this.messages.splice(insertIndex, 0, message);\n }\n } else {\n this.messages.push(message);\n }\n\n if (options?.deduplicationId) {\n this.processedIds.add(options.deduplicationId);\n }\n\n return id;\n }\n\n async sendBatch(\n messages: Array<{ body: T; options?: SendMessageOptions }>\n ): Promise<BatchSendResult> {\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 < messages.length; i++) {\n const msg = messages[i];\n if (!msg) continue;\n\n try {\n const id = await this.send(msg.body, msg.options);\n messageIds.push(id);\n successful++;\n } catch (err) {\n failed++;\n errors.push({\n index: i,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n }\n }\n\n return {\n total: messages.length,\n successful,\n failed,\n messageIds,\n errors,\n };\n }\n\n async receive(\n maxMessages = 10,\n visibilityTimeoutOverride?: number\n ): Promise<QueueMessage<T>[]> {\n const now = Date.now();\n const timeout = (visibilityTimeoutOverride ?? this.visibilityTimeout) * 1000;\n const result: QueueMessage<T>[] = [];\n\n // Find visible messages\n const visibleMessages: InternalMessage<T>[] = [];\n const remainingMessages: InternalMessage<T>[] = [];\n\n for (const msg of this.messages) {\n if (msg.visibleAt <= now && visibleMessages.length < maxMessages) {\n visibleMessages.push(msg);\n } else {\n remainingMessages.push(msg);\n }\n }\n\n this.messages = remainingMessages;\n\n // Move to in-flight and increment attempts\n for (const msg of visibleMessages) {\n msg.attempts++;\n msg.visibleAt = now + timeout;\n this.inFlight.set(msg.id, msg);\n\n result.push({\n id: msg.id,\n body: msg.body,\n timestamp: msg.timestamp,\n attempts: msg.attempts,\n metadata: msg.metadata,\n });\n }\n\n return result;\n }\n\n async ack(messageId: string): Promise<void> {\n const message = this.inFlight.get(messageId);\n if (!message) {\n throw new QueueError(\n `Message ${messageId} not found in flight`,\n QueueErrorCodes.MESSAGE_NOT_FOUND\n );\n }\n\n this.inFlight.delete(messageId);\n }\n\n async ackBatch(messageIds: string[]): Promise<void> {\n for (const id of messageIds) {\n this.inFlight.delete(id);\n }\n }\n\n async nack(messageId: string, delaySeconds?: number): Promise<void> {\n const message = this.inFlight.get(messageId);\n if (!message) {\n throw new QueueError(\n `Message ${messageId} not found in flight`,\n QueueErrorCodes.MESSAGE_NOT_FOUND\n );\n }\n\n this.inFlight.delete(messageId);\n\n // Return to queue with optional delay\n const now = Date.now();\n message.visibleAt = delaySeconds ? now + delaySeconds * 1000 : now;\n this.messages.push(message);\n }\n\n async consume(\n handler: MessageHandler<T>,\n options?: ConsumerOptions\n ): Promise<void> {\n if (this.isConsuming) {\n return;\n }\n\n this.isConsuming = true;\n const batchSize = options?.batchSize ?? 10;\n const pollingInterval = options?.pollingInterval ?? 1000;\n const visibilityTimeout = options?.visibilityTimeout ?? this.visibilityTimeout;\n const maxRetries = options?.maxRetries ?? 3;\n const concurrency = options?.concurrency ?? 1;\n\n const processMessages = async (): Promise<void> => {\n if (!this.isConsuming) return;\n\n const messages = await this.receive(batchSize, visibilityTimeout);\n\n // Process in batches based on concurrency\n for (let i = 0; i < messages.length; i += concurrency) {\n const batch = messages.slice(i, i + concurrency);\n await Promise.all(\n batch.map(async (msg) => {\n try {\n await handler(msg);\n await this.ack(msg.id);\n } catch (err) {\n // Check retry limit\n if (msg.attempts >= maxRetries) {\n // Dead letter - just remove from queue\n await this.ack(msg.id);\n console.error(\n `[Queue ${this.name}] Message ${msg.id} exceeded max retries, dropped`\n );\n } else {\n // Return to queue for retry\n await this.nack(msg.id, 5); // 5 second retry delay\n }\n }\n })\n );\n }\n };\n\n // Start polling\n this.consumeInterval = setInterval(processMessages, pollingInterval);\n\n // Process immediately\n await processMessages();\n }\n\n async stopConsuming(): Promise<void> {\n this.isConsuming = false;\n if (this.consumeInterval) {\n clearInterval(this.consumeInterval);\n this.consumeInterval = null;\n }\n }\n\n async getStats(): Promise<QueueStats> {\n // Return visibility timeout expired in-flight messages\n const now = Date.now();\n for (const [id, msg] of this.inFlight) {\n if (msg.visibleAt <= now) {\n this.inFlight.delete(id);\n this.messages.push(msg);\n }\n }\n\n return {\n messageCount: this.messages.length,\n inFlightCount: this.inFlight.size,\n };\n }\n\n async purge(): Promise<void> {\n this.messages = [];\n this.inFlight.clear();\n }\n\n async close(): Promise<void> {\n await this.stopConsuming();\n await this.purge();\n this.processedIds.clear();\n }\n}\n\n/**\n * Create a memory queue adapter\n */\nexport function createMemoryQueueAdapter<T = unknown>(\n config: MemoryQueueConfig\n): MemoryQueueAdapter<T> {\n return new MemoryQueueAdapter<T>(config);\n}\n","/**\n * @parsrun/queue - Cloudflare Queues Adapter\n * Adapter for Cloudflare Workers Queues\n */\n\nimport type {\n BatchSendResult,\n CloudflareMessage,\n CloudflareMessageBatch,\n CloudflareQueue,\n CloudflareQueueConfig,\n QueueAdapter,\n QueueMessage,\n SendMessageOptions,\n} from \"../types.js\";\nimport { QueueError, QueueErrorCodes } from \"../types.js\";\n\n/**\n * Cloudflare Queue Adapter\n * Uses Cloudflare Workers Queues for serverless message processing\n *\n * Cloudflare Queues uses a push-based model where messages are delivered\n * to queue consumers via Workers.\n *\n * @example\n * ```typescript\n * // In your Worker\n * export default {\n * async fetch(request, env) {\n * const queue = new CloudflareQueueAdapter({\n * queue: env.MY_QUEUE,\n * });\n *\n * await queue.send({ userId: '123', action: 'welcome-email' });\n * return new Response('Queued');\n * },\n *\n * async queue(batch, env) {\n * // Process messages delivered by Cloudflare\n * const processor = new CloudflareQueueProcessor();\n * await processor.processBatch(batch, async (msg) => {\n * console.log('Processing:', msg.body);\n * });\n * }\n * }\n * ```\n */\nexport class CloudflareQueueAdapter<T = unknown> implements QueueAdapter<T> {\n readonly type = \"cloudflare\" as const;\n readonly name = \"cloudflare-queue\";\n\n private queue: CloudflareQueue<T>;\n\n constructor(config: CloudflareQueueConfig) {\n this.queue = config.queue as CloudflareQueue<T>;\n }\n\n async send(body: T, options?: SendMessageOptions): Promise<string> {\n try {\n await this.queue.send(body, {\n delaySeconds: options?.delaySeconds,\n });\n\n // Cloudflare Queues don't return message IDs on send\n // Generate a client-side ID for tracking\n return `cf-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n } catch (err) {\n throw new QueueError(\n `Cloudflare Queue send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n QueueErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n async sendBatch(\n messages: Array<{ body: T; options?: SendMessageOptions }>\n ): Promise<BatchSendResult> {\n try {\n const batchMessages = messages.map((m) => ({\n body: m.body,\n delaySeconds: m.options?.delaySeconds,\n }));\n\n await this.queue.sendBatch(batchMessages);\n\n // Generate client-side IDs\n const messageIds = messages.map(\n () => `cf-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n );\n\n return {\n total: messages.length,\n successful: messages.length,\n failed: 0,\n messageIds,\n errors: [],\n };\n } catch (err) {\n throw new QueueError(\n `Cloudflare Queue batch send failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n QueueErrorCodes.SEND_FAILED,\n err\n );\n }\n }\n\n // Cloudflare Queues are push-based, so receive is not applicable\n // Messages are delivered to queue handlers via Workers\n}\n\n/**\n * Cloudflare Queue Processor\n * Helper for processing queue batches in Workers queue handlers\n *\n * @example\n * ```typescript\n * export default {\n * async queue(batch, env) {\n * const processor = new CloudflareQueueProcessor<MyMessageType>();\n * await processor.processBatch(batch, async (msg) => {\n * // Process each message\n * await handleMessage(msg.body);\n * });\n * }\n * }\n * ```\n */\nexport class CloudflareQueueProcessor<T = unknown> {\n /**\n * Process a batch of messages from Cloudflare Queues\n */\n async processBatch(\n batch: CloudflareMessageBatch<T>,\n handler: (message: QueueMessage<T>) => void | Promise<void>,\n options?: {\n /** Whether to ack all messages at once (default: false - ack individually) */\n ackAll?: boolean;\n /** Whether to retry all on any failure (default: false) */\n retryAllOnFailure?: boolean;\n }\n ): Promise<{ processed: number; failed: number }> {\n let processed = 0;\n let failed = 0;\n\n for (const msg of batch.messages) {\n try {\n const queueMessage: QueueMessage<T> = {\n id: msg.id,\n body: msg.body,\n timestamp: msg.timestamp,\n attempts: msg.attempts,\n };\n\n await handler(queueMessage);\n\n if (!options?.ackAll) {\n msg.ack();\n }\n processed++;\n } catch (err) {\n failed++;\n\n if (options?.retryAllOnFailure) {\n batch.retryAll();\n return { processed, failed: batch.messages.length };\n }\n\n // Retry individual message\n msg.retry();\n }\n }\n\n if (options?.ackAll && failed === 0) {\n batch.ackAll();\n }\n\n return { processed, failed };\n }\n\n /**\n * Convert a Cloudflare message to QueueMessage format\n */\n toQueueMessage(msg: CloudflareMessage<T>): QueueMessage<T> {\n return {\n id: msg.id,\n body: msg.body,\n timestamp: msg.timestamp,\n attempts: msg.attempts,\n };\n }\n}\n\n/**\n * Create a Cloudflare Queue adapter\n */\nexport function createCloudflareQueueAdapter<T = unknown>(\n config: CloudflareQueueConfig\n): CloudflareQueueAdapter<T> {\n return new CloudflareQueueAdapter<T>(config);\n}\n\n/**\n * Create a Cloudflare Queue processor\n */\nexport function createCloudflareQueueProcessor<T = unknown>(): CloudflareQueueProcessor<T> {\n return new CloudflareQueueProcessor<T>();\n}\n","/**\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,67 @@
1
+ import { QueueAdapter, MemoryQueueConfig, SendMessageOptions, BatchSendResult, QueueMessage, MessageHandler, ConsumerOptions, QueueStats } from '../types.js';
2
+ import '@parsrun/types';
3
+
4
+ /**
5
+ * @parsrun/queue - Memory Adapter
6
+ * In-memory queue adapter for development and testing
7
+ */
8
+
9
+ /**
10
+ * Memory Queue Adapter
11
+ * Uses in-memory storage for development and testing
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const queue = new MemoryQueueAdapter<{ userId: string }>({
16
+ * name: 'user-events',
17
+ * });
18
+ *
19
+ * await queue.send({ userId: '123' });
20
+ *
21
+ * // Pull-based processing
22
+ * const messages = await queue.receive(10);
23
+ * for (const msg of messages) {
24
+ * console.log(msg.body);
25
+ * await queue.ack(msg.id);
26
+ * }
27
+ *
28
+ * // Or push-based processing
29
+ * await queue.consume(async (msg) => {
30
+ * console.log(msg.body);
31
+ * });
32
+ * ```
33
+ */
34
+ declare class MemoryQueueAdapter<T = unknown> implements QueueAdapter<T> {
35
+ readonly type: "memory";
36
+ readonly name: string;
37
+ private messages;
38
+ private inFlight;
39
+ private processedIds;
40
+ private maxSize;
41
+ private visibilityTimeout;
42
+ private messageCounter;
43
+ private isConsuming;
44
+ private consumeInterval;
45
+ constructor(config: MemoryQueueConfig);
46
+ private generateId;
47
+ send(body: T, options?: SendMessageOptions): Promise<string>;
48
+ sendBatch(messages: Array<{
49
+ body: T;
50
+ options?: SendMessageOptions;
51
+ }>): Promise<BatchSendResult>;
52
+ receive(maxMessages?: number, visibilityTimeoutOverride?: number): Promise<QueueMessage<T>[]>;
53
+ ack(messageId: string): Promise<void>;
54
+ ackBatch(messageIds: string[]): Promise<void>;
55
+ nack(messageId: string, delaySeconds?: number): Promise<void>;
56
+ consume(handler: MessageHandler<T>, options?: ConsumerOptions): Promise<void>;
57
+ stopConsuming(): Promise<void>;
58
+ getStats(): Promise<QueueStats>;
59
+ purge(): Promise<void>;
60
+ close(): Promise<void>;
61
+ }
62
+ /**
63
+ * Create a memory queue adapter
64
+ */
65
+ declare function createMemoryQueueAdapter<T = unknown>(config: MemoryQueueConfig): MemoryQueueAdapter<T>;
66
+
67
+ export { MemoryQueueAdapter, createMemoryQueueAdapter };
@@ -0,0 +1,251 @@
1
+ // src/types.ts
2
+ import {
3
+ type,
4
+ jobStatus,
5
+ job,
6
+ jobOptions,
7
+ addJobRequest,
8
+ jobProgressUpdate,
9
+ queueStats,
10
+ queueListOptions,
11
+ redisQueueConfig,
12
+ workerOptions,
13
+ queueConfig
14
+ } from "@parsrun/types";
15
+ var QueueError = class extends Error {
16
+ constructor(message, code, cause) {
17
+ super(message);
18
+ this.code = code;
19
+ this.cause = cause;
20
+ this.name = "QueueError";
21
+ }
22
+ };
23
+ var QueueErrorCodes = {
24
+ SEND_FAILED: "SEND_FAILED",
25
+ RECEIVE_FAILED: "RECEIVE_FAILED",
26
+ ACK_FAILED: "ACK_FAILED",
27
+ INVALID_CONFIG: "INVALID_CONFIG",
28
+ QUEUE_FULL: "QUEUE_FULL",
29
+ MESSAGE_NOT_FOUND: "MESSAGE_NOT_FOUND",
30
+ NOT_IMPLEMENTED: "NOT_IMPLEMENTED"
31
+ };
32
+
33
+ // src/adapters/memory.ts
34
+ var MemoryQueueAdapter = class {
35
+ type = "memory";
36
+ name;
37
+ messages = [];
38
+ inFlight = /* @__PURE__ */ new Map();
39
+ processedIds = /* @__PURE__ */ new Set();
40
+ maxSize;
41
+ visibilityTimeout;
42
+ messageCounter = 0;
43
+ isConsuming = false;
44
+ consumeInterval = null;
45
+ constructor(config) {
46
+ this.name = config.name;
47
+ this.maxSize = config.maxSize ?? Infinity;
48
+ this.visibilityTimeout = config.visibilityTimeout ?? 30;
49
+ }
50
+ generateId() {
51
+ this.messageCounter++;
52
+ return `msg-${Date.now()}-${this.messageCounter}`;
53
+ }
54
+ async send(body, options) {
55
+ if (this.messages.length >= this.maxSize) {
56
+ throw new QueueError(
57
+ `Queue ${this.name} is full`,
58
+ QueueErrorCodes.QUEUE_FULL
59
+ );
60
+ }
61
+ if (options?.deduplicationId && this.processedIds.has(options.deduplicationId)) {
62
+ return `dedup-${options.deduplicationId}`;
63
+ }
64
+ const id = this.generateId();
65
+ const now = Date.now();
66
+ const visibleAt = options?.delaySeconds ? now + options.delaySeconds * 1e3 : now;
67
+ const message = {
68
+ id,
69
+ body,
70
+ timestamp: /* @__PURE__ */ new Date(),
71
+ attempts: 0,
72
+ visibleAt,
73
+ deduplicationId: options?.deduplicationId,
74
+ metadata: options?.metadata
75
+ };
76
+ if (options?.priority !== void 0 && options.priority > 0) {
77
+ const insertIndex = this.messages.findIndex(
78
+ (m) => m.metadata?.["priority"] ?? 0 < (options.priority ?? 0)
79
+ );
80
+ if (insertIndex === -1) {
81
+ this.messages.push(message);
82
+ } else {
83
+ this.messages.splice(insertIndex, 0, message);
84
+ }
85
+ } else {
86
+ this.messages.push(message);
87
+ }
88
+ if (options?.deduplicationId) {
89
+ this.processedIds.add(options.deduplicationId);
90
+ }
91
+ return id;
92
+ }
93
+ async sendBatch(messages) {
94
+ const messageIds = [];
95
+ const errors = [];
96
+ let successful = 0;
97
+ let failed = 0;
98
+ for (let i = 0; i < messages.length; i++) {
99
+ const msg = messages[i];
100
+ if (!msg) continue;
101
+ try {
102
+ const id = await this.send(msg.body, msg.options);
103
+ messageIds.push(id);
104
+ successful++;
105
+ } catch (err) {
106
+ failed++;
107
+ errors.push({
108
+ index: i,
109
+ error: err instanceof Error ? err.message : "Unknown error"
110
+ });
111
+ }
112
+ }
113
+ return {
114
+ total: messages.length,
115
+ successful,
116
+ failed,
117
+ messageIds,
118
+ errors
119
+ };
120
+ }
121
+ async receive(maxMessages = 10, visibilityTimeoutOverride) {
122
+ const now = Date.now();
123
+ const timeout = (visibilityTimeoutOverride ?? this.visibilityTimeout) * 1e3;
124
+ const result = [];
125
+ const visibleMessages = [];
126
+ const remainingMessages = [];
127
+ for (const msg of this.messages) {
128
+ if (msg.visibleAt <= now && visibleMessages.length < maxMessages) {
129
+ visibleMessages.push(msg);
130
+ } else {
131
+ remainingMessages.push(msg);
132
+ }
133
+ }
134
+ this.messages = remainingMessages;
135
+ for (const msg of visibleMessages) {
136
+ msg.attempts++;
137
+ msg.visibleAt = now + timeout;
138
+ this.inFlight.set(msg.id, msg);
139
+ result.push({
140
+ id: msg.id,
141
+ body: msg.body,
142
+ timestamp: msg.timestamp,
143
+ attempts: msg.attempts,
144
+ metadata: msg.metadata
145
+ });
146
+ }
147
+ return result;
148
+ }
149
+ async ack(messageId) {
150
+ const message = this.inFlight.get(messageId);
151
+ if (!message) {
152
+ throw new QueueError(
153
+ `Message ${messageId} not found in flight`,
154
+ QueueErrorCodes.MESSAGE_NOT_FOUND
155
+ );
156
+ }
157
+ this.inFlight.delete(messageId);
158
+ }
159
+ async ackBatch(messageIds) {
160
+ for (const id of messageIds) {
161
+ this.inFlight.delete(id);
162
+ }
163
+ }
164
+ async nack(messageId, delaySeconds) {
165
+ const message = this.inFlight.get(messageId);
166
+ if (!message) {
167
+ throw new QueueError(
168
+ `Message ${messageId} not found in flight`,
169
+ QueueErrorCodes.MESSAGE_NOT_FOUND
170
+ );
171
+ }
172
+ this.inFlight.delete(messageId);
173
+ const now = Date.now();
174
+ message.visibleAt = delaySeconds ? now + delaySeconds * 1e3 : now;
175
+ this.messages.push(message);
176
+ }
177
+ async consume(handler, options) {
178
+ if (this.isConsuming) {
179
+ return;
180
+ }
181
+ this.isConsuming = true;
182
+ const batchSize = options?.batchSize ?? 10;
183
+ const pollingInterval = options?.pollingInterval ?? 1e3;
184
+ const visibilityTimeout = options?.visibilityTimeout ?? this.visibilityTimeout;
185
+ const maxRetries = options?.maxRetries ?? 3;
186
+ const concurrency = options?.concurrency ?? 1;
187
+ const processMessages = async () => {
188
+ if (!this.isConsuming) return;
189
+ const messages = await this.receive(batchSize, visibilityTimeout);
190
+ for (let i = 0; i < messages.length; i += concurrency) {
191
+ const batch = messages.slice(i, i + concurrency);
192
+ await Promise.all(
193
+ batch.map(async (msg) => {
194
+ try {
195
+ await handler(msg);
196
+ await this.ack(msg.id);
197
+ } catch (err) {
198
+ if (msg.attempts >= maxRetries) {
199
+ await this.ack(msg.id);
200
+ console.error(
201
+ `[Queue ${this.name}] Message ${msg.id} exceeded max retries, dropped`
202
+ );
203
+ } else {
204
+ await this.nack(msg.id, 5);
205
+ }
206
+ }
207
+ })
208
+ );
209
+ }
210
+ };
211
+ this.consumeInterval = setInterval(processMessages, pollingInterval);
212
+ await processMessages();
213
+ }
214
+ async stopConsuming() {
215
+ this.isConsuming = false;
216
+ if (this.consumeInterval) {
217
+ clearInterval(this.consumeInterval);
218
+ this.consumeInterval = null;
219
+ }
220
+ }
221
+ async getStats() {
222
+ const now = Date.now();
223
+ for (const [id, msg] of this.inFlight) {
224
+ if (msg.visibleAt <= now) {
225
+ this.inFlight.delete(id);
226
+ this.messages.push(msg);
227
+ }
228
+ }
229
+ return {
230
+ messageCount: this.messages.length,
231
+ inFlightCount: this.inFlight.size
232
+ };
233
+ }
234
+ async purge() {
235
+ this.messages = [];
236
+ this.inFlight.clear();
237
+ }
238
+ async close() {
239
+ await this.stopConsuming();
240
+ await this.purge();
241
+ this.processedIds.clear();
242
+ }
243
+ };
244
+ function createMemoryQueueAdapter(config) {
245
+ return new MemoryQueueAdapter(config);
246
+ }
247
+ export {
248
+ MemoryQueueAdapter,
249
+ createMemoryQueueAdapter
250
+ };
251
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types.ts","../../src/adapters/memory.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 - Memory Adapter\n * In-memory queue adapter for development and testing\n */\n\nimport type {\n BatchSendResult,\n ConsumerOptions,\n MemoryQueueConfig,\n MessageHandler,\n QueueAdapter,\n QueueMessage,\n QueueStats,\n SendMessageOptions,\n} from \"../types.js\";\nimport { QueueError, QueueErrorCodes } from \"../types.js\";\n\ninterface InternalMessage<T> {\n id: string;\n body: T;\n timestamp: Date;\n attempts: number;\n visibleAt: number;\n deduplicationId?: string | undefined;\n metadata?: Record<string, unknown> | undefined;\n}\n\n/**\n * Memory Queue Adapter\n * Uses in-memory storage for development and testing\n *\n * @example\n * ```typescript\n * const queue = new MemoryQueueAdapter<{ userId: string }>({\n * name: 'user-events',\n * });\n *\n * await queue.send({ userId: '123' });\n *\n * // Pull-based processing\n * const messages = await queue.receive(10);\n * for (const msg of messages) {\n * console.log(msg.body);\n * await queue.ack(msg.id);\n * }\n *\n * // Or push-based processing\n * await queue.consume(async (msg) => {\n * console.log(msg.body);\n * });\n * ```\n */\nexport class MemoryQueueAdapter<T = unknown> implements QueueAdapter<T> {\n readonly type = \"memory\" as const;\n readonly name: string;\n\n private messages: InternalMessage<T>[] = [];\n private inFlight = new Map<string, InternalMessage<T>>();\n private processedIds = new Set<string>();\n private maxSize: number;\n private visibilityTimeout: number;\n private messageCounter = 0;\n private isConsuming = false;\n private consumeInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(config: MemoryQueueConfig) {\n this.name = config.name;\n this.maxSize = config.maxSize ?? Infinity;\n this.visibilityTimeout = config.visibilityTimeout ?? 30;\n }\n\n private generateId(): string {\n this.messageCounter++;\n return `msg-${Date.now()}-${this.messageCounter}`;\n }\n\n async send(body: T, options?: SendMessageOptions): Promise<string> {\n // Check max size\n if (this.messages.length >= this.maxSize) {\n throw new QueueError(\n `Queue ${this.name} is full`,\n QueueErrorCodes.QUEUE_FULL\n );\n }\n\n // Check deduplication\n if (options?.deduplicationId && this.processedIds.has(options.deduplicationId)) {\n // Return existing message ID for deduplicated messages\n return `dedup-${options.deduplicationId}`;\n }\n\n const id = this.generateId();\n const now = Date.now();\n const visibleAt = options?.delaySeconds\n ? now + options.delaySeconds * 1000\n : now;\n\n const message: InternalMessage<T> = {\n id,\n body,\n timestamp: new Date(),\n attempts: 0,\n visibleAt,\n deduplicationId: options?.deduplicationId,\n metadata: options?.metadata,\n };\n\n // Insert based on priority (higher priority = earlier in queue)\n if (options?.priority !== undefined && options.priority > 0) {\n // Find position for priority message\n const insertIndex = this.messages.findIndex(\n (m) => (m.metadata?.[\"priority\"] as number | undefined) ?? 0 < (options.priority ?? 0)\n );\n if (insertIndex === -1) {\n this.messages.push(message);\n } else {\n this.messages.splice(insertIndex, 0, message);\n }\n } else {\n this.messages.push(message);\n }\n\n if (options?.deduplicationId) {\n this.processedIds.add(options.deduplicationId);\n }\n\n return id;\n }\n\n async sendBatch(\n messages: Array<{ body: T; options?: SendMessageOptions }>\n ): Promise<BatchSendResult> {\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 < messages.length; i++) {\n const msg = messages[i];\n if (!msg) continue;\n\n try {\n const id = await this.send(msg.body, msg.options);\n messageIds.push(id);\n successful++;\n } catch (err) {\n failed++;\n errors.push({\n index: i,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n }\n }\n\n return {\n total: messages.length,\n successful,\n failed,\n messageIds,\n errors,\n };\n }\n\n async receive(\n maxMessages = 10,\n visibilityTimeoutOverride?: number\n ): Promise<QueueMessage<T>[]> {\n const now = Date.now();\n const timeout = (visibilityTimeoutOverride ?? this.visibilityTimeout) * 1000;\n const result: QueueMessage<T>[] = [];\n\n // Find visible messages\n const visibleMessages: InternalMessage<T>[] = [];\n const remainingMessages: InternalMessage<T>[] = [];\n\n for (const msg of this.messages) {\n if (msg.visibleAt <= now && visibleMessages.length < maxMessages) {\n visibleMessages.push(msg);\n } else {\n remainingMessages.push(msg);\n }\n }\n\n this.messages = remainingMessages;\n\n // Move to in-flight and increment attempts\n for (const msg of visibleMessages) {\n msg.attempts++;\n msg.visibleAt = now + timeout;\n this.inFlight.set(msg.id, msg);\n\n result.push({\n id: msg.id,\n body: msg.body,\n timestamp: msg.timestamp,\n attempts: msg.attempts,\n metadata: msg.metadata,\n });\n }\n\n return result;\n }\n\n async ack(messageId: string): Promise<void> {\n const message = this.inFlight.get(messageId);\n if (!message) {\n throw new QueueError(\n `Message ${messageId} not found in flight`,\n QueueErrorCodes.MESSAGE_NOT_FOUND\n );\n }\n\n this.inFlight.delete(messageId);\n }\n\n async ackBatch(messageIds: string[]): Promise<void> {\n for (const id of messageIds) {\n this.inFlight.delete(id);\n }\n }\n\n async nack(messageId: string, delaySeconds?: number): Promise<void> {\n const message = this.inFlight.get(messageId);\n if (!message) {\n throw new QueueError(\n `Message ${messageId} not found in flight`,\n QueueErrorCodes.MESSAGE_NOT_FOUND\n );\n }\n\n this.inFlight.delete(messageId);\n\n // Return to queue with optional delay\n const now = Date.now();\n message.visibleAt = delaySeconds ? now + delaySeconds * 1000 : now;\n this.messages.push(message);\n }\n\n async consume(\n handler: MessageHandler<T>,\n options?: ConsumerOptions\n ): Promise<void> {\n if (this.isConsuming) {\n return;\n }\n\n this.isConsuming = true;\n const batchSize = options?.batchSize ?? 10;\n const pollingInterval = options?.pollingInterval ?? 1000;\n const visibilityTimeout = options?.visibilityTimeout ?? this.visibilityTimeout;\n const maxRetries = options?.maxRetries ?? 3;\n const concurrency = options?.concurrency ?? 1;\n\n const processMessages = async (): Promise<void> => {\n if (!this.isConsuming) return;\n\n const messages = await this.receive(batchSize, visibilityTimeout);\n\n // Process in batches based on concurrency\n for (let i = 0; i < messages.length; i += concurrency) {\n const batch = messages.slice(i, i + concurrency);\n await Promise.all(\n batch.map(async (msg) => {\n try {\n await handler(msg);\n await this.ack(msg.id);\n } catch (err) {\n // Check retry limit\n if (msg.attempts >= maxRetries) {\n // Dead letter - just remove from queue\n await this.ack(msg.id);\n console.error(\n `[Queue ${this.name}] Message ${msg.id} exceeded max retries, dropped`\n );\n } else {\n // Return to queue for retry\n await this.nack(msg.id, 5); // 5 second retry delay\n }\n }\n })\n );\n }\n };\n\n // Start polling\n this.consumeInterval = setInterval(processMessages, pollingInterval);\n\n // Process immediately\n await processMessages();\n }\n\n async stopConsuming(): Promise<void> {\n this.isConsuming = false;\n if (this.consumeInterval) {\n clearInterval(this.consumeInterval);\n this.consumeInterval = null;\n }\n }\n\n async getStats(): Promise<QueueStats> {\n // Return visibility timeout expired in-flight messages\n const now = Date.now();\n for (const [id, msg] of this.inFlight) {\n if (msg.visibleAt <= now) {\n this.inFlight.delete(id);\n this.messages.push(msg);\n }\n }\n\n return {\n messageCount: this.messages.length,\n inFlightCount: this.inFlight.size,\n };\n }\n\n async purge(): Promise<void> {\n this.messages = [];\n this.inFlight.clear();\n }\n\n async close(): Promise<void> {\n await this.stopConsuming();\n await this.purge();\n this.processedIds.clear();\n }\n}\n\n/**\n * Create a memory queue adapter\n */\nexport function createMemoryQueueAdapter<T = unknown>(\n config: MemoryQueueConfig\n): MemoryQueueAdapter<T> {\n return new MemoryQueueAdapter<T>(config);\n}\n"],"mappings":";AAMA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWK;AA8OA,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACE,SACgB,MACA,OAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,kBAAkB;AAAA,EAC7B,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;;;AC5OO,IAAM,qBAAN,MAAiE;AAAA,EAC7D,OAAO;AAAA,EACP;AAAA,EAED,WAAiC,CAAC;AAAA,EAClC,WAAW,oBAAI,IAAgC;AAAA,EAC/C,eAAe,oBAAI,IAAY;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,kBAAyD;AAAA,EAEjE,YAAY,QAA2B;AACrC,SAAK,OAAO,OAAO;AACnB,SAAK,UAAU,OAAO,WAAW;AACjC,SAAK,oBAAoB,OAAO,qBAAqB;AAAA,EACvD;AAAA,EAEQ,aAAqB;AAC3B,SAAK;AACL,WAAO,OAAO,KAAK,IAAI,CAAC,IAAI,KAAK,cAAc;AAAA,EACjD;AAAA,EAEA,MAAM,KAAK,MAAS,SAA+C;AAEjE,QAAI,KAAK,SAAS,UAAU,KAAK,SAAS;AACxC,YAAM,IAAI;AAAA,QACR,SAAS,KAAK,IAAI;AAAA,QAClB,gBAAgB;AAAA,MAClB;AAAA,IACF;AAGA,QAAI,SAAS,mBAAmB,KAAK,aAAa,IAAI,QAAQ,eAAe,GAAG;AAE9E,aAAO,SAAS,QAAQ,eAAe;AAAA,IACzC;AAEA,UAAM,KAAK,KAAK,WAAW;AAC3B,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,YAAY,SAAS,eACvB,MAAM,QAAQ,eAAe,MAC7B;AAEJ,UAAM,UAA8B;AAAA,MAClC;AAAA,MACA;AAAA,MACA,WAAW,oBAAI,KAAK;AAAA,MACpB,UAAU;AAAA,MACV;AAAA,MACA,iBAAiB,SAAS;AAAA,MAC1B,UAAU,SAAS;AAAA,IACrB;AAGA,QAAI,SAAS,aAAa,UAAa,QAAQ,WAAW,GAAG;AAE3D,YAAM,cAAc,KAAK,SAAS;AAAA,QAChC,CAAC,MAAO,EAAE,WAAW,UAAU,KAA4B,KAAK,QAAQ,YAAY;AAAA,MACtF;AACA,UAAI,gBAAgB,IAAI;AACtB,aAAK,SAAS,KAAK,OAAO;AAAA,MAC5B,OAAO;AACL,aAAK,SAAS,OAAO,aAAa,GAAG,OAAO;AAAA,MAC9C;AAAA,IACF,OAAO;AACL,WAAK,SAAS,KAAK,OAAO;AAAA,IAC5B;AAEA,QAAI,SAAS,iBAAiB;AAC5B,WAAK,aAAa,IAAI,QAAQ,eAAe;AAAA,IAC/C;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UACJ,UAC0B;AAC1B,UAAM,aAAuB,CAAC;AAC9B,UAAM,SAAkD,CAAC;AACzD,QAAI,aAAa;AACjB,QAAI,SAAS;AAEb,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,YAAM,MAAM,SAAS,CAAC;AACtB,UAAI,CAAC,IAAK;AAEV,UAAI;AACF,cAAM,KAAK,MAAM,KAAK,KAAK,IAAI,MAAM,IAAI,OAAO;AAChD,mBAAW,KAAK,EAAE;AAClB;AAAA,MACF,SAAS,KAAK;AACZ;AACA,eAAO,KAAK;AAAA,UACV,OAAO;AAAA,UACP,OAAO,eAAe,QAAQ,IAAI,UAAU;AAAA,QAC9C,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,MACL,OAAO,SAAS;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,cAAc,IACd,2BAC4B;AAC5B,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,WAAW,6BAA6B,KAAK,qBAAqB;AACxE,UAAM,SAA4B,CAAC;AAGnC,UAAM,kBAAwC,CAAC;AAC/C,UAAM,oBAA0C,CAAC;AAEjD,eAAW,OAAO,KAAK,UAAU;AAC/B,UAAI,IAAI,aAAa,OAAO,gBAAgB,SAAS,aAAa;AAChE,wBAAgB,KAAK,GAAG;AAAA,MAC1B,OAAO;AACL,0BAAkB,KAAK,GAAG;AAAA,MAC5B;AAAA,IACF;AAEA,SAAK,WAAW;AAGhB,eAAW,OAAO,iBAAiB;AACjC,UAAI;AACJ,UAAI,YAAY,MAAM;AACtB,WAAK,SAAS,IAAI,IAAI,IAAI,GAAG;AAE7B,aAAO,KAAK;AAAA,QACV,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,WAAW,IAAI;AAAA,QACf,UAAU,IAAI;AAAA,QACd,UAAU,IAAI;AAAA,MAChB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,WAAkC;AAC1C,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI;AAAA,QACR,WAAW,SAAS;AAAA,QACpB,gBAAgB;AAAA,MAClB;AAAA,IACF;AAEA,SAAK,SAAS,OAAO,SAAS;AAAA,EAChC;AAAA,EAEA,MAAM,SAAS,YAAqC;AAClD,eAAW,MAAM,YAAY;AAC3B,WAAK,SAAS,OAAO,EAAE;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,WAAmB,cAAsC;AAClE,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI;AAAA,QACR,WAAW,SAAS;AAAA,QACpB,gBAAgB;AAAA,MAClB;AAAA,IACF;AAEA,SAAK,SAAS,OAAO,SAAS;AAG9B,UAAM,MAAM,KAAK,IAAI;AACrB,YAAQ,YAAY,eAAe,MAAM,eAAe,MAAO;AAC/D,SAAK,SAAS,KAAK,OAAO;AAAA,EAC5B;AAAA,EAEA,MAAM,QACJ,SACA,SACe;AACf,QAAI,KAAK,aAAa;AACpB;AAAA,IACF;AAEA,SAAK,cAAc;AACnB,UAAM,YAAY,SAAS,aAAa;AACxC,UAAM,kBAAkB,SAAS,mBAAmB;AACpD,UAAM,oBAAoB,SAAS,qBAAqB,KAAK;AAC7D,UAAM,aAAa,SAAS,cAAc;AAC1C,UAAM,cAAc,SAAS,eAAe;AAE5C,UAAM,kBAAkB,YAA2B;AACjD,UAAI,CAAC,KAAK,YAAa;AAEvB,YAAM,WAAW,MAAM,KAAK,QAAQ,WAAW,iBAAiB;AAGhE,eAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,aAAa;AACrD,cAAM,QAAQ,SAAS,MAAM,GAAG,IAAI,WAAW;AAC/C,cAAM,QAAQ;AAAA,UACZ,MAAM,IAAI,OAAO,QAAQ;AACvB,gBAAI;AACF,oBAAM,QAAQ,GAAG;AACjB,oBAAM,KAAK,IAAI,IAAI,EAAE;AAAA,YACvB,SAAS,KAAK;AAEZ,kBAAI,IAAI,YAAY,YAAY;AAE9B,sBAAM,KAAK,IAAI,IAAI,EAAE;AACrB,wBAAQ;AAAA,kBACN,UAAU,KAAK,IAAI,aAAa,IAAI,EAAE;AAAA,gBACxC;AAAA,cACF,OAAO;AAEL,sBAAM,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,cAC3B;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAGA,SAAK,kBAAkB,YAAY,iBAAiB,eAAe;AAGnE,UAAM,gBAAgB;AAAA,EACxB;AAAA,EAEA,MAAM,gBAA+B;AACnC,SAAK,cAAc;AACnB,QAAI,KAAK,iBAAiB;AACxB,oBAAc,KAAK,eAAe;AAClC,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,MAAM,WAAgC;AAEpC,UAAM,MAAM,KAAK,IAAI;AACrB,eAAW,CAAC,IAAI,GAAG,KAAK,KAAK,UAAU;AACrC,UAAI,IAAI,aAAa,KAAK;AACxB,aAAK,SAAS,OAAO,EAAE;AACvB,aAAK,SAAS,KAAK,GAAG;AAAA,MACxB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,cAAc,KAAK,SAAS;AAAA,MAC5B,eAAe,KAAK,SAAS;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,WAAW,CAAC;AACjB,SAAK,SAAS,MAAM;AAAA,EACtB;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK,cAAc;AACzB,UAAM,KAAK,MAAM;AACjB,SAAK,aAAa,MAAM;AAAA,EAC1B;AACF;AAKO,SAAS,yBACd,QACuB;AACvB,SAAO,IAAI,mBAAsB,MAAM;AACzC;","names":[]}
@@ -0,0 +1,110 @@
1
+ import { QueueAdapter, QStashConfig, SendMessageOptions, BatchSendResult, QueueMessage } from '../types.js';
2
+ import '@parsrun/types';
3
+
4
+ /**
5
+ * @parsrun/queue - QStash Adapter
6
+ * Edge-compatible Upstash QStash adapter using fetch API
7
+ */
8
+
9
+ /**
10
+ * QStash Queue Adapter
11
+ * Edge-compatible using HTTP API
12
+ *
13
+ * QStash is a serverless message queue that delivers messages via HTTP webhooks.
14
+ * Messages are sent to QStash which then delivers them to your endpoint.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const queue = new QStashAdapter({
19
+ * token: process.env.QSTASH_TOKEN,
20
+ * destinationUrl: 'https://myapp.com/api/webhooks/queue',
21
+ * });
22
+ *
23
+ * await queue.send({ userId: '123', action: 'welcome-email' });
24
+ * ```
25
+ */
26
+ declare class QStashAdapter<T = unknown> implements QueueAdapter<T> {
27
+ readonly type: "qstash";
28
+ readonly name = "qstash";
29
+ private token;
30
+ private destinationUrl;
31
+ private baseUrl;
32
+ constructor(config: QStashConfig);
33
+ send(body: T, options?: SendMessageOptions): Promise<string>;
34
+ sendBatch(messages: Array<{
35
+ body: T;
36
+ options?: SendMessageOptions;
37
+ }>): Promise<BatchSendResult>;
38
+ /**
39
+ * Schedule a message for future delivery
40
+ */
41
+ schedule(body: T, cronExpression: string, options?: Omit<SendMessageOptions, "delaySeconds">): Promise<string>;
42
+ /**
43
+ * Delete a scheduled message
44
+ */
45
+ deleteSchedule(scheduleId: string): Promise<void>;
46
+ /**
47
+ * List all schedules
48
+ */
49
+ listSchedules(): Promise<Array<{
50
+ scheduleId: string;
51
+ cron: string;
52
+ destination: string;
53
+ body?: string;
54
+ createdAt: number;
55
+ }>>;
56
+ }
57
+ /**
58
+ * QStash Message Receiver
59
+ * Helper for receiving and verifying messages in your webhook endpoint
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // In your API route
64
+ * const receiver = new QStashReceiver({
65
+ * currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY,
66
+ * nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY,
67
+ * });
68
+ *
69
+ * export async function POST(request: Request) {
70
+ * const message = await receiver.verify(request);
71
+ * if (!message) {
72
+ * return new Response('Invalid signature', { status: 401 });
73
+ * }
74
+ *
75
+ * // Process the message
76
+ * console.log(message.body);
77
+ * return new Response('OK');
78
+ * }
79
+ * ```
80
+ */
81
+ declare class QStashReceiver<T = unknown> {
82
+ private currentSigningKey;
83
+ private nextSigningKey;
84
+ constructor(config: {
85
+ currentSigningKey: string;
86
+ nextSigningKey: string;
87
+ });
88
+ /**
89
+ * Verify a request from QStash and extract the message
90
+ */
91
+ verify(request: Request): Promise<QueueMessage<T> | null>;
92
+ private verifySignature;
93
+ private base64UrlDecode;
94
+ private base64UrlEncode;
95
+ private sha256;
96
+ private sha256Base64;
97
+ }
98
+ /**
99
+ * Create a QStash queue adapter
100
+ */
101
+ declare function createQStashAdapter<T = unknown>(config: QStashConfig): QStashAdapter<T>;
102
+ /**
103
+ * Create a QStash receiver for webhook verification
104
+ */
105
+ declare function createQStashReceiver<T = unknown>(config: {
106
+ currentSigningKey: string;
107
+ nextSigningKey: string;
108
+ }): QStashReceiver<T>;
109
+
110
+ export { QStashAdapter, QStashReceiver, createQStashAdapter, createQStashReceiver };