alepha 0.13.1 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api-files/index.d.ts +28 -91
- package/dist/api-files/index.js +10 -755
- package/dist/api-files/index.js.map +1 -1
- package/dist/api-jobs/index.d.ts +46 -46
- package/dist/api-jobs/index.js +13 -13
- package/dist/api-jobs/index.js.map +1 -1
- package/dist/api-notifications/index.d.ts +129 -146
- package/dist/api-notifications/index.js +17 -39
- package/dist/api-notifications/index.js.map +1 -1
- package/dist/api-parameters/index.d.ts +21 -22
- package/dist/api-parameters/index.js +22 -22
- package/dist/api-parameters/index.js.map +1 -1
- package/dist/api-users/index.d.ts +223 -2000
- package/dist/api-users/index.js +914 -4787
- package/dist/api-users/index.js.map +1 -1
- package/dist/api-verifications/index.d.ts +96 -96
- package/dist/batch/index.d.ts +13 -13
- package/dist/batch/index.js +8 -8
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +14 -14
- package/dist/bucket/index.js +12 -12
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/index.d.ts +11 -11
- package/dist/cache/index.js +9 -9
- package/dist/cache/index.js.map +1 -1
- package/dist/cli/index.d.ts +28 -26
- package/dist/cli/index.js +50 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +19 -19
- package/dist/command/index.js +25 -25
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +218 -218
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +232 -232
- package/dist/core/index.js +218 -218
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +2113 -0
- package/dist/core/index.native.js.map +1 -0
- package/dist/datetime/index.d.ts +9 -9
- package/dist/datetime/index.js +7 -7
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +16 -16
- package/dist/email/index.js +9 -9
- package/dist/email/index.js.map +1 -1
- package/dist/file/index.js +1 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/index.d.ts +9 -9
- package/dist/lock/index.js +8 -8
- package/dist/lock/index.js.map +1 -1
- package/dist/lock-redis/index.js +3 -66
- package/dist/lock-redis/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -5
- package/dist/logger/index.js +8 -8
- package/dist/logger/index.js.map +1 -1
- package/dist/orm/index.browser.js +114 -114
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.d.ts +218 -218
- package/dist/orm/index.js +46 -46
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/index.d.ts +29 -29
- package/dist/queue/index.js +20 -20
- package/dist/queue/index.js.map +1 -1
- package/dist/queue-redis/index.d.ts +2 -2
- package/dist/redis/index.d.ts +10 -10
- package/dist/retry/index.d.ts +19 -19
- package/dist/retry/index.js +7 -7
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +16 -16
- package/dist/scheduler/index.js +9 -9
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +80 -80
- package/dist/security/index.js +32 -32
- package/dist/security/index.js.map +1 -1
- package/dist/server/index.browser.js +1 -1
- package/dist/server/index.browser.js.map +1 -1
- package/dist/server/index.d.ts +101 -101
- package/dist/server/index.js +16 -16
- package/dist/server/index.js.map +1 -1
- package/dist/server-auth/index.browser.js +4 -982
- package/dist/server-auth/index.browser.js.map +1 -1
- package/dist/server-auth/index.d.ts +204 -785
- package/dist/server-auth/index.js +47 -1239
- package/dist/server-auth/index.js.map +1 -1
- package/dist/server-cache/index.d.ts +10 -10
- package/dist/server-cache/index.js +2 -2
- package/dist/server-cache/index.js.map +1 -1
- package/dist/server-compress/index.d.ts +4 -4
- package/dist/server-compress/index.js +1 -1
- package/dist/server-compress/index.js.map +1 -1
- package/dist/server-cookies/index.browser.js +8 -8
- package/dist/server-cookies/index.browser.js.map +1 -1
- package/dist/server-cookies/index.d.ts +17 -17
- package/dist/server-cookies/index.js +10 -10
- package/dist/server-cookies/index.js.map +1 -1
- package/dist/server-cors/index.d.ts +17 -17
- package/dist/server-cors/index.js +9 -9
- package/dist/server-cors/index.js.map +1 -1
- package/dist/server-health/index.d.ts +19 -19
- package/dist/server-helmet/index.d.ts +1 -1
- package/dist/server-links/index.browser.js +12 -12
- package/dist/server-links/index.browser.js.map +1 -1
- package/dist/server-links/index.d.ts +59 -251
- package/dist/server-links/index.js +23 -502
- package/dist/server-links/index.js.map +1 -1
- package/dist/server-metrics/index.d.ts +4 -4
- package/dist/server-multipart/index.d.ts +2 -2
- package/dist/server-proxy/index.d.ts +12 -12
- package/dist/server-proxy/index.js +10 -10
- package/dist/server-proxy/index.js.map +1 -1
- package/dist/server-rate-limit/index.d.ts +22 -22
- package/dist/server-rate-limit/index.js +12 -12
- package/dist/server-rate-limit/index.js.map +1 -1
- package/dist/server-security/index.d.ts +22 -22
- package/dist/server-security/index.js +15 -15
- package/dist/server-security/index.js.map +1 -1
- package/dist/server-static/index.d.ts +14 -14
- package/dist/server-static/index.js +8 -8
- package/dist/server-static/index.js.map +1 -1
- package/dist/server-swagger/index.d.ts +25 -184
- package/dist/server-swagger/index.js +21 -724
- package/dist/server-swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +14 -14
- package/dist/sms/index.js +9 -9
- package/dist/sms/index.js.map +1 -1
- package/dist/thread/index.d.ts +11 -11
- package/dist/thread/index.js +17 -17
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/index.d.ts +26 -26
- package/dist/topic/index.js +16 -16
- package/dist/topic/index.js.map +1 -1
- package/dist/topic-redis/index.d.ts +1 -1
- package/dist/vite/index.d.ts +3 -3
- package/dist/vite/index.js +8 -8
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +11 -11
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +58 -58
- package/dist/websocket/index.js +13 -13
- package/dist/websocket/index.js.map +1 -1
- package/package.json +113 -52
- package/src/api-files/services/FileService.ts +5 -7
- package/src/api-jobs/index.ts +1 -1
- package/src/api-jobs/{descriptors → primitives}/$job.ts +8 -8
- package/src/api-jobs/providers/JobProvider.ts +9 -9
- package/src/api-jobs/services/JobService.ts +5 -5
- package/src/api-notifications/index.ts +5 -15
- package/src/api-notifications/{descriptors → primitives}/$notification.ts +10 -10
- package/src/api-notifications/services/NotificationSenderService.ts +3 -3
- package/src/api-parameters/index.ts +1 -1
- package/src/api-parameters/{descriptors → primitives}/$config.ts +7 -12
- package/src/api-users/index.ts +1 -1
- package/src/api-users/{descriptors → primitives}/$userRealm.ts +8 -8
- package/src/api-users/providers/UserRealmProvider.ts +1 -1
- package/src/batch/index.ts +3 -3
- package/src/batch/{descriptors → primitives}/$batch.ts +13 -16
- package/src/bucket/index.ts +8 -8
- package/src/bucket/{descriptors → primitives}/$bucket.ts +8 -8
- package/src/bucket/providers/LocalFileStorageProvider.ts +3 -3
- package/src/cache/index.ts +4 -4
- package/src/cache/{descriptors → primitives}/$cache.ts +15 -15
- package/src/cli/apps/AlephaPackageBuilderCli.ts +24 -2
- package/src/cli/commands/DrizzleCommands.ts +6 -6
- package/src/cli/commands/VerifyCommands.ts +1 -1
- package/src/cli/commands/ViteCommands.ts +6 -1
- package/src/cli/services/ProjectUtils.ts +34 -3
- package/src/command/index.ts +5 -5
- package/src/command/{descriptors → primitives}/$command.ts +9 -12
- package/src/command/providers/CliProvider.ts +10 -10
- package/src/core/Alepha.ts +30 -33
- package/src/core/constants/KIND.ts +1 -1
- package/src/core/constants/OPTIONS.ts +1 -1
- package/src/core/helpers/{descriptor.ts → primitive.ts} +18 -18
- package/src/core/helpers/ref.ts +1 -1
- package/src/core/index.shared.ts +8 -8
- package/src/core/{descriptors → primitives}/$context.ts +5 -5
- package/src/core/{descriptors → primitives}/$hook.ts +4 -4
- package/src/core/{descriptors → primitives}/$inject.ts +2 -2
- package/src/core/{descriptors → primitives}/$module.ts +9 -9
- package/src/core/{descriptors → primitives}/$use.ts +2 -2
- package/src/core/providers/CodecManager.ts +1 -1
- package/src/core/providers/JsonSchemaCodec.ts +1 -1
- package/src/core/providers/StateManager.ts +2 -2
- package/src/datetime/index.ts +3 -3
- package/src/datetime/{descriptors → primitives}/$interval.ts +6 -6
- package/src/email/index.ts +4 -4
- package/src/email/{descriptors → primitives}/$email.ts +8 -8
- package/src/file/index.ts +1 -1
- package/src/lock/index.ts +3 -3
- package/src/lock/{descriptors → primitives}/$lock.ts +10 -10
- package/src/logger/index.ts +8 -8
- package/src/logger/{descriptors → primitives}/$logger.ts +2 -2
- package/src/logger/services/Logger.ts +1 -1
- package/src/orm/constants/PG_SYMBOLS.ts +2 -2
- package/src/orm/index.browser.ts +2 -2
- package/src/orm/index.ts +8 -8
- package/src/orm/{descriptors → primitives}/$entity.ts +11 -11
- package/src/orm/{descriptors → primitives}/$repository.ts +2 -2
- package/src/orm/{descriptors → primitives}/$sequence.ts +8 -8
- package/src/orm/{descriptors → primitives}/$transaction.ts +4 -4
- package/src/orm/providers/PostgresTypeProvider.ts +3 -3
- package/src/orm/providers/RepositoryProvider.ts +4 -4
- package/src/orm/providers/drivers/DatabaseProvider.ts +7 -7
- package/src/orm/services/ModelBuilder.ts +9 -9
- package/src/orm/services/PgRelationManager.ts +2 -2
- package/src/orm/services/PostgresModelBuilder.ts +5 -5
- package/src/orm/services/Repository.ts +7 -7
- package/src/orm/services/SqliteModelBuilder.ts +5 -5
- package/src/queue/index.ts +7 -7
- package/src/queue/{descriptors → primitives}/$consumer.ts +15 -15
- package/src/queue/{descriptors → primitives}/$queue.ts +12 -12
- package/src/queue/providers/WorkerProvider.ts +7 -7
- package/src/retry/index.ts +3 -3
- package/src/retry/{descriptors → primitives}/$retry.ts +14 -14
- package/src/scheduler/index.ts +3 -3
- package/src/scheduler/{descriptors → primitives}/$scheduler.ts +9 -9
- package/src/scheduler/providers/CronProvider.ts +1 -1
- package/src/security/index.ts +9 -9
- package/src/security/{descriptors → primitives}/$permission.ts +7 -7
- package/src/security/{descriptors → primitives}/$realm.ts +6 -12
- package/src/security/{descriptors → primitives}/$role.ts +12 -12
- package/src/security/{descriptors → primitives}/$serviceAccount.ts +8 -8
- package/src/server/index.browser.ts +1 -1
- package/src/server/index.ts +14 -14
- package/src/server/{descriptors → primitives}/$action.ts +13 -13
- package/src/server/{descriptors → primitives}/$route.ts +9 -9
- package/src/server/providers/NodeHttpServerProvider.ts +1 -1
- package/src/server/services/HttpClient.ts +1 -1
- package/src/server-auth/index.browser.ts +1 -1
- package/src/server-auth/index.ts +6 -6
- package/src/server-auth/{descriptors → primitives}/$auth.ts +10 -10
- package/src/server-auth/{descriptors → primitives}/$authCredentials.ts +4 -4
- package/src/server-auth/{descriptors → primitives}/$authGithub.ts +4 -4
- package/src/server-auth/{descriptors → primitives}/$authGoogle.ts +4 -4
- package/src/server-auth/providers/ServerAuthProvider.ts +4 -4
- package/src/server-cache/providers/ServerCacheProvider.ts +7 -7
- package/src/server-compress/providers/ServerCompressProvider.ts +3 -3
- package/src/server-cookies/index.browser.ts +2 -2
- package/src/server-cookies/index.ts +5 -5
- package/src/server-cookies/{descriptors → primitives}/$cookie.browser.ts +12 -12
- package/src/server-cookies/{descriptors → primitives}/$cookie.ts +13 -13
- package/src/server-cookies/providers/ServerCookiesProvider.ts +4 -4
- package/src/server-cookies/services/CookieParser.ts +1 -1
- package/src/server-cors/index.ts +3 -3
- package/src/server-cors/{descriptors → primitives}/$cors.ts +11 -13
- package/src/server-cors/providers/ServerCorsProvider.ts +5 -5
- package/src/server-links/index.browser.ts +5 -5
- package/src/server-links/index.ts +9 -9
- package/src/server-links/{descriptors → primitives}/$remote.ts +11 -11
- package/src/server-links/providers/LinkProvider.ts +7 -7
- package/src/server-links/providers/{RemoteDescriptorProvider.ts → RemotePrimitiveProvider.ts} +6 -6
- package/src/server-links/providers/ServerLinksProvider.ts +3 -3
- package/src/server-proxy/index.ts +3 -3
- package/src/server-proxy/{descriptors → primitives}/$proxy.ts +8 -8
- package/src/server-proxy/providers/ServerProxyProvider.ts +4 -4
- package/src/server-rate-limit/index.ts +6 -6
- package/src/server-rate-limit/{descriptors → primitives}/$rateLimit.ts +13 -13
- package/src/server-rate-limit/providers/ServerRateLimitProvider.ts +5 -5
- package/src/server-security/index.ts +3 -3
- package/src/server-security/{descriptors → primitives}/$basicAuth.ts +13 -13
- package/src/server-security/providers/ServerBasicAuthProvider.ts +5 -5
- package/src/server-security/providers/ServerSecurityProvider.ts +4 -4
- package/src/server-static/index.ts +3 -3
- package/src/server-static/{descriptors → primitives}/$serve.ts +8 -10
- package/src/server-static/providers/ServerStaticProvider.ts +6 -6
- package/src/server-swagger/index.ts +5 -5
- package/src/server-swagger/{descriptors → primitives}/$swagger.ts +9 -9
- package/src/server-swagger/providers/ServerSwaggerProvider.ts +11 -10
- package/src/sms/index.ts +4 -4
- package/src/sms/{descriptors → primitives}/$sms.ts +8 -8
- package/src/thread/index.ts +3 -3
- package/src/thread/{descriptors → primitives}/$thread.ts +13 -13
- package/src/thread/providers/ThreadProvider.ts +7 -9
- package/src/topic/index.ts +5 -5
- package/src/topic/{descriptors → primitives}/$subscriber.ts +14 -14
- package/src/topic/{descriptors → primitives}/$topic.ts +10 -10
- package/src/topic/providers/TopicProvider.ts +4 -4
- package/src/vite/tasks/copyAssets.ts +1 -1
- package/src/vite/tasks/generateSitemap.ts +3 -3
- package/src/vite/tasks/prerenderPages.ts +2 -2
- package/src/vite/tasks/runAlepha.ts +2 -2
- package/src/websocket/index.browser.ts +3 -3
- package/src/websocket/index.shared.ts +2 -2
- package/src/websocket/index.ts +4 -4
- package/src/websocket/interfaces/WebSocketInterfaces.ts +3 -3
- package/src/websocket/{descriptors → primitives}/$channel.ts +10 -10
- package/src/websocket/{descriptors → primitives}/$websocket.ts +8 -8
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +7 -7
- package/src/websocket/providers/WebSocketServerProvider.ts +3 -3
- package/src/websocket/services/WebSocketClient.ts +5 -5
- package/src/api-notifications/providers/MemorySmsProvider.ts +0 -20
- package/src/api-notifications/providers/SmsProvider.ts +0 -8
- /package/src/core/{descriptors → primitives}/$atom.ts +0 -0
- /package/src/core/{descriptors → primitives}/$env.ts +0 -0
- /package/src/server-auth/{descriptors → primitives}/$authApple.ts +0 -0
- /package/src/server-links/{descriptors → primitives}/$client.ts +0 -0
package/dist/queue/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["waiter: MessageWaiter","job: QueueJob<T>","waiter: JobWaiter","result","jobIds: string[]","stalledJobIds: string[]","toRemove: string[]","promises: Promise<void>[]"],"sources":["../../src/queue/descriptors/$consumer.ts","../../src/queue/providers/QueueProvider.ts","../../src/queue/providers/MemoryQueueProvider.ts","../../src/queue/descriptors/$queue.ts","../../src/queue/providers/WorkerProvider.ts","../../src/queue/index.ts"],"sourcesContent":["import {\n createDescriptor,\n Descriptor,\n KIND,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { QueueDescriptor } from \"./$queue.ts\";\n\n/**\n * Creates a consumer descriptor to process messages from a specific queue.\n *\n * Provides a dedicated message consumer that connects to a queue and processes messages\n * with custom handler logic, enabling scalable architectures where multiple consumers\n * can process messages from the same queue.\n *\n * **Key Features**\n * - Seamless integration with any $queue descriptor\n * - Full type safety inherited from queue schema\n * - Automatic worker management for background processing\n * - Built-in error handling and retry mechanisms\n * - Support for multiple consumers per queue for horizontal scaling\n *\n * **Common Use Cases**\n * - Email sending and notification services\n * - Image and media processing workers\n * - Data synchronization and background jobs\n *\n * @example\n * ```ts\n * class EmailService {\n * emailQueue = $queue({\n * name: \"emails\",\n * schema: t.object({\n * to: t.text(),\n * subject: t.text(),\n * body: t.text()\n * })\n * });\n *\n * emailConsumer = $consumer({\n * queue: this.emailQueue,\n * handler: async (message) => {\n * const { to, subject, body } = message.payload;\n * await this.sendEmail(to, subject, body);\n * }\n * });\n *\n * async sendWelcomeEmail(userEmail: string) {\n * await this.emailQueue.push({\n * to: userEmail,\n * subject: \"Welcome!\",\n * body: \"Thanks for joining.\"\n * });\n * }\n * }\n * ```\n */\nexport const $consumer = <T extends TSchema>(\n options: ConsumerDescriptorOptions<T>,\n): ConsumerDescriptor<T> => {\n return createDescriptor(ConsumerDescriptor<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface ConsumerDescriptorOptions<T extends TSchema> {\n /**\n * The queue descriptor that this consumer will process messages from.\n *\n * This establishes the connection between the consumer and its source queue:\n * - The consumer inherits the queue's message schema for type safety\n * - Messages pushed to the queue will be automatically routed to this consumer\n * - Multiple consumers can be attached to the same queue for parallel processing\n * - The consumer will use the queue's provider and configuration settings\n *\n * **Queue Integration Benefits**:\n * - Type safety: Consumer handler gets fully typed message payloads\n * - Schema validation: Messages are validated before reaching the consumer\n * - Error handling: Failed messages can be retried or moved to dead letter queues\n * - Monitoring: Queue metrics include consumer processing statistics\n *\n * @example\n * ```ts\n * // First, define a queue\n * emailQueue = $queue({\n * name: \"emails\",\n * schema: t.object({ to: t.text(), subject: t.text() })\n * });\n *\n * // Then, create a consumer for that queue\n * emailConsumer = $consumer({\n * queue: this.emailQueue, // Reference the queue descriptor\n * handler: async (message) => { } // process email\n * });\n * ```\n */\n queue: QueueDescriptor<T>;\n\n /**\n * Message handler function that processes individual messages from the queue.\n *\n * This function:\n * - Receives fully typed and validated message payloads from the connected queue\n * - Runs in the background worker system for non-blocking operation\n * - Should implement the core business logic for processing this message type\n * - Can throw errors to trigger the queue's retry mechanisms\n * - Has access to the full Alepha dependency injection container\n * - Should be idempotent to handle potential duplicate deliveries\n *\n * **Handler Design Guidelines**:\n * - Keep handlers focused on a single responsibility\n * - Use proper error handling and meaningful error messages\n * - Log important processing steps for debugging and monitoring\n * - Consider transaction boundaries for data consistency\n * - Make operations idempotent when possible\n * - Validate business rules within the handler logic\n *\n * **Error Handling Strategy**:\n * - Throw errors for temporary failures that should be retried\n * - Log and handle permanent failures gracefully\n * - Use specific error types to control retry behavior\n * - Consider implementing circuit breakers for external service calls\n *\n * @param message - The queue message containing the validated payload\n * @param message.payload - The typed message data based on the queue's schema\n * @returns Promise that resolves when processing is complete\n *\n * @example\n * ```ts\n * handler: async (message) => {\n * const { userId, action, data } = message.payload;\n *\n * try {\n * // Log processing start\n * this.logger.info(`Processing ${action} for user ${userId}`);\n *\n * // Validate business rules\n * if (!await this.userService.exists(userId)) {\n * throw new Error(`User ${userId} not found`);\n * }\n *\n * // Perform the main processing logic\n * switch (action) {\n * case \"create\":\n * await this.processCreation(userId, data);\n * break;\n * case \"update\":\n * await this.processUpdate(userId, data);\n * break;\n * default:\n * throw new Error(`Unknown action: ${action}`);\n * }\n *\n * // Log successful completion\n * this.logger.info(`Successfully processed ${action} for user ${userId}`);\n *\n * } catch (error) {\n * // Log error with context\n * this.logger.error(`Failed to process ${action} for user ${userId}`, {\n * error: error.message,\n * userId,\n * action,\n * data\n * });\n *\n * // Re-throw to trigger queue retry mechanism\n * throw error;\n * }\n * }\n * ```\n */\n handler: (message: { payload: Static<T> }) => Promise<void>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ConsumerDescriptor<T extends TSchema> extends Descriptor<\n ConsumerDescriptorOptions<T>\n> {}\n\n$consumer[KIND] = ConsumerDescriptor;\n","import type {\n QueueAcquiredJob,\n QueueCleanOptions,\n QueueEvent,\n QueueEventHandler,\n QueueEventMap,\n QueueEventType,\n QueueGetJobsOptions,\n QueueJob,\n QueueJobCounts,\n QueueJobOptions,\n QueueJobStatus,\n} from \"../interfaces/QueueJob.ts\";\n\n/**\n * Queue provider interface supporting both simple message-based and advanced job-based operations.\n *\n * The simple API (push/pop/popBlocking) is for basic fire-and-forget messaging.\n * The job API provides crash recovery, retries, delayed jobs, priorities, and job history.\n */\nexport abstract class QueueProvider {\n // ===========================================\n // Event System\n // ===========================================\n\n protected eventHandlers: Map<\n QueueEventType | \"*\",\n Set<QueueEventHandler<QueueEvent>>\n > = new Map();\n\n /**\n * Subscribe to queue events.\n *\n * @param event Event type to listen for, or \"*\" for all events.\n * @param handler Handler function to call when event occurs.\n * @returns Unsubscribe function.\n *\n * @example\n * ```ts\n * // Listen for completed events\n * const unsubscribe = provider.on(\"completed\", (event) => {\n * console.log(`Job ${event.jobId} completed in ${event.duration}ms`);\n * });\n *\n * // Listen for all events\n * provider.on(\"*\", (event) => {\n * console.log(`Event: ${event.type} for job ${event.jobId}`);\n * });\n *\n * // Unsubscribe later\n * unsubscribe();\n * ```\n */\n public on<T extends QueueEventType>(\n event: T,\n handler: QueueEventHandler<QueueEventMap[T]>,\n ): () => void;\n public on(event: \"*\", handler: QueueEventHandler<QueueEvent>): () => void;\n public on(\n event: QueueEventType | \"*\",\n handler: QueueEventHandler<QueueEvent>,\n ): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n\n return () => {\n this.eventHandlers.get(event)?.delete(handler);\n };\n }\n\n /**\n * Emit a queue event to all registered handlers.\n *\n * @param event The event to emit.\n */\n protected async emit(event: QueueEvent): Promise<void> {\n const handlers = [\n ...(this.eventHandlers.get(event.type) ?? []),\n ...(this.eventHandlers.get(\"*\") ?? []),\n ];\n\n await Promise.all(handlers.map((handler) => handler(event)));\n }\n // ===========================================\n // Simple Message API (backward compatible)\n // ===========================================\n\n /**\n * Push a message to the queue.\n *\n * @param queue Name of the queue to push the message to.\n * @param message String message to be pushed to the queue.\n */\n public abstract push(queue: string, message: string): Promise<void>;\n\n /**\n * Pop a message from the queue.\n *\n * @param queue Name of the queue to pop the message from.\n * @returns The message popped or `undefined` if the queue is empty.\n */\n public abstract pop(queue: string): Promise<string | undefined>;\n\n /**\n * Pop a message from one of the specified queues, blocking until available or timeout.\n *\n * @param queues Array of queue names to listen on.\n * @param timeoutSeconds Maximum time to wait in seconds.\n * @returns Object with queue name and message, or `undefined` if timeout expired.\n */\n public abstract popBlocking(\n queues: string[],\n timeoutSeconds: number,\n ): Promise<{ queue: string; message: string } | undefined>;\n\n // ===========================================\n // Job-based API (advanced features)\n // ===========================================\n\n /**\n * Add a job to the queue.\n *\n * @param queue Queue name.\n * @param payload Job data to process.\n * @param options Job options (priority, delay, retries, etc.).\n * @returns The created job.\n */\n public abstract addJob<T>(\n queue: string,\n payload: T,\n options?: QueueJobOptions,\n ): Promise<QueueJob<T>>;\n\n /**\n * Acquire the next available job for processing.\n *\n * This atomically:\n * 1. Finds the highest priority job that is ready for processing\n * 2. Moves it to \"active\" status\n * 3. Sets a lock with the worker ID\n *\n * @param queues Queue names to check (in order of preference).\n * @param workerId Unique identifier for the worker acquiring the job.\n * @param timeoutSeconds Maximum time to wait for a job.\n * @returns The acquired job or undefined if timeout.\n */\n public abstract acquireJob(\n queues: string[],\n workerId: string,\n timeoutSeconds: number,\n ): Promise<QueueAcquiredJob | undefined>;\n\n /**\n * Mark a job as completed successfully.\n *\n * @param queue Queue name.\n * @param jobId Job ID.\n * @param result Optional result data from processing.\n */\n public abstract completeJob(\n queue: string,\n jobId: string,\n result?: unknown,\n ): Promise<void>;\n\n /**\n * Mark a job as failed.\n *\n * If the job has remaining retry attempts, it will be moved to \"delayed\" status\n * (for backoff) or \"waiting\" status. Otherwise, it will be moved to \"failed\" status.\n *\n * @param queue Queue name.\n * @param jobId Job ID.\n * @param error Error message.\n * @param stackTrace Optional stack trace.\n */\n public abstract failJob(\n queue: string,\n jobId: string,\n error: string,\n stackTrace?: string,\n ): Promise<void>;\n\n /**\n * Extend the lock on an active job.\n *\n * Workers should call this periodically while processing long-running jobs\n * to prevent them from being considered stalled.\n *\n * @param queue Queue name.\n * @param jobId Job ID.\n * @param workerId Worker ID (must match the lock holder).\n * @returns True if lock was extended, false if job is not locked by this worker.\n */\n public abstract renewJobLock(\n queue: string,\n jobId: string,\n workerId: string,\n ): Promise<boolean>;\n\n /**\n * Get a job by ID.\n *\n * @param queue Queue name.\n * @param jobId Job ID.\n * @returns The job or undefined if not found.\n */\n public abstract getJob(\n queue: string,\n jobId: string,\n ): Promise<QueueJob | undefined>;\n\n /**\n * Get jobs by status.\n *\n * @param queue Queue name.\n * @param status Job status to filter by.\n * @param options Pagination options.\n * @returns Array of jobs.\n */\n public abstract getJobs(\n queue: string,\n status: QueueJobStatus,\n options?: QueueGetJobsOptions,\n ): Promise<QueueJob[]>;\n\n /**\n * Get job counts by status.\n *\n * @param queue Queue name.\n * @returns Object with counts for each status.\n */\n public abstract getJobCounts(queue: string): Promise<QueueJobCounts>;\n\n /**\n * Promote delayed jobs that are ready for processing.\n *\n * Moves jobs from \"delayed\" to \"waiting\" status when their availableAt time has passed.\n *\n * @param queue Queue name.\n * @returns Number of jobs promoted.\n */\n public abstract promoteDelayedJobs(queue: string): Promise<number>;\n\n /**\n * Recover stalled jobs.\n *\n * Finds jobs in \"active\" status whose locks have expired and either:\n * - Moves them back to \"waiting\" for retry (if attempts remaining)\n * - Moves them to \"failed\" (if no attempts remaining)\n *\n * @param queue Queue name.\n * @param stalledThresholdMs Jobs with expired locks older than this are considered stalled.\n * @returns Array of recovered job IDs.\n */\n public abstract recoverStalledJobs(\n queue: string,\n stalledThresholdMs: number,\n ): Promise<string[]>;\n\n /**\n * Remove old completed or failed jobs.\n *\n * @param queue Queue name.\n * @param status Status to clean (\"completed\" or \"failed\").\n * @param options Cleaning options (maxAge, maxCount).\n * @returns Number of jobs removed.\n */\n public abstract cleanJobs(\n queue: string,\n status: \"completed\" | \"failed\",\n options?: QueueCleanOptions,\n ): Promise<number>;\n\n /**\n * Remove a specific job.\n *\n * @param queue Queue name.\n * @param jobId Job ID.\n */\n public abstract removeJob(queue: string, jobId: string): Promise<void>;\n\n /**\n * Cancel all pending waiters.\n *\n * This is called during shutdown to immediately release all blocking\n * acquireJob calls, preventing shutdown delays.\n */\n public abstract cancelWaiters(): void;\n}\n\n// Re-export types for convenience\nexport type {\n QueueAcquiredJob,\n QueueAddJobOptions,\n QueueCleanOptions,\n QueueEvent,\n QueueEventActive,\n QueueEventBase,\n QueueEventCompleted,\n QueueEventFailed,\n QueueEventHandler,\n QueueEventMap,\n QueueEventProgress,\n QueueEventRemoved,\n QueueEventRetrying,\n QueueEventStalled,\n QueueEventType,\n QueueEventWaiting,\n QueueGetJobsOptions,\n QueueJob,\n QueueJobBackoff,\n QueueJobCounts,\n QueueJobOptions,\n QueueJobState,\n QueueJobStatus,\n} from \"../interfaces/QueueJob.ts\";\n","import { $logger } from \"alepha/logger\";\nimport type {\n QueueAcquiredJob,\n QueueCleanOptions,\n QueueGetJobsOptions,\n QueueJob,\n QueueJobCounts,\n QueueJobOptions,\n QueueJobStatus,\n} from \"../interfaces/QueueJob.ts\";\nimport { QueueProvider } from \"./QueueProvider.ts\";\n\n// Default job options\nconst DEFAULT_MAX_ATTEMPTS = 1;\nconst DEFAULT_LOCK_DURATION = 30000; // 30 seconds\nconst DEFAULT_BACKOFF_DELAY = 1000; // 1 second\nconst DEFAULT_BACKOFF_MAX_DELAY = 30000; // 30 seconds\n\ninterface MessageWaiter {\n queues: Set<string>;\n resolve: (result: { queue: string; message: string } | undefined) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface JobWaiter {\n queues: Set<string>;\n workerId: string;\n resolve: (result: QueueAcquiredJob | undefined) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * In-memory queue provider with full job support.\n *\n * This provider stores all data in memory and is suitable for:\n * - Development and testing\n * - Single-instance applications\n * - Scenarios where job persistence across restarts is not required\n */\nexport class MemoryQueueProvider extends QueueProvider {\n protected readonly log = $logger();\n\n // Simple message API storage\n protected messageQueues: Record<string, string[]> = {};\n protected messageWaiters: Set<MessageWaiter> = new Set();\n\n // Job API storage (per queue)\n protected jobs: Map<string, Map<string, QueueJob>> = new Map(); // queue -> jobId -> job\n protected waiting: Map<string, string[]> = new Map(); // queue -> jobIds (sorted by priority)\n protected delayed: Map<string, string[]> = new Map(); // queue -> jobIds (sorted by availableAt)\n protected active: Map<string, Set<string>> = new Map(); // queue -> jobIds\n protected completed: Map<string, string[]> = new Map(); // queue -> jobIds (newest first)\n protected failed: Map<string, string[]> = new Map(); // queue -> jobIds (newest first)\n protected jobWaiters: Set<JobWaiter> = new Set();\n\n protected jobIdCounter = 0;\n\n // ===========================================\n // Simple Message API\n // ===========================================\n\n public async push(queue: string, ...messages: string[]): Promise<void> {\n if (this.messageQueues[queue] == null) {\n this.messageQueues[queue] = [];\n }\n\n for (const message of messages) {\n const waiter = this.findMessageWaiter(queue);\n if (waiter) {\n this.removeMessageWaiter(waiter);\n waiter.resolve({ queue, message });\n } else {\n this.messageQueues[queue].push(message);\n }\n }\n }\n\n public async pop(queue: string): Promise<string | undefined> {\n return this.messageQueues[queue]?.shift();\n }\n\n public async popBlocking(\n queues: string[],\n timeoutSeconds: number,\n ): Promise<{ queue: string; message: string } | undefined> {\n for (const queue of queues) {\n const message = this.messageQueues[queue]?.shift();\n if (message) {\n return { queue, message };\n }\n }\n\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this.removeMessageWaiter(waiter);\n resolve(undefined);\n }, timeoutSeconds * 1000);\n\n const waiter: MessageWaiter = {\n queues: new Set(queues),\n resolve: (result) => {\n clearTimeout(timer);\n resolve(result);\n },\n timer,\n };\n\n this.messageWaiters.add(waiter);\n });\n }\n\n protected findMessageWaiter(queue: string): MessageWaiter | undefined {\n for (const waiter of this.messageWaiters) {\n if (waiter.queues.has(queue)) {\n return waiter;\n }\n }\n return undefined;\n }\n\n protected removeMessageWaiter(waiter: MessageWaiter): void {\n clearTimeout(waiter.timer);\n this.messageWaiters.delete(waiter);\n }\n\n // ===========================================\n // Job API Implementation\n // ===========================================\n\n protected generateJobId(): string {\n return `job_${++this.jobIdCounter}_${Date.now()}`;\n }\n\n protected ensureQueueStructures(queue: string): void {\n if (!this.jobs.has(queue)) {\n this.jobs.set(queue, new Map());\n this.waiting.set(queue, []);\n this.delayed.set(queue, []);\n this.active.set(queue, new Set());\n this.completed.set(queue, []);\n this.failed.set(queue, []);\n }\n }\n\n public async addJob<T>(\n queue: string,\n payload: T,\n options?: QueueJobOptions,\n ): Promise<QueueJob<T>> {\n this.ensureQueueStructures(queue);\n\n const now = Date.now();\n const delay = options?.delay ?? 0;\n const isDelayed = delay > 0;\n\n const job: QueueJob<T> = {\n id: this.generateJobId(),\n queue,\n payload,\n options: {\n priority: options?.priority ?? 0,\n delay: options?.delay ?? 0,\n maxAttempts: options?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,\n backoff: options?.backoff,\n lockDuration: options?.lockDuration ?? DEFAULT_LOCK_DURATION,\n removeOnComplete: options?.removeOnComplete,\n removeOnFail: options?.removeOnFail,\n },\n state: {\n status: isDelayed ? \"delayed\" : \"waiting\",\n attempts: 0,\n createdAt: now,\n availableAt: isDelayed ? now + delay : now,\n },\n };\n\n this.jobs.get(queue)!.set(job.id, job);\n\n if (isDelayed) {\n this.insertDelayed(queue, job);\n } else {\n this.insertWaiting(queue, job);\n this.notifyJobWaiters(queue);\n }\n\n this.log.debug(`Added job ${job.id} to queue ${queue}`, {\n status: job.state.status,\n priority: job.options.priority,\n });\n\n // Emit waiting event\n if (!isDelayed) {\n await this.emit({\n type: \"waiting\",\n queue,\n jobId: job.id,\n timestamp: now,\n job,\n });\n }\n\n return job;\n }\n\n protected insertWaiting(queue: string, job: QueueJob): void {\n const waitingList = this.waiting.get(queue)!;\n const priority = job.options.priority ?? 0;\n\n // Insert in priority order (lower priority value = higher priority)\n let insertIndex = waitingList.length;\n for (let i = 0; i < waitingList.length; i++) {\n const existingJob = this.jobs.get(queue)!.get(waitingList[i]);\n if (existingJob && (existingJob.options.priority ?? 0) > priority) {\n insertIndex = i;\n break;\n }\n }\n waitingList.splice(insertIndex, 0, job.id);\n }\n\n protected insertDelayed(queue: string, job: QueueJob): void {\n const delayedList = this.delayed.get(queue)!;\n const availableAt = job.state.availableAt ?? 0;\n\n // Insert sorted by availableAt (ascending)\n let insertIndex = delayedList.length;\n for (let i = 0; i < delayedList.length; i++) {\n const existingJob = this.jobs.get(queue)!.get(delayedList[i]);\n if (existingJob && (existingJob.state.availableAt ?? 0) > availableAt) {\n insertIndex = i;\n break;\n }\n }\n delayedList.splice(insertIndex, 0, job.id);\n }\n\n protected notifyJobWaiters(queue: string): void {\n for (const waiter of this.jobWaiters) {\n if (waiter.queues.has(queue)) {\n // Try to acquire a job for this waiter\n const result = this.tryAcquireJob(\n Array.from(waiter.queues),\n waiter.workerId,\n );\n if (result) {\n this.removeJobWaiter(waiter);\n waiter.resolve(result);\n return;\n }\n }\n }\n }\n\n protected removeJobWaiter(waiter: JobWaiter): void {\n clearTimeout(waiter.timer);\n this.jobWaiters.delete(waiter);\n }\n\n protected tryAcquireJob(\n queues: string[],\n workerId: string,\n ): QueueAcquiredJob | undefined {\n const now = Date.now();\n\n for (const queue of queues) {\n const waitingList = this.waiting.get(queue);\n if (!waitingList || waitingList.length === 0) continue;\n\n const jobId = waitingList.shift()!;\n const job = this.jobs.get(queue)!.get(jobId);\n if (!job) continue;\n\n // Move to active\n job.state.status = \"active\";\n job.state.attempts += 1;\n job.state.lockedBy = workerId;\n job.state.lockedUntil =\n now + (job.options.lockDuration ?? DEFAULT_LOCK_DURATION);\n job.state.processedAt = now;\n\n this.active.get(queue)!.add(jobId);\n\n this.log.debug(`Worker ${workerId} acquired job ${jobId}`, {\n queue,\n attempt: job.state.attempts,\n });\n\n // Emit active event (fire and forget to not block acquisition)\n this.emit({\n type: \"active\",\n queue,\n jobId,\n timestamp: now,\n workerId,\n attempt: job.state.attempts,\n });\n\n return { queue, job };\n }\n\n return undefined;\n }\n\n public async acquireJob(\n queues: string[],\n workerId: string,\n timeoutSeconds: number,\n ): Promise<QueueAcquiredJob | undefined> {\n // Ensure queue structures exist\n for (const queue of queues) {\n this.ensureQueueStructures(queue);\n }\n\n // Try to acquire immediately\n const result = this.tryAcquireJob(queues, workerId);\n if (result) {\n return result;\n }\n\n // Wait for a job\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this.removeJobWaiter(waiter);\n resolve(undefined);\n }, timeoutSeconds * 1000);\n\n const waiter: JobWaiter = {\n queues: new Set(queues),\n workerId,\n resolve: (result) => {\n clearTimeout(timer);\n resolve(result);\n },\n timer,\n };\n\n this.jobWaiters.add(waiter);\n });\n }\n\n public async completeJob(\n queue: string,\n jobId: string,\n result?: unknown,\n ): Promise<void> {\n const job = this.jobs.get(queue)?.get(jobId);\n if (!job) {\n this.log.warn(`Attempted to complete unknown job ${jobId}`);\n return;\n }\n\n const now = Date.now();\n const duration = now - (job.state.processedAt ?? now);\n\n // Remove from active\n this.active.get(queue)?.delete(jobId);\n\n // Update job state\n job.state.status = \"completed\";\n job.state.completedAt = now;\n job.state.result = result;\n job.state.lockedBy = undefined;\n job.state.lockedUntil = undefined;\n\n // Handle removeOnComplete\n const removeOnComplete = job.options.removeOnComplete;\n if (removeOnComplete === true) {\n // Remove immediately\n this.jobs.get(queue)?.delete(jobId);\n this.log.debug(`Job ${jobId} completed and removed`, { queue, result });\n } else {\n // Add to completed history (newest first)\n this.completed.get(queue)!.unshift(jobId);\n\n // If removeOnComplete is a number, trim the list (0 means keep none)\n if (typeof removeOnComplete === \"number\" && removeOnComplete >= 0) {\n await this.cleanJobs(queue, \"completed\", {\n maxCount: removeOnComplete,\n });\n }\n\n this.log.debug(`Job ${jobId} completed`, { queue, result });\n }\n\n // Emit completed event\n await this.emit({\n type: \"completed\",\n queue,\n jobId,\n timestamp: now,\n result,\n duration,\n });\n }\n\n public async failJob(\n queue: string,\n jobId: string,\n error: string,\n stackTrace?: string,\n ): Promise<void> {\n const job = this.jobs.get(queue)?.get(jobId);\n if (!job) {\n this.log.warn(`Attempted to fail unknown job ${jobId}`);\n return;\n }\n\n const now = Date.now();\n\n // Remove from active\n this.active.get(queue)?.delete(jobId);\n\n const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const hasMoreAttempts = job.state.attempts < maxAttempts;\n\n if (hasMoreAttempts) {\n // Calculate backoff delay for retry\n const backoffDelay = this.calculateBackoff(job);\n\n job.state.status = \"delayed\";\n job.state.availableAt = now + backoffDelay;\n job.state.error = error;\n job.state.stackTrace = stackTrace;\n job.state.lockedBy = undefined;\n job.state.lockedUntil = undefined;\n\n this.insertDelayed(queue, job);\n\n this.log.debug(`Job ${jobId} failed, will retry in ${backoffDelay}ms`, {\n queue,\n attempt: job.state.attempts,\n maxAttempts,\n error,\n });\n\n // Emit retrying event\n await this.emit({\n type: \"retrying\",\n queue,\n jobId,\n timestamp: now,\n error,\n attempt: job.state.attempts + 1,\n delay: backoffDelay,\n });\n } else {\n // No more retries, mark as permanently failed\n job.state.status = \"failed\";\n job.state.failedAt = now;\n job.state.error = error;\n job.state.stackTrace = stackTrace;\n job.state.lockedBy = undefined;\n job.state.lockedUntil = undefined;\n\n // Handle removeOnFail\n const removeOnFail = job.options.removeOnFail;\n if (removeOnFail === true) {\n // Remove immediately\n this.jobs.get(queue)?.delete(jobId);\n this.log.debug(\n `Job ${jobId} permanently failed and removed after ${job.state.attempts} attempts`,\n { queue, error },\n );\n } else {\n this.failed.get(queue)!.unshift(jobId);\n\n // If removeOnFail is a number, trim the list (0 means keep none)\n if (typeof removeOnFail === \"number\" && removeOnFail >= 0) {\n await this.cleanJobs(queue, \"failed\", { maxCount: removeOnFail });\n }\n\n this.log.debug(\n `Job ${jobId} permanently failed after ${job.state.attempts} attempts`,\n { queue, error },\n );\n }\n\n // Emit failed event\n await this.emit({\n type: \"failed\",\n queue,\n jobId,\n timestamp: now,\n error,\n stackTrace,\n attempts: job.state.attempts,\n });\n }\n }\n\n protected calculateBackoff(job: QueueJob): number {\n const backoff = job.options.backoff;\n const attempt = job.state.attempts;\n\n if (!backoff) {\n return DEFAULT_BACKOFF_DELAY;\n }\n\n const baseDelay = backoff.delay ?? DEFAULT_BACKOFF_DELAY;\n const maxDelay = backoff.maxDelay ?? DEFAULT_BACKOFF_MAX_DELAY;\n\n if (backoff.type === \"fixed\") {\n return baseDelay;\n }\n\n // Exponential backoff: delay * 2^(attempt-1)\n const exponentialDelay = baseDelay * 2 ** (attempt - 1);\n return Math.min(exponentialDelay, maxDelay);\n }\n\n public async renewJobLock(\n queue: string,\n jobId: string,\n workerId: string,\n ): Promise<boolean> {\n const job = this.jobs.get(queue)?.get(jobId);\n if (!job || job.state.lockedBy !== workerId) {\n return false;\n }\n\n job.state.lockedUntil =\n Date.now() + (job.options.lockDuration ?? DEFAULT_LOCK_DURATION);\n return true;\n }\n\n public async getJob(\n queue: string,\n jobId: string,\n ): Promise<QueueJob | undefined> {\n return this.jobs.get(queue)?.get(jobId);\n }\n\n public async getJobs(\n queue: string,\n status: QueueJobStatus,\n options?: QueueGetJobsOptions,\n ): Promise<QueueJob[]> {\n const limit = options?.limit ?? 100;\n const offset = options?.offset ?? 0;\n\n let jobIds: string[];\n switch (status) {\n case \"waiting\":\n jobIds = this.waiting.get(queue) ?? [];\n break;\n case \"delayed\":\n jobIds = this.delayed.get(queue) ?? [];\n break;\n case \"active\":\n jobIds = Array.from(this.active.get(queue) ?? []);\n break;\n case \"completed\":\n jobIds = this.completed.get(queue) ?? [];\n break;\n case \"failed\":\n jobIds = this.failed.get(queue) ?? [];\n break;\n default:\n jobIds = [];\n }\n\n const jobsMap = this.jobs.get(queue);\n if (!jobsMap) return [];\n\n return jobIds\n .slice(offset, offset + limit)\n .map((id) => jobsMap.get(id))\n .filter((job): job is QueueJob => job !== undefined);\n }\n\n public async getJobCounts(queue: string): Promise<QueueJobCounts> {\n return {\n waiting: this.waiting.get(queue)?.length ?? 0,\n delayed: this.delayed.get(queue)?.length ?? 0,\n active: this.active.get(queue)?.size ?? 0,\n completed: this.completed.get(queue)?.length ?? 0,\n failed: this.failed.get(queue)?.length ?? 0,\n };\n }\n\n public async promoteDelayedJobs(queue: string): Promise<number> {\n const delayedList = this.delayed.get(queue);\n if (!delayedList || delayedList.length === 0) return 0;\n\n const now = Date.now();\n let promoted = 0;\n\n while (delayedList.length > 0) {\n const jobId = delayedList[0];\n const job = this.jobs.get(queue)?.get(jobId);\n\n if (!job || (job.state.availableAt ?? 0) > now) {\n break; // No more jobs ready\n }\n\n // Remove from delayed\n delayedList.shift();\n\n // Move to waiting\n job.state.status = \"waiting\";\n this.insertWaiting(queue, job);\n promoted++;\n\n this.log.debug(`Promoted delayed job ${jobId}`, { queue });\n\n // Emit waiting event\n this.emit({\n type: \"waiting\",\n queue,\n jobId,\n timestamp: now,\n job,\n });\n }\n\n if (promoted > 0) {\n this.notifyJobWaiters(queue);\n }\n\n return promoted;\n }\n\n public async recoverStalledJobs(\n queue: string,\n stalledThresholdMs: number,\n ): Promise<string[]> {\n const activeSet = this.active.get(queue);\n if (!activeSet || activeSet.size === 0) return [];\n\n const now = Date.now();\n const stalledJobIds: string[] = [];\n\n for (const jobId of activeSet) {\n const job = this.jobs.get(queue)?.get(jobId);\n if (!job) continue;\n\n const lockExpired =\n (job.state.lockedUntil ?? 0) + stalledThresholdMs < now;\n if (lockExpired) {\n stalledJobIds.push(jobId);\n }\n }\n\n for (const jobId of stalledJobIds) {\n const job = this.jobs.get(queue)!.get(jobId)!;\n const workerId = job.state.lockedBy;\n activeSet.delete(jobId);\n\n const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const hasMoreAttempts = job.state.attempts < maxAttempts;\n\n // Emit stalled event\n await this.emit({\n type: \"stalled\",\n queue,\n jobId,\n timestamp: now,\n workerId,\n willRetry: hasMoreAttempts,\n });\n\n if (hasMoreAttempts) {\n // Return to waiting for retry\n job.state.status = \"waiting\";\n job.state.lockedBy = undefined;\n job.state.lockedUntil = undefined;\n job.state.error = \"Job stalled (worker timeout)\";\n this.insertWaiting(queue, job);\n\n this.log.warn(`Recovered stalled job ${jobId}, returning to waiting`, {\n queue,\n attempt: job.state.attempts,\n });\n\n // Emit waiting event\n await this.emit({\n type: \"waiting\",\n queue,\n jobId,\n timestamp: now,\n job,\n });\n } else {\n // No more retries\n job.state.status = \"failed\";\n job.state.failedAt = now;\n job.state.lockedBy = undefined;\n job.state.lockedUntil = undefined;\n job.state.error =\n \"Job stalled (worker timeout) - max attempts exceeded\";\n\n // Handle removeOnFail\n const removeOnFail = job.options.removeOnFail;\n if (removeOnFail === true) {\n this.jobs.get(queue)?.delete(jobId);\n } else {\n this.failed.get(queue)!.unshift(jobId);\n if (typeof removeOnFail === \"number\" && removeOnFail > 0) {\n await this.cleanJobs(queue, \"failed\", { maxCount: removeOnFail });\n }\n }\n\n this.log.warn(`Stalled job ${jobId} permanently failed`, {\n queue,\n attempts: job.state.attempts,\n });\n\n // Emit failed event\n await this.emit({\n type: \"failed\",\n queue,\n jobId,\n timestamp: now,\n error: job.state.error,\n attempts: job.state.attempts,\n });\n }\n }\n\n if (stalledJobIds.length > 0) {\n this.notifyJobWaiters(queue);\n }\n\n return stalledJobIds;\n }\n\n public async cleanJobs(\n queue: string,\n status: \"completed\" | \"failed\",\n options?: QueueCleanOptions,\n ): Promise<number> {\n const jobsList =\n status === \"completed\"\n ? this.completed.get(queue)\n : this.failed.get(queue);\n\n if (!jobsList || jobsList.length === 0) return 0;\n\n const jobsMap = this.jobs.get(queue);\n if (!jobsMap) return 0;\n\n const now = Date.now();\n const maxAge = options?.maxAge;\n const maxCount = options?.maxCount;\n\n let removed = 0;\n\n // Remove by age\n if (maxAge !== undefined) {\n const cutoff = now - maxAge;\n const toRemove: string[] = [];\n\n for (const jobId of jobsList) {\n const job = jobsMap.get(jobId);\n if (job) {\n const timestamp =\n status === \"completed\" ? job.state.completedAt : job.state.failedAt;\n if (timestamp && timestamp < cutoff) {\n toRemove.push(jobId);\n }\n }\n }\n\n for (const jobId of toRemove) {\n const idx = jobsList.indexOf(jobId);\n if (idx !== -1) {\n jobsList.splice(idx, 1);\n jobsMap.delete(jobId);\n removed++;\n }\n }\n }\n\n // Remove by count (keep only maxCount newest)\n if (maxCount !== undefined && jobsList.length > maxCount) {\n const toRemove = jobsList.splice(maxCount);\n for (const jobId of toRemove) {\n jobsMap.delete(jobId);\n removed++;\n }\n }\n\n return removed;\n }\n\n public async removeJob(queue: string, jobId: string): Promise<void> {\n const job = this.jobs.get(queue)?.get(jobId);\n if (!job) return;\n\n const previousStatus = job.state.status;\n\n // Remove from appropriate list based on status\n switch (job.state.status) {\n case \"waiting\": {\n const list = this.waiting.get(queue);\n const idx = list?.indexOf(jobId) ?? -1;\n if (idx !== -1) list!.splice(idx, 1);\n break;\n }\n case \"delayed\": {\n const list = this.delayed.get(queue);\n const idx = list?.indexOf(jobId) ?? -1;\n if (idx !== -1) list!.splice(idx, 1);\n break;\n }\n case \"active\":\n this.active.get(queue)?.delete(jobId);\n break;\n case \"completed\": {\n const list = this.completed.get(queue);\n const idx = list?.indexOf(jobId) ?? -1;\n if (idx !== -1) list!.splice(idx, 1);\n break;\n }\n case \"failed\": {\n const list = this.failed.get(queue);\n const idx = list?.indexOf(jobId) ?? -1;\n if (idx !== -1) list!.splice(idx, 1);\n break;\n }\n }\n\n this.jobs.get(queue)?.delete(jobId);\n\n // Emit removed event\n await this.emit({\n type: \"removed\",\n queue,\n jobId,\n timestamp: Date.now(),\n previousStatus,\n });\n }\n\n public cancelWaiters(): void {\n // Cancel all job waiters\n for (const waiter of this.jobWaiters) {\n clearTimeout(waiter.timer);\n waiter.resolve(undefined);\n }\n this.jobWaiters.clear();\n\n // Cancel all message waiters\n for (const waiter of this.messageWaiters) {\n clearTimeout(waiter.timer);\n waiter.resolve(undefined);\n }\n this.messageWaiters.clear();\n }\n}\n","import {\n createDescriptor,\n Descriptor,\n KIND,\n type Service,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type {\n QueueAddJobOptions,\n QueueJobBackoff,\n} from \"../interfaces/QueueJob.ts\";\nimport { MemoryQueueProvider } from \"../providers/MemoryQueueProvider.ts\";\nimport { QueueProvider } from \"../providers/QueueProvider.ts\";\n\n/**\n * Creates a queue descriptor for asynchronous message processing with background workers.\n *\n * The $queue descriptor enables powerful asynchronous communication patterns in your application.\n * It provides type-safe message queuing with automatic worker processing, making it perfect for\n * decoupling components and handling background tasks efficiently.\n *\n * **Background Processing**\n * - Automatic worker threads for non-blocking message processing\n * - Built-in retry mechanisms and error handling\n * - Dead letter queues for failed message handling\n * - Graceful shutdown and worker lifecycle management\n *\n * **Type Safety**\n * - Full TypeScript support with schema validation using TypeBox\n * - Type-safe message payloads with automatic inference\n * - Runtime validation of all queued messages\n * - Compile-time errors for invalid message structures\n *\n * **Storage Flexibility**\n * - Memory provider for development and testing\n * - Redis provider for production scalability and persistence\n * - Custom provider support for specialized backends\n * - Automatic failover and connection pooling\n *\n * **Performance & Scalability**\n * - Batch processing support for high-throughput scenarios\n * - Horizontal scaling with distributed queue backends\n * - Configurable concurrency and worker pools\n * - Efficient serialization and message routing\n *\n * **Reliability**\n * - Message persistence across application restarts\n * - Automatic retry with exponential backoff\n * - Dead letter handling for permanently failed messages\n * - Comprehensive logging and monitoring integration\n *\n * @example Basic notification queue\n * ```typescript\n * const emailQueue = $queue({\n * name: \"email-notifications\",\n * schema: t.object({\n * to: t.text(),\n * subject: t.text(),\n * body: t.text(),\n * priority: t.optional(t.enum([\"high\", \"normal\"]))\n * }),\n * handler: async (message) => {\n * await emailService.send(message.payload);\n * console.log(`Email sent to ${message.payload.to}`);\n * }\n * });\n *\n * // Push messages for background processing\n * await emailQueue.push({\n * to: \"user@example.com\",\n * subject: \"Welcome!\",\n * body: \"Welcome to our platform\",\n * priority: \"high\"\n * });\n * ```\n *\n * @example Batch processing with Redis\n * ```typescript\n * const imageQueue = $queue({\n * name: \"image-processing\",\n * provider: RedisQueueProvider,\n * schema: t.object({\n * imageId: t.text(),\n * operations: t.array(t.enum([\"resize\", \"compress\", \"thumbnail\"]))\n * }),\n * handler: async (message) => {\n * for (const op of message.payload.operations) {\n * await processImage(message.payload.imageId, op);\n * }\n * }\n * });\n *\n * // Batch processing multiple images\n * await imageQueue.push(\n * { imageId: \"img1\", operations: [\"resize\", \"thumbnail\"] },\n * { imageId: \"img2\", operations: [\"compress\"] },\n * { imageId: \"img3\", operations: [\"resize\", \"compress\", \"thumbnail\"] }\n * );\n * ```\n *\n * @example Development with memory provider\n * ```typescript\n * const taskQueue = $queue({\n * name: \"dev-tasks\",\n * provider: \"memory\",\n * schema: t.object({\n * taskType: t.enum([\"cleanup\", \"backup\", \"report\"]),\n * data: t.record(t.text(), t.any())\n * }),\n * handler: async (message) => {\n * switch (message.payload.taskType) {\n * case \"cleanup\":\n * await performCleanup(message.payload.data);\n * break;\n * case \"backup\":\n * await createBackup(message.payload.data);\n * break;\n * case \"report\":\n * await generateReport(message.payload.data);\n * break;\n * }\n * }\n * });\n * ```\n */\nexport const $queue = <T extends TSchema>(\n options: QueueDescriptorOptions<T>,\n): QueueDescriptor<T> => {\n return createDescriptor(QueueDescriptor<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface QueueDescriptorOptions<T extends TSchema> {\n /**\n * Unique name for the queue.\n *\n * This name is used for:\n * - Queue identification across the system\n * - Storage backend key generation\n * - Logging and monitoring\n * - Worker assignment and routing\n *\n * If not provided, defaults to the property key where the queue is declared.\n *\n * @example \"email-notifications\"\n * @example \"image-processing\"\n * @example \"order-fulfillment\"\n */\n name?: string;\n\n /**\n * Human-readable description of the queue's purpose.\n *\n * Used for:\n * - Documentation generation\n * - Monitoring dashboards\n * - Development team communication\n * - Queue management interfaces\n *\n * @example \"Process user registration emails and welcome sequences\"\n * @example \"Handle image uploads, resizing, and thumbnail generation\"\n * @example \"Manage order processing, payment, and shipping workflows\"\n */\n description?: string;\n\n /**\n * Queue storage provider configuration.\n *\n * Options:\n * - **\"memory\"**: In-memory queue (default for development, lost on restart)\n * - **Service<QueueProvider>**: Custom provider class (e.g., RedisQueueProvider)\n * - **undefined**: Uses the default queue provider from dependency injection\n *\n * **Provider Selection Guidelines**:\n * - Development: Use \"memory\" for fast, simple testing\n * - Production: Use Redis or database-backed providers for persistence\n * - High-throughput: Use specialized providers with connection pooling\n * - Distributed systems: Use Redis or message brokers for scalability\n *\n * @default Uses injected QueueProvider\n * @example \"memory\"\n * @example RedisQueueProvider\n * @example DatabaseQueueProvider\n */\n provider?: \"memory\" | Service<QueueProvider>;\n\n /**\n * TypeBox schema defining the structure of messages in this queue.\n *\n * This schema:\n * - Validates all messages pushed to the queue\n * - Provides full TypeScript type inference\n * - Ensures type safety between producers and consumers\n * - Enables automatic serialization/deserialization\n *\n * **Schema Design Best Practices**:\n * - Keep schemas simple and focused on the specific task\n * - Use optional fields for data that might not always be available\n * - Include version fields for schema evolution\n * - Use union types for different message types in the same queue\n *\n * @example\n * ```ts\n * t.object({\n * userId: t.text(),\n * action: t.enum([\"create\", \"update\"]),\n * data: t.record(t.text(), t.any()),\n * timestamp: t.optional(t.number())\n * })\n * ```\n */\n schema: T;\n\n /**\n * Message handler function that processes queue messages.\n *\n * This function:\n * - Runs in background worker threads for non-blocking processing\n * - Receives type-safe message payloads based on the schema\n * - Should be idempotent to handle potential retries\n * - Can throw errors to trigger retry mechanisms\n * - Has access to the full Alepha dependency injection container\n *\n * **Handler Best Practices**:\n * - Keep handlers focused on a single responsibility\n * - Use proper error handling and logging\n * - Make operations idempotent when possible\n * - Validate critical business logic within handlers\n * - Consider using transactions for data consistency\n *\n * @param message - The queue message with validated payload\n * @returns Promise that resolves when processing is complete\n *\n * @example\n * ```ts\n * handler: async (message) => {\n * const { userId, email, template } = message.payload;\n *\n * try {\n * await this.emailService.send({\n * to: email,\n * template,\n * data: { userId }\n * });\n *\n * await this.userService.markEmailSent(userId, template);\n * } catch (error) {\n * // Log error and let the queue system handle retries\n * this.logger.error(`Failed to send email to ${email}`, error);\n * throw error;\n * }\n * }\n * ```\n */\n handler?: (message: QueueMessage<T>) => Promise<void>;\n\n // ===========================================\n // Job Options (for crash recovery and retries)\n // ===========================================\n\n /**\n * Maximum number of processing attempts before the job is marked as failed.\n * Includes the initial attempt.\n *\n * Set this to enable automatic retries on failure.\n *\n * @default 1 (no retries)\n * @example 3 // Allows 2 retries after initial failure\n */\n maxAttempts?: number;\n\n /**\n * Backoff configuration for retries.\n * Controls the delay between retry attempts.\n *\n * @example\n * ```ts\n * backoff: {\n * type: \"exponential\",\n * delay: 1000, // Initial delay: 1 second\n * maxDelay: 60000 // Maximum delay: 1 minute\n * }\n * ```\n */\n backoff?: QueueJobBackoff;\n\n /**\n * Maximum time in milliseconds a job can be processed before it's considered stalled.\n * If the worker doesn't complete or extend the lock within this time, the job\n * can be picked up by another worker.\n *\n * Increase this for long-running jobs.\n *\n * @default 30000 (30 seconds)\n */\n lockDuration?: number;\n\n /**\n * Automatically remove jobs when they complete successfully.\n * - `true`: Remove immediately after completion\n * - `false`: Keep in completed list (default)\n * - `number`: Keep this many most recent completed jobs, remove older ones\n *\n * @default false\n * @example\n * ```ts\n * // Remove immediately after completion\n * removeOnComplete: true\n *\n * // Keep only the last 100 completed jobs\n * removeOnComplete: 100\n * ```\n */\n removeOnComplete?: boolean | number;\n\n /**\n * Automatically remove jobs when they fail permanently (after all retries exhausted).\n * - `true`: Remove immediately after failure\n * - `false`: Keep in failed list (default)\n * - `number`: Keep this many most recent failed jobs, remove older ones\n *\n * @default false\n * @example\n * ```ts\n * // Remove immediately after failure\n * removeOnFail: true\n *\n * // Keep only the last 50 failed jobs for debugging\n * removeOnFail: 50\n * ```\n */\n removeOnFail?: boolean | number;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class QueueDescriptor<T extends TSchema> extends Descriptor<\n QueueDescriptorOptions<T>\n> {\n protected readonly log = $logger();\n public readonly provider = this.$provider();\n\n /**\n * Push one or more payloads to the queue for background processing.\n *\n * Jobs will be processed with crash recovery, retries (if configured),\n * and proper lifecycle management.\n *\n * @param payloads - One or more payloads to queue\n */\n public async push(...payloads: Array<Static<T>>): Promise<void>;\n /**\n * Push a payload to the queue with specific options.\n *\n * @param payload - The payload to queue\n * @param options - Job options (priority, delay)\n */\n public async push(\n payload: Static<T>,\n options: QueueAddJobOptions,\n ): Promise<void>;\n\n public async push(\n payloadOrFirst: Static<T>,\n optionsOrSecond?: QueueAddJobOptions | Static<T>,\n ...rest: Array<Static<T>>\n ): Promise<void> {\n // Check if second argument is options object\n const isOptions =\n optionsOrSecond != null &&\n typeof optionsOrSecond === \"object\" &&\n (\"priority\" in optionsOrSecond || \"delay\" in optionsOrSecond);\n\n if (isOptions) {\n // Single payload with options\n const payload = this.alepha.codec.decode(\n this.options.schema,\n payloadOrFirst,\n );\n await this.provider.addJob(this.name, payload, {\n ...this.getDefaultJobOptions(),\n priority: (optionsOrSecond as QueueAddJobOptions).priority,\n delay: (optionsOrSecond as QueueAddJobOptions).delay,\n });\n this.log.debug(`Pushed job to queue ${this.name}`, {\n payload,\n options: optionsOrSecond,\n });\n } else {\n // Multiple payloads without per-job options\n const payloads =\n optionsOrSecond != null\n ? [payloadOrFirst, optionsOrSecond as Static<T>, ...rest]\n : [payloadOrFirst, ...rest];\n\n await Promise.all(\n payloads.map((p) => {\n const payload = this.alepha.codec.decode(this.options.schema, p);\n return this.provider.addJob(\n this.name,\n payload,\n this.getDefaultJobOptions(),\n );\n }),\n );\n\n this.log.debug(\n `Pushed ${payloads.length} job(s) to queue ${this.name}`,\n payloads,\n );\n }\n }\n\n /**\n * Get default job options from descriptor configuration.\n */\n protected getDefaultJobOptions() {\n return {\n maxAttempts: this.options.maxAttempts,\n backoff: this.options.backoff,\n lockDuration: this.options.lockDuration,\n removeOnComplete: this.options.removeOnComplete,\n removeOnFail: this.options.removeOnFail,\n };\n }\n\n public get name() {\n return this.options.name || this.config.propertyKey;\n }\n\n protected $provider() {\n if (!this.options.provider) {\n return this.alepha.inject(QueueProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryQueueProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n$queue[KIND] = QueueDescriptor;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface QueueMessageSchema {\n payload: TSchema;\n}\n\nexport interface QueueMessage<T extends TSchema> {\n payload: Static<T>;\n}\n","import {\n $env,\n $hook,\n $inject,\n Alepha,\n type Static,\n type TSchema,\n t,\n} from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $consumer } from \"../descriptors/$consumer.ts\";\nimport {\n $queue,\n type QueueDescriptor,\n type QueueMessage,\n} from \"../descriptors/$queue.ts\";\nimport type { QueueAcquiredJob } from \"../interfaces/QueueJob.ts\";\nimport { QueueProvider } from \"./QueueProvider.ts\";\n\nconst envSchema = t.object({\n /**\n * The timeout in seconds for blocking job acquisition.\n * Workers will check for shutdown after each timeout period.\n */\n QUEUE_WORKER_BLOCKING_TIMEOUT: t.integer({\n default: 5,\n }),\n /**\n * The number of workers to run concurrently. Defaults to 1.\n * Useful only if you are doing a lot of I/O.\n */\n QUEUE_WORKER_CONCURRENCY: t.integer({\n default: 1,\n }),\n /**\n * Interval in milliseconds for renewing job locks during processing.\n * Should be less than the job's lock duration.\n */\n QUEUE_WORKER_LOCK_RENEWAL_INTERVAL: t.integer({\n default: 10000, // 10 seconds\n }),\n /**\n * Interval in milliseconds for the scheduler to check delayed jobs and stalled jobs.\n */\n QUEUE_SCHEDULER_INTERVAL: t.integer({\n default: 5000, // 5 seconds\n }),\n /**\n * Threshold in milliseconds after lock expiration to consider a job stalled.\n */\n QUEUE_STALLED_THRESHOLD: t.integer({\n default: 5000, // 5 seconds grace period after lock expires\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\nexport class WorkerProvider {\n protected readonly log = $logger();\n protected readonly env = $env(envSchema);\n protected readonly alepha = $inject(Alepha);\n protected readonly queueProvider = $inject(QueueProvider);\n protected readonly dateTime = $inject(DateTimeProvider);\n\n protected workerPromises: Array<Promise<void>> = [];\n protected workersRunning = 0;\n protected shouldStop = false;\n protected consumers: Array<Consumer> = [];\n protected consumersByProvider: Map<QueueProvider, Consumer[]> = new Map();\n protected schedulerPromise: Promise<void> | undefined;\n protected schedulerRunning = false;\n protected abortController: AbortController | undefined;\n protected workerId: string = `worker_${process.pid}_${Date.now()}`;\n\n public get isRunning(): boolean {\n return this.workersRunning > 0;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n priority: \"last\",\n handler: () => {\n for (const queue of this.alepha.descriptors($queue)) {\n const handler = queue.options.handler;\n if (handler) {\n this.consumers.push({\n handler,\n queue,\n });\n }\n }\n\n for (const consumer of this.alepha.descriptors($consumer)) {\n this.consumers.push(consumer.options);\n }\n\n // Group consumers by their provider for efficient blocking\n for (const consumer of this.consumers) {\n const provider = consumer.queue.provider;\n const list = this.consumersByProvider.get(provider) ?? [];\n list.push(consumer);\n this.consumersByProvider.set(provider, list);\n }\n\n if (this.consumers.length > 0) {\n this.startWorkers();\n this.startScheduler();\n this.log.debug(\n `Watching for ${this.consumers.length} queue${this.consumers.length > 1 ? \"s\" : \"\"} with ${this.env.QUEUE_WORKER_CONCURRENCY} worker${\n this.env.QUEUE_WORKER_CONCURRENCY > 1 ? \"s\" : \"\"\n }.`,\n );\n }\n },\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Engine part - this is the part that will run the workers and process the jobs\n\n /**\n * Start the workers.\n * Each worker acquires jobs and processes them with proper lifecycle management.\n */\n protected startWorkers(): void {\n const workerToStart =\n this.env.QUEUE_WORKER_CONCURRENCY - this.workersRunning;\n\n for (let i = 0; i < workerToStart; i++) {\n this.workersRunning += 1;\n const workerIndex = i;\n const localWorkerId = `${this.workerId}_${workerIndex}`;\n this.log.debug(`Starting worker n-${workerIndex}`);\n\n const workerLoop = async () => {\n while (!this.shouldStop) {\n this.log.trace(`Worker n-${workerIndex} is waiting for jobs`);\n const acquired = await this.acquireNextJob(localWorkerId);\n if (acquired) {\n await this.processJob(acquired, localWorkerId);\n }\n // If no job (timeout), loop continues and checks shouldStop\n }\n this.log.info(`Worker n-${workerIndex} has stopped`);\n };\n\n this.workerPromises.push(\n workerLoop().catch((e) => {\n this.log.error(`Worker n-${workerIndex} has crashed`, e);\n this.workersRunning -= 1;\n }),\n );\n }\n }\n\n /**\n * Start the scheduler for delayed job promotion and stalled job recovery.\n */\n protected startScheduler(): void {\n if (this.schedulerRunning) return;\n this.schedulerRunning = true;\n this.abortController = new AbortController();\n this.log.debug(\"Starting scheduler\");\n\n const schedulerLoop = async () => {\n while (!this.shouldStop) {\n try {\n await this.runSchedulerCycle();\n } catch (e) {\n this.log.error(\"Scheduler cycle failed\", e);\n }\n\n // Wait for next interval (interruptible via AbortController)\n await this.dateTime.wait(this.env.QUEUE_SCHEDULER_INTERVAL, {\n signal: this.abortController?.signal,\n });\n }\n this.log.debug(\"Scheduler stopped\");\n };\n\n this.schedulerPromise = schedulerLoop();\n }\n\n /**\n * Run one cycle of the scheduler.\n * Promotes delayed jobs and recovers stalled jobs.\n */\n protected async runSchedulerCycle(): Promise<void> {\n for (const [provider, consumers] of this.consumersByProvider) {\n const queues = new Set(consumers.map((c) => c.queue.name));\n\n for (const queue of queues) {\n // Promote delayed jobs\n const promoted = await provider.promoteDelayedJobs(queue);\n if (promoted > 0) {\n this.log.debug(`Promoted ${promoted} delayed jobs in queue ${queue}`);\n }\n\n // Recover stalled jobs\n const recovered = await provider.recoverStalledJobs(\n queue,\n this.env.QUEUE_STALLED_THRESHOLD,\n );\n if (recovered.length > 0) {\n this.log.warn(\n `Recovered ${recovered.length} stalled jobs in queue ${queue}`,\n );\n }\n }\n }\n }\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: async () => {\n if (this.consumers.length > 0) {\n await this.stopWorkers();\n }\n },\n });\n\n /**\n * Acquire the next available job from any provider.\n */\n protected async acquireNextJob(\n localWorkerId: string,\n ): Promise<AcquiredJobWithConsumer | undefined> {\n for (const [provider, consumers] of this.consumersByProvider) {\n const queueNames = consumers.map((c) => c.queue.name);\n const acquired = await provider.acquireJob(\n queueNames,\n localWorkerId,\n this.env.QUEUE_WORKER_BLOCKING_TIMEOUT,\n );\n\n if (acquired) {\n const consumer = consumers.find((c) => c.queue.name === acquired.queue);\n if (consumer) {\n return { acquired, consumer, provider };\n }\n }\n }\n\n return undefined;\n }\n\n /**\n * Process a job with proper lifecycle management.\n * - Starts a lock renewal interval\n * - Calls the handler\n * - Marks job as completed or failed\n */\n protected async processJob(\n { acquired, consumer, provider }: AcquiredJobWithConsumer,\n localWorkerId: string,\n ): Promise<void> {\n const { queue, job } = acquired;\n\n // Start lock renewal heartbeat\n const lockRenewalInterval = this.dateTime.createInterval(\n async () => {\n try {\n const renewed = await provider.renewJobLock(\n queue,\n job.id,\n localWorkerId,\n );\n if (!renewed) {\n this.log.warn(\n `Failed to renew lock for job ${job.id}, lock may have been stolen`,\n );\n }\n } catch (e) {\n this.log.error(`Error renewing lock for job ${job.id}`, e);\n }\n },\n this.env.QUEUE_WORKER_LOCK_RENEWAL_INTERVAL,\n true, // start immediately\n );\n\n try {\n // Decode payload and run handler\n const payload = this.alepha.codec.decode(\n consumer.queue.options.schema,\n job.payload,\n );\n\n await this.alepha.context.run(() => consumer.handler({ payload }));\n\n // Mark as completed\n await provider.completeJob(queue, job.id);\n this.log.debug(`Job ${job.id} completed successfully`, { queue });\n } catch (e) {\n // Mark as failed (provider handles retry logic)\n const error = e instanceof Error ? e.message : String(e);\n const stackTrace = e instanceof Error ? e.stack : undefined;\n await provider.failJob(queue, job.id, error, stackTrace);\n this.log.error(`Job ${job.id} failed`, e);\n } finally {\n this.dateTime.clearInterval(lockRenewalInterval);\n }\n }\n\n /**\n * Stop the workers and scheduler.\n */\n protected async stopWorkers(): Promise<void> {\n this.shouldStop = true;\n this.workersRunning = 0;\n this.schedulerRunning = false;\n\n // Abort the scheduler's wait immediately\n this.abortController?.abort();\n\n // Cancel all pending acquireJob waiters to unblock workers immediately\n for (const provider of this.consumersByProvider.keys()) {\n provider.cancelWaiters();\n }\n\n this.log.trace(\"Stopping workers...\");\n this.log.trace(\"Waiting for workers to finish...\");\n\n const promises: Promise<void>[] = [...this.workerPromises];\n if (this.schedulerPromise) {\n promises.push(this.schedulerPromise);\n }\n\n await Promise.all(promises);\n }\n}\n\nexport interface Consumer<T extends TSchema = TSchema> {\n queue: QueueDescriptor<T>;\n handler: (message: QueueMessage<T>) => Promise<void>;\n}\n\nexport interface AcquiredJobWithConsumer {\n acquired: QueueAcquiredJob;\n consumer: Consumer;\n provider: QueueProvider;\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { $consumer } from \"./descriptors/$consumer.ts\";\nimport { $queue } from \"./descriptors/$queue.ts\";\nimport { MemoryQueueProvider } from \"./providers/MemoryQueueProvider.ts\";\nimport { QueueProvider } from \"./providers/QueueProvider.ts\";\nimport { WorkerProvider } from \"./providers/WorkerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./descriptors/$consumer.ts\";\nexport * from \"./descriptors/$queue.ts\";\nexport * from \"./interfaces/QueueJob.ts\";\nexport * from \"./providers/MemoryQueueProvider.ts\";\nexport * from \"./providers/QueueProvider.ts\";\nexport * from \"./providers/WorkerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides asynchronous message queuing and processing capabilities through declarative queue descriptors.\n *\n * The queue module enables reliable background job processing and message passing using the `$queue` descriptor\n * on class properties. It supports schema validation, automatic retries, and multiple queue backends for\n * building scalable, decoupled applications with robust error handling.\n *\n * @see {@link $queue}\n * @see {@link $consumer}\n * @module alepha.queue\n */\nexport const AlephaQueue = $module({\n name: \"alepha.queue\",\n descriptors: [$queue, $consumer],\n services: [QueueProvider, MemoryQueueProvider, WorkerProvider],\n register: (alepha: Alepha) =>\n alepha\n .with({\n optional: true,\n provide: QueueProvider,\n use: MemoryQueueProvider,\n })\n .with(WorkerProvider),\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["waiter: MessageWaiter","job: QueueJob<T>","waiter: JobWaiter","result","jobIds: string[]","stalledJobIds: string[]","toRemove: string[]","promises: Promise<void>[]"],"sources":["../../src/queue/primitives/$consumer.ts","../../src/queue/providers/QueueProvider.ts","../../src/queue/providers/MemoryQueueProvider.ts","../../src/queue/primitives/$queue.ts","../../src/queue/providers/WorkerProvider.ts","../../src/queue/index.ts"],"sourcesContent":["import {\n createPrimitive,\n KIND,\n Primitive,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport type { QueuePrimitive } from \"./$queue.ts\";\n\n/**\n * Creates a consumer primitive to process messages from a specific queue.\n *\n * Provides a dedicated message consumer that connects to a queue and processes messages\n * with custom handler logic, enabling scalable architectures where multiple consumers\n * can process messages from the same queue.\n *\n * **Key Features**\n * - Seamless integration with any $queue primitive\n * - Full type safety inherited from queue schema\n * - Automatic worker management for background processing\n * - Built-in error handling and retry mechanisms\n * - Support for multiple consumers per queue for horizontal scaling\n *\n * **Common Use Cases**\n * - Email sending and notification services\n * - Image and media processing workers\n * - Data synchronization and background jobs\n *\n * @example\n * ```ts\n * class EmailService {\n * emailQueue = $queue({\n * name: \"emails\",\n * schema: t.object({\n * to: t.text(),\n * subject: t.text(),\n * body: t.text()\n * })\n * });\n *\n * emailConsumer = $consumer({\n * queue: this.emailQueue,\n * handler: async (message) => {\n * const { to, subject, body } = message.payload;\n * await this.sendEmail(to, subject, body);\n * }\n * });\n *\n * async sendWelcomeEmail(userEmail: string) {\n * await this.emailQueue.push({\n * to: userEmail,\n * subject: \"Welcome!\",\n * body: \"Thanks for joining.\"\n * });\n * }\n * }\n * ```\n */\nexport const $consumer = <T extends TSchema>(\n options: ConsumerPrimitiveOptions<T>,\n): ConsumerPrimitive<T> => {\n return createPrimitive(ConsumerPrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface ConsumerPrimitiveOptions<T extends TSchema> {\n /**\n * The queue primitive that this consumer will process messages from.\n *\n * This establishes the connection between the consumer and its source queue:\n * - The consumer inherits the queue's message schema for type safety\n * - Messages pushed to the queue will be automatically routed to this consumer\n * - Multiple consumers can be attached to the same queue for parallel processing\n * - The consumer will use the queue's provider and configuration settings\n *\n * **Queue Integration Benefits**:\n * - Type safety: Consumer handler gets fully typed message payloads\n * - Schema validation: Messages are validated before reaching the consumer\n * - Error handling: Failed messages can be retried or moved to dead letter queues\n * - Monitoring: Queue metrics include consumer processing statistics\n *\n * @example\n * ```ts\n * // First, define a queue\n * emailQueue = $queue({\n * name: \"emails\",\n * schema: t.object({ to: t.text(), subject: t.text() })\n * });\n *\n * // Then, create a consumer for that queue\n * emailConsumer = $consumer({\n * queue: this.emailQueue, // Reference the queue primitive\n * handler: async (message) => { } // process email\n * });\n * ```\n */\n queue: QueuePrimitive<T>;\n\n /**\n * Message handler function that processes individual messages from the queue.\n *\n * This function:\n * - Receives fully typed and validated message payloads from the connected queue\n * - Runs in the background worker system for non-blocking operation\n * - Should implement the core business logic for processing this message type\n * - Can throw errors to trigger the queue's retry mechanisms\n * - Has access to the full Alepha dependency injection container\n * - Should be idempotent to handle potential duplicate deliveries\n *\n * **Handler Design Guidelines**:\n * - Keep handlers focused on a single responsibility\n * - Use proper error handling and meaningful error messages\n * - Log important processing steps for debugging and monitoring\n * - Consider transaction boundaries for data consistency\n * - Make operations idempotent when possible\n * - Validate business rules within the handler logic\n *\n * **Error Handling Strategy**:\n * - Throw errors for temporary failures that should be retried\n * - Log and handle permanent failures gracefully\n * - Use specific error types to control retry behavior\n * - Consider implementing circuit breakers for external service calls\n *\n * @param message - The queue message containing the validated payload\n * @param message.payload - The typed message data based on the queue's schema\n * @returns Promise that resolves when processing is complete\n *\n * @example\n * ```ts\n * handler: async (message) => {\n * const { userId, action, data } = message.payload;\n *\n * try {\n * // Log processing start\n * this.logger.info(`Processing ${action} for user ${userId}`);\n *\n * // Validate business rules\n * if (!await this.userService.exists(userId)) {\n * throw new Error(`User ${userId} not found`);\n * }\n *\n * // Perform the main processing logic\n * switch (action) {\n * case \"create\":\n * await this.processCreation(userId, data);\n * break;\n * case \"update\":\n * await this.processUpdate(userId, data);\n * break;\n * default:\n * throw new Error(`Unknown action: ${action}`);\n * }\n *\n * // Log successful completion\n * this.logger.info(`Successfully processed ${action} for user ${userId}`);\n *\n * } catch (error) {\n * // Log error with context\n * this.logger.error(`Failed to process ${action} for user ${userId}`, {\n * error: error.message,\n * userId,\n * action,\n * data\n * });\n *\n * // Re-throw to trigger queue retry mechanism\n * throw error;\n * }\n * }\n * ```\n */\n handler: (message: { payload: Static<T> }) => Promise<void>;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class ConsumerPrimitive<T extends TSchema> extends Primitive<\n ConsumerPrimitiveOptions<T>\n> {}\n\n$consumer[KIND] = ConsumerPrimitive;\n","import type {\n QueueAcquiredJob,\n QueueCleanOptions,\n QueueEvent,\n QueueEventHandler,\n QueueEventMap,\n QueueEventType,\n QueueGetJobsOptions,\n QueueJob,\n QueueJobCounts,\n QueueJobOptions,\n QueueJobStatus,\n} from \"../interfaces/QueueJob.ts\";\n\n/**\n * Queue provider interface supporting both simple message-based and advanced job-based operations.\n *\n * The simple API (push/pop/popBlocking) is for basic fire-and-forget messaging.\n * The job API provides crash recovery, retries, delayed jobs, priorities, and job history.\n */\nexport abstract class QueueProvider {\n // ===========================================\n // Event System\n // ===========================================\n\n protected eventHandlers: Map<\n QueueEventType | \"*\",\n Set<QueueEventHandler<QueueEvent>>\n > = new Map();\n\n /**\n * Subscribe to queue events.\n *\n * @param event Event type to listen for, or \"*\" for all events.\n * @param handler Handler function to call when event occurs.\n * @returns Unsubscribe function.\n *\n * @example\n * ```ts\n * // Listen for completed events\n * const unsubscribe = provider.on(\"completed\", (event) => {\n * console.log(`Job ${event.jobId} completed in ${event.duration}ms`);\n * });\n *\n * // Listen for all events\n * provider.on(\"*\", (event) => {\n * console.log(`Event: ${event.type} for job ${event.jobId}`);\n * });\n *\n * // Unsubscribe later\n * unsubscribe();\n * ```\n */\n public on<T extends QueueEventType>(\n event: T,\n handler: QueueEventHandler<QueueEventMap[T]>,\n ): () => void;\n public on(event: \"*\", handler: QueueEventHandler<QueueEvent>): () => void;\n public on(\n event: QueueEventType | \"*\",\n handler: QueueEventHandler<QueueEvent>,\n ): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler);\n\n return () => {\n this.eventHandlers.get(event)?.delete(handler);\n };\n }\n\n /**\n * Emit a queue event to all registered handlers.\n *\n * @param event The event to emit.\n */\n protected async emit(event: QueueEvent): Promise<void> {\n const handlers = [\n ...(this.eventHandlers.get(event.type) ?? []),\n ...(this.eventHandlers.get(\"*\") ?? []),\n ];\n\n await Promise.all(handlers.map((handler) => handler(event)));\n }\n // ===========================================\n // Simple Message API (backward compatible)\n // ===========================================\n\n /**\n * Push a message to the queue.\n *\n * @param queue Name of the queue to push the message to.\n * @param message String message to be pushed to the queue.\n */\n public abstract push(queue: string, message: string): Promise<void>;\n\n /**\n * Pop a message from the queue.\n *\n * @param queue Name of the queue to pop the message from.\n * @returns The message popped or `undefined` if the queue is empty.\n */\n public abstract pop(queue: string): Promise<string | undefined>;\n\n /**\n * Pop a message from one of the specified queues, blocking until available or timeout.\n *\n * @param queues Array of queue names to listen on.\n * @param timeoutSeconds Maximum time to wait in seconds.\n * @returns Object with queue name and message, or `undefined` if timeout expired.\n */\n public abstract popBlocking(\n queues: string[],\n timeoutSeconds: number,\n ): Promise<{ queue: string; message: string } | undefined>;\n\n // ===========================================\n // Job-based API (advanced features)\n // ===========================================\n\n /**\n * Add a job to the queue.\n *\n * @param queue Queue name.\n * @param payload Job data to process.\n * @param options Job options (priority, delay, retries, etc.).\n * @returns The created job.\n */\n public abstract addJob<T>(\n queue: string,\n payload: T,\n options?: QueueJobOptions,\n ): Promise<QueueJob<T>>;\n\n /**\n * Acquire the next available job for processing.\n *\n * This atomically:\n * 1. Finds the highest priority job that is ready for processing\n * 2. Moves it to \"active\" status\n * 3. Sets a lock with the worker ID\n *\n * @param queues Queue names to check (in order of preference).\n * @param workerId Unique identifier for the worker acquiring the job.\n * @param timeoutSeconds Maximum time to wait for a job.\n * @returns The acquired job or undefined if timeout.\n */\n public abstract acquireJob(\n queues: string[],\n workerId: string,\n timeoutSeconds: number,\n ): Promise<QueueAcquiredJob | undefined>;\n\n /**\n * Mark a job as completed successfully.\n *\n * @param queue Queue name.\n * @param jobId Job ID.\n * @param result Optional result data from processing.\n */\n public abstract completeJob(\n queue: string,\n jobId: string,\n result?: unknown,\n ): Promise<void>;\n\n /**\n * Mark a job as failed.\n *\n * If the job has remaining retry attempts, it will be moved to \"delayed\" status\n * (for backoff) or \"waiting\" status. Otherwise, it will be moved to \"failed\" status.\n *\n * @param queue Queue name.\n * @param jobId Job ID.\n * @param error Error message.\n * @param stackTrace Optional stack trace.\n */\n public abstract failJob(\n queue: string,\n jobId: string,\n error: string,\n stackTrace?: string,\n ): Promise<void>;\n\n /**\n * Extend the lock on an active job.\n *\n * Workers should call this periodically while processing long-running jobs\n * to prevent them from being considered stalled.\n *\n * @param queue Queue name.\n * @param jobId Job ID.\n * @param workerId Worker ID (must match the lock holder).\n * @returns True if lock was extended, false if job is not locked by this worker.\n */\n public abstract renewJobLock(\n queue: string,\n jobId: string,\n workerId: string,\n ): Promise<boolean>;\n\n /**\n * Get a job by ID.\n *\n * @param queue Queue name.\n * @param jobId Job ID.\n * @returns The job or undefined if not found.\n */\n public abstract getJob(\n queue: string,\n jobId: string,\n ): Promise<QueueJob | undefined>;\n\n /**\n * Get jobs by status.\n *\n * @param queue Queue name.\n * @param status Job status to filter by.\n * @param options Pagination options.\n * @returns Array of jobs.\n */\n public abstract getJobs(\n queue: string,\n status: QueueJobStatus,\n options?: QueueGetJobsOptions,\n ): Promise<QueueJob[]>;\n\n /**\n * Get job counts by status.\n *\n * @param queue Queue name.\n * @returns Object with counts for each status.\n */\n public abstract getJobCounts(queue: string): Promise<QueueJobCounts>;\n\n /**\n * Promote delayed jobs that are ready for processing.\n *\n * Moves jobs from \"delayed\" to \"waiting\" status when their availableAt time has passed.\n *\n * @param queue Queue name.\n * @returns Number of jobs promoted.\n */\n public abstract promoteDelayedJobs(queue: string): Promise<number>;\n\n /**\n * Recover stalled jobs.\n *\n * Finds jobs in \"active\" status whose locks have expired and either:\n * - Moves them back to \"waiting\" for retry (if attempts remaining)\n * - Moves them to \"failed\" (if no attempts remaining)\n *\n * @param queue Queue name.\n * @param stalledThresholdMs Jobs with expired locks older than this are considered stalled.\n * @returns Array of recovered job IDs.\n */\n public abstract recoverStalledJobs(\n queue: string,\n stalledThresholdMs: number,\n ): Promise<string[]>;\n\n /**\n * Remove old completed or failed jobs.\n *\n * @param queue Queue name.\n * @param status Status to clean (\"completed\" or \"failed\").\n * @param options Cleaning options (maxAge, maxCount).\n * @returns Number of jobs removed.\n */\n public abstract cleanJobs(\n queue: string,\n status: \"completed\" | \"failed\",\n options?: QueueCleanOptions,\n ): Promise<number>;\n\n /**\n * Remove a specific job.\n *\n * @param queue Queue name.\n * @param jobId Job ID.\n */\n public abstract removeJob(queue: string, jobId: string): Promise<void>;\n\n /**\n * Cancel all pending waiters.\n *\n * This is called during shutdown to immediately release all blocking\n * acquireJob calls, preventing shutdown delays.\n */\n public abstract cancelWaiters(): void;\n}\n\n// Re-export types for convenience\nexport type {\n QueueAcquiredJob,\n QueueAddJobOptions,\n QueueCleanOptions,\n QueueEvent,\n QueueEventActive,\n QueueEventBase,\n QueueEventCompleted,\n QueueEventFailed,\n QueueEventHandler,\n QueueEventMap,\n QueueEventProgress,\n QueueEventRemoved,\n QueueEventRetrying,\n QueueEventStalled,\n QueueEventType,\n QueueEventWaiting,\n QueueGetJobsOptions,\n QueueJob,\n QueueJobBackoff,\n QueueJobCounts,\n QueueJobOptions,\n QueueJobState,\n QueueJobStatus,\n} from \"../interfaces/QueueJob.ts\";\n","import { $logger } from \"alepha/logger\";\nimport type {\n QueueAcquiredJob,\n QueueCleanOptions,\n QueueGetJobsOptions,\n QueueJob,\n QueueJobCounts,\n QueueJobOptions,\n QueueJobStatus,\n} from \"../interfaces/QueueJob.ts\";\nimport { QueueProvider } from \"./QueueProvider.ts\";\n\n// Default job options\nconst DEFAULT_MAX_ATTEMPTS = 1;\nconst DEFAULT_LOCK_DURATION = 30000; // 30 seconds\nconst DEFAULT_BACKOFF_DELAY = 1000; // 1 second\nconst DEFAULT_BACKOFF_MAX_DELAY = 30000; // 30 seconds\n\ninterface MessageWaiter {\n queues: Set<string>;\n resolve: (result: { queue: string; message: string } | undefined) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface JobWaiter {\n queues: Set<string>;\n workerId: string;\n resolve: (result: QueueAcquiredJob | undefined) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * In-memory queue provider with full job support.\n *\n * This provider stores all data in memory and is suitable for:\n * - Development and testing\n * - Single-instance applications\n * - Scenarios where job persistence across restarts is not required\n */\nexport class MemoryQueueProvider extends QueueProvider {\n protected readonly log = $logger();\n\n // Simple message API storage\n protected messageQueues: Record<string, string[]> = {};\n protected messageWaiters: Set<MessageWaiter> = new Set();\n\n // Job API storage (per queue)\n protected jobs: Map<string, Map<string, QueueJob>> = new Map(); // queue -> jobId -> job\n protected waiting: Map<string, string[]> = new Map(); // queue -> jobIds (sorted by priority)\n protected delayed: Map<string, string[]> = new Map(); // queue -> jobIds (sorted by availableAt)\n protected active: Map<string, Set<string>> = new Map(); // queue -> jobIds\n protected completed: Map<string, string[]> = new Map(); // queue -> jobIds (newest first)\n protected failed: Map<string, string[]> = new Map(); // queue -> jobIds (newest first)\n protected jobWaiters: Set<JobWaiter> = new Set();\n\n protected jobIdCounter = 0;\n\n // ===========================================\n // Simple Message API\n // ===========================================\n\n public async push(queue: string, ...messages: string[]): Promise<void> {\n if (this.messageQueues[queue] == null) {\n this.messageQueues[queue] = [];\n }\n\n for (const message of messages) {\n const waiter = this.findMessageWaiter(queue);\n if (waiter) {\n this.removeMessageWaiter(waiter);\n waiter.resolve({ queue, message });\n } else {\n this.messageQueues[queue].push(message);\n }\n }\n }\n\n public async pop(queue: string): Promise<string | undefined> {\n return this.messageQueues[queue]?.shift();\n }\n\n public async popBlocking(\n queues: string[],\n timeoutSeconds: number,\n ): Promise<{ queue: string; message: string } | undefined> {\n for (const queue of queues) {\n const message = this.messageQueues[queue]?.shift();\n if (message) {\n return { queue, message };\n }\n }\n\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this.removeMessageWaiter(waiter);\n resolve(undefined);\n }, timeoutSeconds * 1000);\n\n const waiter: MessageWaiter = {\n queues: new Set(queues),\n resolve: (result) => {\n clearTimeout(timer);\n resolve(result);\n },\n timer,\n };\n\n this.messageWaiters.add(waiter);\n });\n }\n\n protected findMessageWaiter(queue: string): MessageWaiter | undefined {\n for (const waiter of this.messageWaiters) {\n if (waiter.queues.has(queue)) {\n return waiter;\n }\n }\n return undefined;\n }\n\n protected removeMessageWaiter(waiter: MessageWaiter): void {\n clearTimeout(waiter.timer);\n this.messageWaiters.delete(waiter);\n }\n\n // ===========================================\n // Job API Implementation\n // ===========================================\n\n protected generateJobId(): string {\n return `job_${++this.jobIdCounter}_${Date.now()}`;\n }\n\n protected ensureQueueStructures(queue: string): void {\n if (!this.jobs.has(queue)) {\n this.jobs.set(queue, new Map());\n this.waiting.set(queue, []);\n this.delayed.set(queue, []);\n this.active.set(queue, new Set());\n this.completed.set(queue, []);\n this.failed.set(queue, []);\n }\n }\n\n public async addJob<T>(\n queue: string,\n payload: T,\n options?: QueueJobOptions,\n ): Promise<QueueJob<T>> {\n this.ensureQueueStructures(queue);\n\n const now = Date.now();\n const delay = options?.delay ?? 0;\n const isDelayed = delay > 0;\n\n const job: QueueJob<T> = {\n id: this.generateJobId(),\n queue,\n payload,\n options: {\n priority: options?.priority ?? 0,\n delay: options?.delay ?? 0,\n maxAttempts: options?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,\n backoff: options?.backoff,\n lockDuration: options?.lockDuration ?? DEFAULT_LOCK_DURATION,\n removeOnComplete: options?.removeOnComplete,\n removeOnFail: options?.removeOnFail,\n },\n state: {\n status: isDelayed ? \"delayed\" : \"waiting\",\n attempts: 0,\n createdAt: now,\n availableAt: isDelayed ? now + delay : now,\n },\n };\n\n this.jobs.get(queue)!.set(job.id, job);\n\n if (isDelayed) {\n this.insertDelayed(queue, job);\n } else {\n this.insertWaiting(queue, job);\n this.notifyJobWaiters(queue);\n }\n\n this.log.debug(`Added job ${job.id} to queue ${queue}`, {\n status: job.state.status,\n priority: job.options.priority,\n });\n\n // Emit waiting event\n if (!isDelayed) {\n await this.emit({\n type: \"waiting\",\n queue,\n jobId: job.id,\n timestamp: now,\n job,\n });\n }\n\n return job;\n }\n\n protected insertWaiting(queue: string, job: QueueJob): void {\n const waitingList = this.waiting.get(queue)!;\n const priority = job.options.priority ?? 0;\n\n // Insert in priority order (lower priority value = higher priority)\n let insertIndex = waitingList.length;\n for (let i = 0; i < waitingList.length; i++) {\n const existingJob = this.jobs.get(queue)!.get(waitingList[i]);\n if (existingJob && (existingJob.options.priority ?? 0) > priority) {\n insertIndex = i;\n break;\n }\n }\n waitingList.splice(insertIndex, 0, job.id);\n }\n\n protected insertDelayed(queue: string, job: QueueJob): void {\n const delayedList = this.delayed.get(queue)!;\n const availableAt = job.state.availableAt ?? 0;\n\n // Insert sorted by availableAt (ascending)\n let insertIndex = delayedList.length;\n for (let i = 0; i < delayedList.length; i++) {\n const existingJob = this.jobs.get(queue)!.get(delayedList[i]);\n if (existingJob && (existingJob.state.availableAt ?? 0) > availableAt) {\n insertIndex = i;\n break;\n }\n }\n delayedList.splice(insertIndex, 0, job.id);\n }\n\n protected notifyJobWaiters(queue: string): void {\n for (const waiter of this.jobWaiters) {\n if (waiter.queues.has(queue)) {\n // Try to acquire a job for this waiter\n const result = this.tryAcquireJob(\n Array.from(waiter.queues),\n waiter.workerId,\n );\n if (result) {\n this.removeJobWaiter(waiter);\n waiter.resolve(result);\n return;\n }\n }\n }\n }\n\n protected removeJobWaiter(waiter: JobWaiter): void {\n clearTimeout(waiter.timer);\n this.jobWaiters.delete(waiter);\n }\n\n protected tryAcquireJob(\n queues: string[],\n workerId: string,\n ): QueueAcquiredJob | undefined {\n const now = Date.now();\n\n for (const queue of queues) {\n const waitingList = this.waiting.get(queue);\n if (!waitingList || waitingList.length === 0) continue;\n\n const jobId = waitingList.shift()!;\n const job = this.jobs.get(queue)!.get(jobId);\n if (!job) continue;\n\n // Move to active\n job.state.status = \"active\";\n job.state.attempts += 1;\n job.state.lockedBy = workerId;\n job.state.lockedUntil =\n now + (job.options.lockDuration ?? DEFAULT_LOCK_DURATION);\n job.state.processedAt = now;\n\n this.active.get(queue)!.add(jobId);\n\n this.log.debug(`Worker ${workerId} acquired job ${jobId}`, {\n queue,\n attempt: job.state.attempts,\n });\n\n // Emit active event (fire and forget to not block acquisition)\n this.emit({\n type: \"active\",\n queue,\n jobId,\n timestamp: now,\n workerId,\n attempt: job.state.attempts,\n });\n\n return { queue, job };\n }\n\n return undefined;\n }\n\n public async acquireJob(\n queues: string[],\n workerId: string,\n timeoutSeconds: number,\n ): Promise<QueueAcquiredJob | undefined> {\n // Ensure queue structures exist\n for (const queue of queues) {\n this.ensureQueueStructures(queue);\n }\n\n // Try to acquire immediately\n const result = this.tryAcquireJob(queues, workerId);\n if (result) {\n return result;\n }\n\n // Wait for a job\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this.removeJobWaiter(waiter);\n resolve(undefined);\n }, timeoutSeconds * 1000);\n\n const waiter: JobWaiter = {\n queues: new Set(queues),\n workerId,\n resolve: (result) => {\n clearTimeout(timer);\n resolve(result);\n },\n timer,\n };\n\n this.jobWaiters.add(waiter);\n });\n }\n\n public async completeJob(\n queue: string,\n jobId: string,\n result?: unknown,\n ): Promise<void> {\n const job = this.jobs.get(queue)?.get(jobId);\n if (!job) {\n this.log.warn(`Attempted to complete unknown job ${jobId}`);\n return;\n }\n\n const now = Date.now();\n const duration = now - (job.state.processedAt ?? now);\n\n // Remove from active\n this.active.get(queue)?.delete(jobId);\n\n // Update job state\n job.state.status = \"completed\";\n job.state.completedAt = now;\n job.state.result = result;\n job.state.lockedBy = undefined;\n job.state.lockedUntil = undefined;\n\n // Handle removeOnComplete\n const removeOnComplete = job.options.removeOnComplete;\n if (removeOnComplete === true) {\n // Remove immediately\n this.jobs.get(queue)?.delete(jobId);\n this.log.debug(`Job ${jobId} completed and removed`, { queue, result });\n } else {\n // Add to completed history (newest first)\n this.completed.get(queue)!.unshift(jobId);\n\n // If removeOnComplete is a number, trim the list (0 means keep none)\n if (typeof removeOnComplete === \"number\" && removeOnComplete >= 0) {\n await this.cleanJobs(queue, \"completed\", {\n maxCount: removeOnComplete,\n });\n }\n\n this.log.debug(`Job ${jobId} completed`, { queue, result });\n }\n\n // Emit completed event\n await this.emit({\n type: \"completed\",\n queue,\n jobId,\n timestamp: now,\n result,\n duration,\n });\n }\n\n public async failJob(\n queue: string,\n jobId: string,\n error: string,\n stackTrace?: string,\n ): Promise<void> {\n const job = this.jobs.get(queue)?.get(jobId);\n if (!job) {\n this.log.warn(`Attempted to fail unknown job ${jobId}`);\n return;\n }\n\n const now = Date.now();\n\n // Remove from active\n this.active.get(queue)?.delete(jobId);\n\n const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const hasMoreAttempts = job.state.attempts < maxAttempts;\n\n if (hasMoreAttempts) {\n // Calculate backoff delay for retry\n const backoffDelay = this.calculateBackoff(job);\n\n job.state.status = \"delayed\";\n job.state.availableAt = now + backoffDelay;\n job.state.error = error;\n job.state.stackTrace = stackTrace;\n job.state.lockedBy = undefined;\n job.state.lockedUntil = undefined;\n\n this.insertDelayed(queue, job);\n\n this.log.debug(`Job ${jobId} failed, will retry in ${backoffDelay}ms`, {\n queue,\n attempt: job.state.attempts,\n maxAttempts,\n error,\n });\n\n // Emit retrying event\n await this.emit({\n type: \"retrying\",\n queue,\n jobId,\n timestamp: now,\n error,\n attempt: job.state.attempts + 1,\n delay: backoffDelay,\n });\n } else {\n // No more retries, mark as permanently failed\n job.state.status = \"failed\";\n job.state.failedAt = now;\n job.state.error = error;\n job.state.stackTrace = stackTrace;\n job.state.lockedBy = undefined;\n job.state.lockedUntil = undefined;\n\n // Handle removeOnFail\n const removeOnFail = job.options.removeOnFail;\n if (removeOnFail === true) {\n // Remove immediately\n this.jobs.get(queue)?.delete(jobId);\n this.log.debug(\n `Job ${jobId} permanently failed and removed after ${job.state.attempts} attempts`,\n { queue, error },\n );\n } else {\n this.failed.get(queue)!.unshift(jobId);\n\n // If removeOnFail is a number, trim the list (0 means keep none)\n if (typeof removeOnFail === \"number\" && removeOnFail >= 0) {\n await this.cleanJobs(queue, \"failed\", { maxCount: removeOnFail });\n }\n\n this.log.debug(\n `Job ${jobId} permanently failed after ${job.state.attempts} attempts`,\n { queue, error },\n );\n }\n\n // Emit failed event\n await this.emit({\n type: \"failed\",\n queue,\n jobId,\n timestamp: now,\n error,\n stackTrace,\n attempts: job.state.attempts,\n });\n }\n }\n\n protected calculateBackoff(job: QueueJob): number {\n const backoff = job.options.backoff;\n const attempt = job.state.attempts;\n\n if (!backoff) {\n return DEFAULT_BACKOFF_DELAY;\n }\n\n const baseDelay = backoff.delay ?? DEFAULT_BACKOFF_DELAY;\n const maxDelay = backoff.maxDelay ?? DEFAULT_BACKOFF_MAX_DELAY;\n\n if (backoff.type === \"fixed\") {\n return baseDelay;\n }\n\n // Exponential backoff: delay * 2^(attempt-1)\n const exponentialDelay = baseDelay * 2 ** (attempt - 1);\n return Math.min(exponentialDelay, maxDelay);\n }\n\n public async renewJobLock(\n queue: string,\n jobId: string,\n workerId: string,\n ): Promise<boolean> {\n const job = this.jobs.get(queue)?.get(jobId);\n if (!job || job.state.lockedBy !== workerId) {\n return false;\n }\n\n job.state.lockedUntil =\n Date.now() + (job.options.lockDuration ?? DEFAULT_LOCK_DURATION);\n return true;\n }\n\n public async getJob(\n queue: string,\n jobId: string,\n ): Promise<QueueJob | undefined> {\n return this.jobs.get(queue)?.get(jobId);\n }\n\n public async getJobs(\n queue: string,\n status: QueueJobStatus,\n options?: QueueGetJobsOptions,\n ): Promise<QueueJob[]> {\n const limit = options?.limit ?? 100;\n const offset = options?.offset ?? 0;\n\n let jobIds: string[];\n switch (status) {\n case \"waiting\":\n jobIds = this.waiting.get(queue) ?? [];\n break;\n case \"delayed\":\n jobIds = this.delayed.get(queue) ?? [];\n break;\n case \"active\":\n jobIds = Array.from(this.active.get(queue) ?? []);\n break;\n case \"completed\":\n jobIds = this.completed.get(queue) ?? [];\n break;\n case \"failed\":\n jobIds = this.failed.get(queue) ?? [];\n break;\n default:\n jobIds = [];\n }\n\n const jobsMap = this.jobs.get(queue);\n if (!jobsMap) return [];\n\n return jobIds\n .slice(offset, offset + limit)\n .map((id) => jobsMap.get(id))\n .filter((job): job is QueueJob => job !== undefined);\n }\n\n public async getJobCounts(queue: string): Promise<QueueJobCounts> {\n return {\n waiting: this.waiting.get(queue)?.length ?? 0,\n delayed: this.delayed.get(queue)?.length ?? 0,\n active: this.active.get(queue)?.size ?? 0,\n completed: this.completed.get(queue)?.length ?? 0,\n failed: this.failed.get(queue)?.length ?? 0,\n };\n }\n\n public async promoteDelayedJobs(queue: string): Promise<number> {\n const delayedList = this.delayed.get(queue);\n if (!delayedList || delayedList.length === 0) return 0;\n\n const now = Date.now();\n let promoted = 0;\n\n while (delayedList.length > 0) {\n const jobId = delayedList[0];\n const job = this.jobs.get(queue)?.get(jobId);\n\n if (!job || (job.state.availableAt ?? 0) > now) {\n break; // No more jobs ready\n }\n\n // Remove from delayed\n delayedList.shift();\n\n // Move to waiting\n job.state.status = \"waiting\";\n this.insertWaiting(queue, job);\n promoted++;\n\n this.log.debug(`Promoted delayed job ${jobId}`, { queue });\n\n // Emit waiting event\n this.emit({\n type: \"waiting\",\n queue,\n jobId,\n timestamp: now,\n job,\n });\n }\n\n if (promoted > 0) {\n this.notifyJobWaiters(queue);\n }\n\n return promoted;\n }\n\n public async recoverStalledJobs(\n queue: string,\n stalledThresholdMs: number,\n ): Promise<string[]> {\n const activeSet = this.active.get(queue);\n if (!activeSet || activeSet.size === 0) return [];\n\n const now = Date.now();\n const stalledJobIds: string[] = [];\n\n for (const jobId of activeSet) {\n const job = this.jobs.get(queue)?.get(jobId);\n if (!job) continue;\n\n const lockExpired =\n (job.state.lockedUntil ?? 0) + stalledThresholdMs < now;\n if (lockExpired) {\n stalledJobIds.push(jobId);\n }\n }\n\n for (const jobId of stalledJobIds) {\n const job = this.jobs.get(queue)!.get(jobId)!;\n const workerId = job.state.lockedBy;\n activeSet.delete(jobId);\n\n const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const hasMoreAttempts = job.state.attempts < maxAttempts;\n\n // Emit stalled event\n await this.emit({\n type: \"stalled\",\n queue,\n jobId,\n timestamp: now,\n workerId,\n willRetry: hasMoreAttempts,\n });\n\n if (hasMoreAttempts) {\n // Return to waiting for retry\n job.state.status = \"waiting\";\n job.state.lockedBy = undefined;\n job.state.lockedUntil = undefined;\n job.state.error = \"Job stalled (worker timeout)\";\n this.insertWaiting(queue, job);\n\n this.log.warn(`Recovered stalled job ${jobId}, returning to waiting`, {\n queue,\n attempt: job.state.attempts,\n });\n\n // Emit waiting event\n await this.emit({\n type: \"waiting\",\n queue,\n jobId,\n timestamp: now,\n job,\n });\n } else {\n // No more retries\n job.state.status = \"failed\";\n job.state.failedAt = now;\n job.state.lockedBy = undefined;\n job.state.lockedUntil = undefined;\n job.state.error =\n \"Job stalled (worker timeout) - max attempts exceeded\";\n\n // Handle removeOnFail\n const removeOnFail = job.options.removeOnFail;\n if (removeOnFail === true) {\n this.jobs.get(queue)?.delete(jobId);\n } else {\n this.failed.get(queue)!.unshift(jobId);\n if (typeof removeOnFail === \"number\" && removeOnFail > 0) {\n await this.cleanJobs(queue, \"failed\", { maxCount: removeOnFail });\n }\n }\n\n this.log.warn(`Stalled job ${jobId} permanently failed`, {\n queue,\n attempts: job.state.attempts,\n });\n\n // Emit failed event\n await this.emit({\n type: \"failed\",\n queue,\n jobId,\n timestamp: now,\n error: job.state.error,\n attempts: job.state.attempts,\n });\n }\n }\n\n if (stalledJobIds.length > 0) {\n this.notifyJobWaiters(queue);\n }\n\n return stalledJobIds;\n }\n\n public async cleanJobs(\n queue: string,\n status: \"completed\" | \"failed\",\n options?: QueueCleanOptions,\n ): Promise<number> {\n const jobsList =\n status === \"completed\"\n ? this.completed.get(queue)\n : this.failed.get(queue);\n\n if (!jobsList || jobsList.length === 0) return 0;\n\n const jobsMap = this.jobs.get(queue);\n if (!jobsMap) return 0;\n\n const now = Date.now();\n const maxAge = options?.maxAge;\n const maxCount = options?.maxCount;\n\n let removed = 0;\n\n // Remove by age\n if (maxAge !== undefined) {\n const cutoff = now - maxAge;\n const toRemove: string[] = [];\n\n for (const jobId of jobsList) {\n const job = jobsMap.get(jobId);\n if (job) {\n const timestamp =\n status === \"completed\" ? job.state.completedAt : job.state.failedAt;\n if (timestamp && timestamp < cutoff) {\n toRemove.push(jobId);\n }\n }\n }\n\n for (const jobId of toRemove) {\n const idx = jobsList.indexOf(jobId);\n if (idx !== -1) {\n jobsList.splice(idx, 1);\n jobsMap.delete(jobId);\n removed++;\n }\n }\n }\n\n // Remove by count (keep only maxCount newest)\n if (maxCount !== undefined && jobsList.length > maxCount) {\n const toRemove = jobsList.splice(maxCount);\n for (const jobId of toRemove) {\n jobsMap.delete(jobId);\n removed++;\n }\n }\n\n return removed;\n }\n\n public async removeJob(queue: string, jobId: string): Promise<void> {\n const job = this.jobs.get(queue)?.get(jobId);\n if (!job) return;\n\n const previousStatus = job.state.status;\n\n // Remove from appropriate list based on status\n switch (job.state.status) {\n case \"waiting\": {\n const list = this.waiting.get(queue);\n const idx = list?.indexOf(jobId) ?? -1;\n if (idx !== -1) list!.splice(idx, 1);\n break;\n }\n case \"delayed\": {\n const list = this.delayed.get(queue);\n const idx = list?.indexOf(jobId) ?? -1;\n if (idx !== -1) list!.splice(idx, 1);\n break;\n }\n case \"active\":\n this.active.get(queue)?.delete(jobId);\n break;\n case \"completed\": {\n const list = this.completed.get(queue);\n const idx = list?.indexOf(jobId) ?? -1;\n if (idx !== -1) list!.splice(idx, 1);\n break;\n }\n case \"failed\": {\n const list = this.failed.get(queue);\n const idx = list?.indexOf(jobId) ?? -1;\n if (idx !== -1) list!.splice(idx, 1);\n break;\n }\n }\n\n this.jobs.get(queue)?.delete(jobId);\n\n // Emit removed event\n await this.emit({\n type: \"removed\",\n queue,\n jobId,\n timestamp: Date.now(),\n previousStatus,\n });\n }\n\n public cancelWaiters(): void {\n // Cancel all job waiters\n for (const waiter of this.jobWaiters) {\n clearTimeout(waiter.timer);\n waiter.resolve(undefined);\n }\n this.jobWaiters.clear();\n\n // Cancel all message waiters\n for (const waiter of this.messageWaiters) {\n clearTimeout(waiter.timer);\n waiter.resolve(undefined);\n }\n this.messageWaiters.clear();\n }\n}\n","import {\n createPrimitive,\n KIND,\n Primitive,\n type Service,\n type Static,\n type TSchema,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type {\n QueueAddJobOptions,\n QueueJobBackoff,\n} from \"../interfaces/QueueJob.ts\";\nimport { MemoryQueueProvider } from \"../providers/MemoryQueueProvider.ts\";\nimport { QueueProvider } from \"../providers/QueueProvider.ts\";\n\n/**\n * Creates a queue primitive for asynchronous message processing with background workers.\n *\n * The $queue primitive enables powerful asynchronous communication patterns in your application.\n * It provides type-safe message queuing with automatic worker processing, making it perfect for\n * decoupling components and handling background tasks efficiently.\n *\n * **Background Processing**\n * - Automatic worker threads for non-blocking message processing\n * - Built-in retry mechanisms and error handling\n * - Dead letter queues for failed message handling\n * - Graceful shutdown and worker lifecycle management\n *\n * **Type Safety**\n * - Full TypeScript support with schema validation using TypeBox\n * - Type-safe message payloads with automatic inference\n * - Runtime validation of all queued messages\n * - Compile-time errors for invalid message structures\n *\n * **Storage Flexibility**\n * - Memory provider for development and testing\n * - Redis provider for production scalability and persistence\n * - Custom provider support for specialized backends\n * - Automatic failover and connection pooling\n *\n * **Performance & Scalability**\n * - Batch processing support for high-throughput scenarios\n * - Horizontal scaling with distributed queue backends\n * - Configurable concurrency and worker pools\n * - Efficient serialization and message routing\n *\n * **Reliability**\n * - Message persistence across application restarts\n * - Automatic retry with exponential backoff\n * - Dead letter handling for permanently failed messages\n * - Comprehensive logging and monitoring integration\n *\n * @example Basic notification queue\n * ```typescript\n * const emailQueue = $queue({\n * name: \"email-notifications\",\n * schema: t.object({\n * to: t.text(),\n * subject: t.text(),\n * body: t.text(),\n * priority: t.optional(t.enum([\"high\", \"normal\"]))\n * }),\n * handler: async (message) => {\n * await emailService.send(message.payload);\n * console.log(`Email sent to ${message.payload.to}`);\n * }\n * });\n *\n * // Push messages for background processing\n * await emailQueue.push({\n * to: \"user@example.com\",\n * subject: \"Welcome!\",\n * body: \"Welcome to our platform\",\n * priority: \"high\"\n * });\n * ```\n *\n * @example Batch processing with Redis\n * ```typescript\n * const imageQueue = $queue({\n * name: \"image-processing\",\n * provider: RedisQueueProvider,\n * schema: t.object({\n * imageId: t.text(),\n * operations: t.array(t.enum([\"resize\", \"compress\", \"thumbnail\"]))\n * }),\n * handler: async (message) => {\n * for (const op of message.payload.operations) {\n * await processImage(message.payload.imageId, op);\n * }\n * }\n * });\n *\n * // Batch processing multiple images\n * await imageQueue.push(\n * { imageId: \"img1\", operations: [\"resize\", \"thumbnail\"] },\n * { imageId: \"img2\", operations: [\"compress\"] },\n * { imageId: \"img3\", operations: [\"resize\", \"compress\", \"thumbnail\"] }\n * );\n * ```\n *\n * @example Development with memory provider\n * ```typescript\n * const taskQueue = $queue({\n * name: \"dev-tasks\",\n * provider: \"memory\",\n * schema: t.object({\n * taskType: t.enum([\"cleanup\", \"backup\", \"report\"]),\n * data: t.record(t.text(), t.any())\n * }),\n * handler: async (message) => {\n * switch (message.payload.taskType) {\n * case \"cleanup\":\n * await performCleanup(message.payload.data);\n * break;\n * case \"backup\":\n * await createBackup(message.payload.data);\n * break;\n * case \"report\":\n * await generateReport(message.payload.data);\n * break;\n * }\n * }\n * });\n * ```\n */\nexport const $queue = <T extends TSchema>(\n options: QueuePrimitiveOptions<T>,\n): QueuePrimitive<T> => {\n return createPrimitive(QueuePrimitive<T>, options);\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface QueuePrimitiveOptions<T extends TSchema> {\n /**\n * Unique name for the queue.\n *\n * This name is used for:\n * - Queue identification across the system\n * - Storage backend key generation\n * - Logging and monitoring\n * - Worker assignment and routing\n *\n * If not provided, defaults to the property key where the queue is declared.\n *\n * @example \"email-notifications\"\n * @example \"image-processing\"\n * @example \"order-fulfillment\"\n */\n name?: string;\n\n /**\n * Human-readable description of the queue's purpose.\n *\n * Used for:\n * - Documentation generation\n * - Monitoring dashboards\n * - Development team communication\n * - Queue management interfaces\n *\n * @example \"Process user registration emails and welcome sequences\"\n * @example \"Handle image uploads, resizing, and thumbnail generation\"\n * @example \"Manage order processing, payment, and shipping workflows\"\n */\n description?: string;\n\n /**\n * Queue storage provider configuration.\n *\n * Options:\n * - **\"memory\"**: In-memory queue (default for development, lost on restart)\n * - **Service<QueueProvider>**: Custom provider class (e.g., RedisQueueProvider)\n * - **undefined**: Uses the default queue provider from dependency injection\n *\n * **Provider Selection Guidelines**:\n * - Development: Use \"memory\" for fast, simple testing\n * - Production: Use Redis or database-backed providers for persistence\n * - High-throughput: Use specialized providers with connection pooling\n * - Distributed systems: Use Redis or message brokers for scalability\n *\n * @default Uses injected QueueProvider\n * @example \"memory\"\n * @example RedisQueueProvider\n * @example DatabaseQueueProvider\n */\n provider?: \"memory\" | Service<QueueProvider>;\n\n /**\n * TypeBox schema defining the structure of messages in this queue.\n *\n * This schema:\n * - Validates all messages pushed to the queue\n * - Provides full TypeScript type inference\n * - Ensures type safety between producers and consumers\n * - Enables automatic serialization/deserialization\n *\n * **Schema Design Best Practices**:\n * - Keep schemas simple and focused on the specific task\n * - Use optional fields for data that might not always be available\n * - Include version fields for schema evolution\n * - Use union types for different message types in the same queue\n *\n * @example\n * ```ts\n * t.object({\n * userId: t.text(),\n * action: t.enum([\"create\", \"update\"]),\n * data: t.record(t.text(), t.any()),\n * timestamp: t.optional(t.number())\n * })\n * ```\n */\n schema: T;\n\n /**\n * Message handler function that processes queue messages.\n *\n * This function:\n * - Runs in background worker threads for non-blocking processing\n * - Receives type-safe message payloads based on the schema\n * - Should be idempotent to handle potential retries\n * - Can throw errors to trigger retry mechanisms\n * - Has access to the full Alepha dependency injection container\n *\n * **Handler Best Practices**:\n * - Keep handlers focused on a single responsibility\n * - Use proper error handling and logging\n * - Make operations idempotent when possible\n * - Validate critical business logic within handlers\n * - Consider using transactions for data consistency\n *\n * @param message - The queue message with validated payload\n * @returns Promise that resolves when processing is complete\n *\n * @example\n * ```ts\n * handler: async (message) => {\n * const { userId, email, template } = message.payload;\n *\n * try {\n * await this.emailService.send({\n * to: email,\n * template,\n * data: { userId }\n * });\n *\n * await this.userService.markEmailSent(userId, template);\n * } catch (error) {\n * // Log error and let the queue system handle retries\n * this.logger.error(`Failed to send email to ${email}`, error);\n * throw error;\n * }\n * }\n * ```\n */\n handler?: (message: QueueMessage<T>) => Promise<void>;\n\n // ===========================================\n // Job Options (for crash recovery and retries)\n // ===========================================\n\n /**\n * Maximum number of processing attempts before the job is marked as failed.\n * Includes the initial attempt.\n *\n * Set this to enable automatic retries on failure.\n *\n * @default 1 (no retries)\n * @example 3 // Allows 2 retries after initial failure\n */\n maxAttempts?: number;\n\n /**\n * Backoff configuration for retries.\n * Controls the delay between retry attempts.\n *\n * @example\n * ```ts\n * backoff: {\n * type: \"exponential\",\n * delay: 1000, // Initial delay: 1 second\n * maxDelay: 60000 // Maximum delay: 1 minute\n * }\n * ```\n */\n backoff?: QueueJobBackoff;\n\n /**\n * Maximum time in milliseconds a job can be processed before it's considered stalled.\n * If the worker doesn't complete or extend the lock within this time, the job\n * can be picked up by another worker.\n *\n * Increase this for long-running jobs.\n *\n * @default 30000 (30 seconds)\n */\n lockDuration?: number;\n\n /**\n * Automatically remove jobs when they complete successfully.\n * - `true`: Remove immediately after completion\n * - `false`: Keep in completed list (default)\n * - `number`: Keep this many most recent completed jobs, remove older ones\n *\n * @default false\n * @example\n * ```ts\n * // Remove immediately after completion\n * removeOnComplete: true\n *\n * // Keep only the last 100 completed jobs\n * removeOnComplete: 100\n * ```\n */\n removeOnComplete?: boolean | number;\n\n /**\n * Automatically remove jobs when they fail permanently (after all retries exhausted).\n * - `true`: Remove immediately after failure\n * - `false`: Keep in failed list (default)\n * - `number`: Keep this many most recent failed jobs, remove older ones\n *\n * @default false\n * @example\n * ```ts\n * // Remove immediately after failure\n * removeOnFail: true\n *\n * // Keep only the last 50 failed jobs for debugging\n * removeOnFail: 50\n * ```\n */\n removeOnFail?: boolean | number;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class QueuePrimitive<T extends TSchema> extends Primitive<\n QueuePrimitiveOptions<T>\n> {\n protected readonly log = $logger();\n public readonly provider = this.$provider();\n\n /**\n * Push one or more payloads to the queue for background processing.\n *\n * Jobs will be processed with crash recovery, retries (if configured),\n * and proper lifecycle management.\n *\n * @param payloads - One or more payloads to queue\n */\n public async push(...payloads: Array<Static<T>>): Promise<void>;\n /**\n * Push a payload to the queue with specific options.\n *\n * @param payload - The payload to queue\n * @param options - Job options (priority, delay)\n */\n public async push(\n payload: Static<T>,\n options: QueueAddJobOptions,\n ): Promise<void>;\n\n public async push(\n payloadOrFirst: Static<T>,\n optionsOrSecond?: QueueAddJobOptions | Static<T>,\n ...rest: Array<Static<T>>\n ): Promise<void> {\n // Check if second argument is options object\n const isOptions =\n optionsOrSecond != null &&\n typeof optionsOrSecond === \"object\" &&\n (\"priority\" in optionsOrSecond || \"delay\" in optionsOrSecond);\n\n if (isOptions) {\n // Single payload with options\n const payload = this.alepha.codec.decode(\n this.options.schema,\n payloadOrFirst,\n );\n await this.provider.addJob(this.name, payload, {\n ...this.getDefaultJobOptions(),\n priority: (optionsOrSecond as QueueAddJobOptions).priority,\n delay: (optionsOrSecond as QueueAddJobOptions).delay,\n });\n this.log.debug(`Pushed job to queue ${this.name}`, {\n payload,\n options: optionsOrSecond,\n });\n } else {\n // Multiple payloads without per-job options\n const payloads =\n optionsOrSecond != null\n ? [payloadOrFirst, optionsOrSecond as Static<T>, ...rest]\n : [payloadOrFirst, ...rest];\n\n await Promise.all(\n payloads.map((p) => {\n const payload = this.alepha.codec.decode(this.options.schema, p);\n return this.provider.addJob(\n this.name,\n payload,\n this.getDefaultJobOptions(),\n );\n }),\n );\n\n this.log.debug(\n `Pushed ${payloads.length} job(s) to queue ${this.name}`,\n payloads,\n );\n }\n }\n\n /**\n * Get default job options from primitive configuration.\n */\n protected getDefaultJobOptions() {\n return {\n maxAttempts: this.options.maxAttempts,\n backoff: this.options.backoff,\n lockDuration: this.options.lockDuration,\n removeOnComplete: this.options.removeOnComplete,\n removeOnFail: this.options.removeOnFail,\n };\n }\n\n public get name() {\n return this.options.name || this.config.propertyKey;\n }\n\n protected $provider() {\n if (!this.options.provider) {\n return this.alepha.inject(QueueProvider);\n }\n if (this.options.provider === \"memory\") {\n return this.alepha.inject(MemoryQueueProvider);\n }\n return this.alepha.inject(this.options.provider);\n }\n}\n\n$queue[KIND] = QueuePrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface QueueMessageSchema {\n payload: TSchema;\n}\n\nexport interface QueueMessage<T extends TSchema> {\n payload: Static<T>;\n}\n","import {\n $env,\n $hook,\n $inject,\n Alepha,\n type Static,\n type TSchema,\n t,\n} from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport type { QueueAcquiredJob } from \"../interfaces/QueueJob.ts\";\nimport { $consumer } from \"../primitives/$consumer.ts\";\nimport {\n $queue,\n type QueueMessage,\n type QueuePrimitive,\n} from \"../primitives/$queue.ts\";\nimport { QueueProvider } from \"./QueueProvider.ts\";\n\nconst envSchema = t.object({\n /**\n * The timeout in seconds for blocking job acquisition.\n * Workers will check for shutdown after each timeout period.\n */\n QUEUE_WORKER_BLOCKING_TIMEOUT: t.integer({\n default: 5,\n }),\n /**\n * The number of workers to run concurrently. Defaults to 1.\n * Useful only if you are doing a lot of I/O.\n */\n QUEUE_WORKER_CONCURRENCY: t.integer({\n default: 1,\n }),\n /**\n * Interval in milliseconds for renewing job locks during processing.\n * Should be less than the job's lock duration.\n */\n QUEUE_WORKER_LOCK_RENEWAL_INTERVAL: t.integer({\n default: 10000, // 10 seconds\n }),\n /**\n * Interval in milliseconds for the scheduler to check delayed jobs and stalled jobs.\n */\n QUEUE_SCHEDULER_INTERVAL: t.integer({\n default: 5000, // 5 seconds\n }),\n /**\n * Threshold in milliseconds after lock expiration to consider a job stalled.\n */\n QUEUE_STALLED_THRESHOLD: t.integer({\n default: 5000, // 5 seconds grace period after lock expires\n }),\n});\n\ndeclare module \"alepha\" {\n interface Env extends Partial<Static<typeof envSchema>> {}\n}\n\nexport class WorkerProvider {\n protected readonly log = $logger();\n protected readonly env = $env(envSchema);\n protected readonly alepha = $inject(Alepha);\n protected readonly queueProvider = $inject(QueueProvider);\n protected readonly dateTime = $inject(DateTimeProvider);\n\n protected workerPromises: Array<Promise<void>> = [];\n protected workersRunning = 0;\n protected shouldStop = false;\n protected consumers: Array<Consumer> = [];\n protected consumersByProvider: Map<QueueProvider, Consumer[]> = new Map();\n protected schedulerPromise: Promise<void> | undefined;\n protected schedulerRunning = false;\n protected abortController: AbortController | undefined;\n protected workerId: string = `worker_${process.pid}_${Date.now()}`;\n\n public get isRunning(): boolean {\n return this.workersRunning > 0;\n }\n\n protected readonly start = $hook({\n on: \"start\",\n priority: \"last\",\n handler: () => {\n for (const queue of this.alepha.primitives($queue)) {\n const handler = queue.options.handler;\n if (handler) {\n this.consumers.push({\n handler,\n queue,\n });\n }\n }\n\n for (const consumer of this.alepha.primitives($consumer)) {\n this.consumers.push(consumer.options);\n }\n\n // Group consumers by their provider for efficient blocking\n for (const consumer of this.consumers) {\n const provider = consumer.queue.provider;\n const list = this.consumersByProvider.get(provider) ?? [];\n list.push(consumer);\n this.consumersByProvider.set(provider, list);\n }\n\n if (this.consumers.length > 0) {\n this.startWorkers();\n this.startScheduler();\n this.log.debug(\n `Watching for ${this.consumers.length} queue${this.consumers.length > 1 ? \"s\" : \"\"} with ${this.env.QUEUE_WORKER_CONCURRENCY} worker${\n this.env.QUEUE_WORKER_CONCURRENCY > 1 ? \"s\" : \"\"\n }.`,\n );\n }\n },\n });\n\n // -------------------------------------------------------------------------------------------------------------------\n\n // Engine part - this is the part that will run the workers and process the jobs\n\n /**\n * Start the workers.\n * Each worker acquires jobs and processes them with proper lifecycle management.\n */\n protected startWorkers(): void {\n const workerToStart =\n this.env.QUEUE_WORKER_CONCURRENCY - this.workersRunning;\n\n for (let i = 0; i < workerToStart; i++) {\n this.workersRunning += 1;\n const workerIndex = i;\n const localWorkerId = `${this.workerId}_${workerIndex}`;\n this.log.debug(`Starting worker n-${workerIndex}`);\n\n const workerLoop = async () => {\n while (!this.shouldStop) {\n this.log.trace(`Worker n-${workerIndex} is waiting for jobs`);\n const acquired = await this.acquireNextJob(localWorkerId);\n if (acquired) {\n await this.processJob(acquired, localWorkerId);\n }\n // If no job (timeout), loop continues and checks shouldStop\n }\n this.log.info(`Worker n-${workerIndex} has stopped`);\n };\n\n this.workerPromises.push(\n workerLoop().catch((e) => {\n this.log.error(`Worker n-${workerIndex} has crashed`, e);\n this.workersRunning -= 1;\n }),\n );\n }\n }\n\n /**\n * Start the scheduler for delayed job promotion and stalled job recovery.\n */\n protected startScheduler(): void {\n if (this.schedulerRunning) return;\n this.schedulerRunning = true;\n this.abortController = new AbortController();\n this.log.debug(\"Starting scheduler\");\n\n const schedulerLoop = async () => {\n while (!this.shouldStop) {\n try {\n await this.runSchedulerCycle();\n } catch (e) {\n this.log.error(\"Scheduler cycle failed\", e);\n }\n\n // Wait for next interval (interruptible via AbortController)\n await this.dateTime.wait(this.env.QUEUE_SCHEDULER_INTERVAL, {\n signal: this.abortController?.signal,\n });\n }\n this.log.debug(\"Scheduler stopped\");\n };\n\n this.schedulerPromise = schedulerLoop();\n }\n\n /**\n * Run one cycle of the scheduler.\n * Promotes delayed jobs and recovers stalled jobs.\n */\n protected async runSchedulerCycle(): Promise<void> {\n for (const [provider, consumers] of this.consumersByProvider) {\n const queues = new Set(consumers.map((c) => c.queue.name));\n\n for (const queue of queues) {\n // Promote delayed jobs\n const promoted = await provider.promoteDelayedJobs(queue);\n if (promoted > 0) {\n this.log.debug(`Promoted ${promoted} delayed jobs in queue ${queue}`);\n }\n\n // Recover stalled jobs\n const recovered = await provider.recoverStalledJobs(\n queue,\n this.env.QUEUE_STALLED_THRESHOLD,\n );\n if (recovered.length > 0) {\n this.log.warn(\n `Recovered ${recovered.length} stalled jobs in queue ${queue}`,\n );\n }\n }\n }\n }\n\n protected readonly stop = $hook({\n on: \"stop\",\n handler: async () => {\n if (this.consumers.length > 0) {\n await this.stopWorkers();\n }\n },\n });\n\n /**\n * Acquire the next available job from any provider.\n */\n protected async acquireNextJob(\n localWorkerId: string,\n ): Promise<AcquiredJobWithConsumer | undefined> {\n for (const [provider, consumers] of this.consumersByProvider) {\n const queueNames = consumers.map((c) => c.queue.name);\n const acquired = await provider.acquireJob(\n queueNames,\n localWorkerId,\n this.env.QUEUE_WORKER_BLOCKING_TIMEOUT,\n );\n\n if (acquired) {\n const consumer = consumers.find((c) => c.queue.name === acquired.queue);\n if (consumer) {\n return { acquired, consumer, provider };\n }\n }\n }\n\n return undefined;\n }\n\n /**\n * Process a job with proper lifecycle management.\n * - Starts a lock renewal interval\n * - Calls the handler\n * - Marks job as completed or failed\n */\n protected async processJob(\n { acquired, consumer, provider }: AcquiredJobWithConsumer,\n localWorkerId: string,\n ): Promise<void> {\n const { queue, job } = acquired;\n\n // Start lock renewal heartbeat\n const lockRenewalInterval = this.dateTime.createInterval(\n async () => {\n try {\n const renewed = await provider.renewJobLock(\n queue,\n job.id,\n localWorkerId,\n );\n if (!renewed) {\n this.log.warn(\n `Failed to renew lock for job ${job.id}, lock may have been stolen`,\n );\n }\n } catch (e) {\n this.log.error(`Error renewing lock for job ${job.id}`, e);\n }\n },\n this.env.QUEUE_WORKER_LOCK_RENEWAL_INTERVAL,\n true, // start immediately\n );\n\n try {\n // Decode payload and run handler\n const payload = this.alepha.codec.decode(\n consumer.queue.options.schema,\n job.payload,\n );\n\n await this.alepha.context.run(() => consumer.handler({ payload }));\n\n // Mark as completed\n await provider.completeJob(queue, job.id);\n this.log.debug(`Job ${job.id} completed successfully`, { queue });\n } catch (e) {\n // Mark as failed (provider handles retry logic)\n const error = e instanceof Error ? e.message : String(e);\n const stackTrace = e instanceof Error ? e.stack : undefined;\n await provider.failJob(queue, job.id, error, stackTrace);\n this.log.error(`Job ${job.id} failed`, e);\n } finally {\n this.dateTime.clearInterval(lockRenewalInterval);\n }\n }\n\n /**\n * Stop the workers and scheduler.\n */\n protected async stopWorkers(): Promise<void> {\n this.shouldStop = true;\n this.workersRunning = 0;\n this.schedulerRunning = false;\n\n // Abort the scheduler's wait immediately\n this.abortController?.abort();\n\n // Cancel all pending acquireJob waiters to unblock workers immediately\n for (const provider of this.consumersByProvider.keys()) {\n provider.cancelWaiters();\n }\n\n this.log.trace(\"Stopping workers...\");\n this.log.trace(\"Waiting for workers to finish...\");\n\n const promises: Promise<void>[] = [...this.workerPromises];\n if (this.schedulerPromise) {\n promises.push(this.schedulerPromise);\n }\n\n await Promise.all(promises);\n }\n}\n\nexport interface Consumer<T extends TSchema = TSchema> {\n queue: QueuePrimitive<T>;\n handler: (message: QueueMessage<T>) => Promise<void>;\n}\n\nexport interface AcquiredJobWithConsumer {\n acquired: QueueAcquiredJob;\n consumer: Consumer;\n provider: QueueProvider;\n}\n","import { $module, type Alepha } from \"alepha\";\nimport { $consumer } from \"./primitives/$consumer.ts\";\nimport { $queue } from \"./primitives/$queue.ts\";\nimport { MemoryQueueProvider } from \"./providers/MemoryQueueProvider.ts\";\nimport { QueueProvider } from \"./providers/QueueProvider.ts\";\nimport { WorkerProvider } from \"./providers/WorkerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./interfaces/QueueJob.ts\";\nexport * from \"./primitives/$consumer.ts\";\nexport * from \"./primitives/$queue.ts\";\nexport * from \"./providers/MemoryQueueProvider.ts\";\nexport * from \"./providers/QueueProvider.ts\";\nexport * from \"./providers/WorkerProvider.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Provides asynchronous message queuing and processing capabilities through declarative queue primitives.\n *\n * The queue module enables reliable background job processing and message passing using the `$queue` primitive\n * on class properties. It supports schema validation, automatic retries, and multiple queue backends for\n * building scalable, decoupled applications with robust error handling.\n *\n * @see {@link $queue}\n * @see {@link $consumer}\n * @module alepha.queue\n */\nexport const AlephaQueue = $module({\n name: \"alepha.queue\",\n primitives: [$queue, $consumer],\n services: [QueueProvider, MemoryQueueProvider, WorkerProvider],\n register: (alepha: Alepha) =>\n alepha\n .with({\n optional: true,\n provide: QueueProvider,\n use: MemoryQueueProvider,\n })\n .with(WorkerProvider),\n});\n"],"mappings":"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"}
|
|
@@ -36,8 +36,8 @@ declare class RedisQueueProvider extends QueueProvider {
|
|
|
36
36
|
protected acquireJobSha: string | undefined;
|
|
37
37
|
protected completeJobSha: string | undefined;
|
|
38
38
|
protected failJobSha: string | undefined;
|
|
39
|
-
protected readonly start: alepha1.
|
|
40
|
-
protected readonly stop: alepha1.
|
|
39
|
+
protected readonly start: alepha1.HookPrimitive<"start">;
|
|
40
|
+
protected readonly stop: alepha1.HookPrimitive<"stop">;
|
|
41
41
|
protected key(type: string, queue: string, id?: string): string;
|
|
42
42
|
protected messageKey(queue: string): string;
|
|
43
43
|
protected notifyKey(queue: string): string;
|
package/dist/redis/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as alepha1 from "alepha";
|
|
2
2
|
import { Alepha, Static } from "alepha";
|
|
3
3
|
import { RedisClientType, SetOptions, createClient } from "@redis/client";
|
|
4
4
|
import * as alepha_logger0 from "alepha/logger";
|
|
5
5
|
|
|
6
6
|
//#region src/redis/providers/RedisProvider.d.ts
|
|
7
|
-
declare const envSchema:
|
|
8
|
-
REDIS_PORT:
|
|
9
|
-
REDIS_HOST:
|
|
10
|
-
REDIS_PASSWORD:
|
|
7
|
+
declare const envSchema: alepha1.TObject<{
|
|
8
|
+
REDIS_PORT: alepha1.TInteger;
|
|
9
|
+
REDIS_HOST: alepha1.TString;
|
|
10
|
+
REDIS_PASSWORD: alepha1.TOptional<alepha1.TString>;
|
|
11
11
|
}>;
|
|
12
12
|
declare module "alepha" {
|
|
13
13
|
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
@@ -30,8 +30,8 @@ declare class RedisProvider {
|
|
|
30
30
|
};
|
|
31
31
|
protected readonly client: RedisClient;
|
|
32
32
|
get publisher(): RedisClient;
|
|
33
|
-
protected readonly start:
|
|
34
|
-
protected readonly stop:
|
|
33
|
+
protected readonly start: alepha1.HookPrimitive<"start">;
|
|
34
|
+
protected readonly stop: alepha1.HookPrimitive<"stop">;
|
|
35
35
|
/**
|
|
36
36
|
* Connect to the Redis server.
|
|
37
37
|
*/
|
|
@@ -59,8 +59,8 @@ declare class RedisSubscriberProvider {
|
|
|
59
59
|
protected readonly redisProvider: RedisProvider;
|
|
60
60
|
protected readonly client: RedisClient;
|
|
61
61
|
get subscriber(): RedisClient;
|
|
62
|
-
protected readonly start:
|
|
63
|
-
protected readonly stop:
|
|
62
|
+
protected readonly start: alepha1.HookPrimitive<"start">;
|
|
63
|
+
protected readonly stop: alepha1.HookPrimitive<"stop">;
|
|
64
64
|
connect(): Promise<void>;
|
|
65
65
|
close(): Promise<void>;
|
|
66
66
|
/**
|
|
@@ -76,7 +76,7 @@ declare class RedisSubscriberProvider {
|
|
|
76
76
|
* @see {@link RedisProvider}
|
|
77
77
|
* @module alepha.redis
|
|
78
78
|
*/
|
|
79
|
-
declare const AlephaRedis:
|
|
79
|
+
declare const AlephaRedis: alepha1.Service<alepha1.Module>;
|
|
80
80
|
//#endregion
|
|
81
81
|
export { AlephaRedis, RedisClient, RedisClientOptions, RedisProvider, RedisSetOptions, RedisSubscriberProvider };
|
|
82
82
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/retry/index.d.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import * as alepha0 from "alepha";
|
|
2
|
-
import { AlephaError,
|
|
2
|
+
import { AlephaError, KIND, Primitive, PrimitiveArgs } from "alepha";
|
|
3
3
|
import { DateTimeProvider, DurationLike } from "alepha/datetime";
|
|
4
4
|
import * as alepha_logger0 from "alepha/logger";
|
|
5
5
|
|
|
6
|
+
//#region src/retry/errors/RetryCancelError.d.ts
|
|
7
|
+
declare class RetryCancelError extends AlephaError {
|
|
8
|
+
constructor();
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/retry/errors/RetryTimeoutError.d.ts
|
|
12
|
+
declare class RetryTimeoutError extends AlephaError {
|
|
13
|
+
constructor(duration: number);
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
6
16
|
//#region src/retry/providers/RetryProvider.d.ts
|
|
7
17
|
interface RetryOptions<T extends (...args: any[]) => any> {
|
|
8
18
|
/**
|
|
@@ -90,16 +100,16 @@ declare class RetryProvider {
|
|
|
90
100
|
protected calculateBackoff(attempt: number, options?: number | RetryBackoffOptions): number;
|
|
91
101
|
}
|
|
92
102
|
//#endregion
|
|
93
|
-
//#region src/retry/
|
|
103
|
+
//#region src/retry/primitives/$retry.d.ts
|
|
94
104
|
/**
|
|
95
105
|
* Creates a function that automatically retries a handler upon failure,
|
|
96
106
|
* with support for exponential backoff, max duration, and cancellation.
|
|
97
107
|
*/
|
|
98
108
|
declare const $retry: {
|
|
99
|
-
<T extends (...args: any[]) => any>(options:
|
|
100
|
-
[KIND]: typeof
|
|
109
|
+
<T extends (...args: any[]) => any>(options: RetryPrimitiveOptions<T>): RetryPrimitiveFn<T>;
|
|
110
|
+
[KIND]: typeof RetryPrimitive;
|
|
101
111
|
};
|
|
102
|
-
interface
|
|
112
|
+
interface RetryPrimitiveOptions<T extends (...args: any[]) => any> {
|
|
103
113
|
/**
|
|
104
114
|
* The function to retry.
|
|
105
115
|
*/
|
|
@@ -139,26 +149,16 @@ interface RetryDescriptorOptions<T extends (...args: any[]) => any> {
|
|
|
139
149
|
*/
|
|
140
150
|
signal?: AbortSignal;
|
|
141
151
|
}
|
|
142
|
-
declare class
|
|
152
|
+
declare class RetryPrimitive<T extends (...args: any[]) => any> extends Primitive<RetryPrimitiveOptions<T>> {
|
|
143
153
|
protected readonly retryProvider: RetryProvider;
|
|
144
154
|
protected appAbortController?: AbortController;
|
|
145
|
-
constructor(args:
|
|
155
|
+
constructor(args: PrimitiveArgs<RetryPrimitiveOptions<T>>);
|
|
146
156
|
run(...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
147
157
|
}
|
|
148
|
-
interface
|
|
158
|
+
interface RetryPrimitiveFn<T extends (...args: any[]) => any> extends RetryPrimitive<T> {
|
|
149
159
|
(...args: Parameters<T>): Promise<ReturnType<T>>;
|
|
150
160
|
}
|
|
151
161
|
//#endregion
|
|
152
|
-
//#region src/retry/errors/RetryCancelError.d.ts
|
|
153
|
-
declare class RetryCancelError extends AlephaError {
|
|
154
|
-
constructor();
|
|
155
|
-
}
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region src/retry/errors/RetryTimeoutError.d.ts
|
|
158
|
-
declare class RetryTimeoutError extends AlephaError {
|
|
159
|
-
constructor(duration: number);
|
|
160
|
-
}
|
|
161
|
-
//#endregion
|
|
162
162
|
//#region src/retry/index.d.ts
|
|
163
163
|
/**
|
|
164
164
|
* Retry mechanism provider for Alepha applications.
|
|
@@ -168,5 +168,5 @@ declare class RetryTimeoutError extends AlephaError {
|
|
|
168
168
|
*/
|
|
169
169
|
declare const AlephaRetry: alepha0.Service<alepha0.Module>;
|
|
170
170
|
//#endregion
|
|
171
|
-
export { $retry, AlephaRetry, RetryBackoffOptions, RetryCancelError,
|
|
171
|
+
export { $retry, AlephaRetry, RetryBackoffOptions, RetryCancelError, RetryOptions, RetryPrimitive, RetryPrimitiveFn, RetryPrimitiveOptions, RetryProvider, RetryTimeoutError };
|
|
172
172
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/retry/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $inject, $module, AlephaError,
|
|
1
|
+
import { $inject, $module, AlephaError, KIND, Primitive, createPrimitive } from "alepha";
|
|
2
2
|
import { DateTimeProvider } from "alepha/datetime";
|
|
3
3
|
import { $logger } from "alepha/logger";
|
|
4
4
|
|
|
@@ -93,17 +93,17 @@ var RetryProvider = class {
|
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
//#endregion
|
|
96
|
-
//#region src/retry/
|
|
96
|
+
//#region src/retry/primitives/$retry.ts
|
|
97
97
|
/**
|
|
98
98
|
* Creates a function that automatically retries a handler upon failure,
|
|
99
99
|
* with support for exponential backoff, max duration, and cancellation.
|
|
100
100
|
*/
|
|
101
101
|
const $retry = (options) => {
|
|
102
|
-
const instance =
|
|
102
|
+
const instance = createPrimitive(RetryPrimitive, options);
|
|
103
103
|
const fn = (...args) => instance.run(...args);
|
|
104
104
|
return Object.setPrototypeOf(fn, instance);
|
|
105
105
|
};
|
|
106
|
-
var
|
|
106
|
+
var RetryPrimitive = class extends Primitive {
|
|
107
107
|
retryProvider = $inject(RetryProvider);
|
|
108
108
|
appAbortController;
|
|
109
109
|
constructor(args) {
|
|
@@ -120,7 +120,7 @@ var RetryDescriptor = class extends Descriptor {
|
|
|
120
120
|
}, ...args);
|
|
121
121
|
}
|
|
122
122
|
};
|
|
123
|
-
$retry[KIND] =
|
|
123
|
+
$retry[KIND] = RetryPrimitive;
|
|
124
124
|
|
|
125
125
|
//#endregion
|
|
126
126
|
//#region src/retry/index.ts
|
|
@@ -132,10 +132,10 @@ $retry[KIND] = RetryDescriptor;
|
|
|
132
132
|
*/
|
|
133
133
|
const AlephaRetry = $module({
|
|
134
134
|
name: "alepha.retry",
|
|
135
|
-
|
|
135
|
+
primitives: [$retry],
|
|
136
136
|
services: [RetryProvider]
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
//#endregion
|
|
140
|
-
export { $retry, AlephaRetry, RetryCancelError,
|
|
140
|
+
export { $retry, AlephaRetry, RetryCancelError, RetryPrimitive, RetryProvider, RetryTimeoutError };
|
|
141
141
|
//# sourceMappingURL=index.js.map
|