alepha 0.13.7 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/assets/swagger-ui/swagger-ui-standalone-preset.js +1 -1
- package/assets/swagger-ui/swagger-ui.css +1 -1
- package/dist/{api-audits → api/audits}/index.browser.js +4 -4
- package/dist/api/audits/index.browser.js.map +1 -0
- package/dist/{api-audits → api/audits}/index.d.ts +10 -9
- package/dist/api/audits/index.d.ts.map +1 -0
- package/dist/{api-audits → api/audits}/index.js +8 -8
- package/dist/api/audits/index.js.map +1 -0
- package/dist/{api-files → api/files}/index.browser.js +5 -5
- package/dist/api/files/index.browser.js.map +1 -0
- package/dist/{api-files → api/files}/index.d.ts +18 -10
- package/dist/api/files/index.d.ts.map +1 -0
- package/dist/{api-files → api/files}/index.js +10 -10
- package/dist/api/files/index.js.map +1 -0
- package/dist/{api-jobs → api/jobs}/index.browser.js +5 -5
- package/dist/api/jobs/index.browser.js.map +1 -0
- package/dist/{api-jobs → api/jobs}/index.d.ts +168 -167
- package/dist/api/jobs/index.d.ts.map +1 -0
- package/dist/{api-jobs → api/jobs}/index.js +9 -9
- package/dist/api/jobs/index.js.map +1 -0
- package/dist/{api-notifications → api/notifications}/index.browser.js +11 -11
- package/dist/api/notifications/index.browser.js.map +1 -0
- package/dist/api/notifications/index.d.ts +327 -0
- package/dist/api/notifications/index.d.ts.map +1 -0
- package/dist/{api-notifications → api/notifications}/index.js +11 -11
- package/dist/api/notifications/index.js.map +1 -0
- package/dist/{api-parameters → api/parameters}/index.browser.js +2 -2
- package/dist/api/parameters/index.browser.js.map +1 -0
- package/dist/{api-parameters → api/parameters}/index.d.ts +11 -11
- package/dist/api/parameters/index.d.ts.map +1 -0
- package/dist/{api-parameters → api/parameters}/index.js +7 -7
- package/dist/api/parameters/index.js.map +1 -0
- package/dist/{api-users → api/users}/index.browser.js +6 -6
- package/dist/api/users/index.browser.js.map +1 -0
- package/dist/{api-users → api/users}/index.d.ts +836 -836
- package/dist/api/users/index.d.ts.map +1 -0
- package/dist/{api-users → api/users}/index.js +99 -766
- package/dist/api/users/index.js.map +1 -0
- package/dist/{api-verifications → api/verifications}/index.browser.js +5 -5
- package/dist/api/verifications/index.browser.js.map +1 -0
- package/dist/api/verifications/index.d.ts +248 -0
- package/dist/api/verifications/index.d.ts.map +1 -0
- package/dist/{api-verifications → api/verifications}/index.js +11 -11
- package/dist/api/verifications/index.js.map +1 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/bucket/index.d.ts.map +1 -0
- package/dist/cache/{index.d.ts → core/index.d.ts} +4 -4
- package/dist/cache/core/index.d.ts.map +1 -0
- package/dist/cache/{index.js → core/index.js} +5 -5
- package/dist/cache/core/index.js.map +1 -0
- package/dist/{cache-redis → cache/redis}/index.d.ts +2 -2
- package/dist/cache/redis/index.d.ts.map +1 -0
- package/dist/{cache-redis → cache/redis}/index.js +2 -2
- package/dist/cache/redis/index.js.map +1 -0
- package/dist/cli/index.d.ts +78 -58
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +454 -154
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +15 -5
- package/dist/command/index.d.ts.map +1 -0
- package/dist/command/index.js +45 -6
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +1334 -1318
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +75 -71
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +1337 -1321
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +1337 -1321
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts.map +1 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/fake/index.d.ts.map +1 -0
- package/dist/file/index.d.ts.map +1 -0
- package/dist/lock/{index.d.ts → core/index.d.ts} +5 -5
- package/dist/lock/core/index.d.ts.map +1 -0
- package/dist/lock/{index.js → core/index.js} +5 -5
- package/dist/lock/core/index.js.map +1 -0
- package/dist/{lock-redis → lock/redis}/index.d.ts +2 -2
- package/dist/lock/redis/index.d.ts.map +1 -0
- package/dist/{lock-redis → lock/redis}/index.js +2 -2
- package/dist/lock/redis/index.js.map +1 -0
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +820 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +978 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/orm/index.d.ts +180 -107
- package/dist/orm/index.d.ts.map +1 -0
- package/dist/orm/index.js +260 -174
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +548 -0
- package/dist/queue/core/index.d.ts.map +1 -0
- package/dist/queue/core/index.js +391 -0
- package/dist/queue/core/index.js.map +1 -0
- package/dist/queue/redis/index.d.ts +28 -0
- package/dist/queue/redis/index.d.ts.map +1 -0
- package/dist/queue/redis/index.js +43 -0
- package/dist/queue/redis/index.js.map +1 -0
- package/dist/redis/index.d.ts.map +1 -0
- package/dist/retry/index.d.ts.map +1 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/scheduler/index.d.ts +1 -1
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +1 -393
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +2 -1413
- package/dist/security/index.js.map +1 -1
- package/dist/{server-auth → server/auth}/index.browser.js +6 -6
- package/dist/server/auth/index.browser.js.map +1 -0
- package/dist/{server-auth → server/auth}/index.d.ts +167 -167
- package/dist/server/auth/index.d.ts.map +1 -0
- package/dist/server/auth/index.js +742 -0
- package/dist/server/auth/index.js.map +1 -0
- package/dist/{server-cache → server/cache}/index.d.ts +2 -2
- package/dist/server/cache/index.d.ts.map +1 -0
- package/dist/{server-cache → server/cache}/index.js +2 -2
- package/dist/server/cache/index.js.map +1 -0
- package/dist/{server-compress → server/compress}/index.d.ts +2 -2
- package/dist/server/compress/index.d.ts.map +1 -0
- package/dist/{server-compress → server/compress}/index.js +2 -2
- package/dist/server/compress/index.js.map +1 -0
- package/dist/{server-cookies → server/cookies}/index.browser.js +3 -3
- package/dist/server/cookies/index.browser.js.map +1 -0
- package/dist/{server-cookies → server/cookies}/index.d.ts +4 -4
- package/dist/server/cookies/index.d.ts.map +1 -0
- package/dist/{server-cookies → server/cookies}/index.js +4 -4
- package/dist/server/cookies/index.js.map +1 -0
- package/dist/server/{index.browser.js → core/index.browser.js} +14 -14
- package/dist/server/core/index.browser.js.map +1 -0
- package/dist/server/{index.d.ts → core/index.d.ts} +36 -36
- package/dist/server/core/index.d.ts.map +1 -0
- package/dist/server/{index.js → core/index.js} +27 -27
- package/dist/server/core/index.js.map +1 -0
- package/dist/{server-cors → server/cors}/index.d.ts +3 -3
- package/dist/server/cors/index.d.ts.map +1 -0
- package/dist/{server-cors → server/cors}/index.js +3 -3
- package/dist/server/cors/index.js.map +1 -0
- package/dist/{server-health → server/health}/index.d.ts +3 -3
- package/dist/server/health/index.d.ts.map +1 -0
- package/dist/{server-health → server/health}/index.js +3 -3
- package/dist/server/health/index.js.map +1 -0
- package/dist/{server-helmet → server/helmet}/index.d.ts +2 -2
- package/dist/server/helmet/index.d.ts.map +1 -0
- package/dist/{server-helmet → server/helmet}/index.js +2 -2
- package/dist/server/helmet/index.js.map +1 -0
- package/dist/{server-links → server/links}/index.browser.js +5 -5
- package/dist/server/links/index.browser.js.map +1 -0
- package/dist/{server-links → server/links}/index.d.ts +40 -40
- package/dist/server/links/index.d.ts.map +1 -0
- package/dist/{server-links → server/links}/index.js +7 -7
- package/dist/server/links/index.js.map +1 -0
- package/dist/{server-metrics → server/metrics}/index.d.ts +2 -2
- package/dist/server/metrics/index.d.ts.map +1 -0
- package/dist/server/metrics/index.js +74 -0
- package/dist/server/metrics/index.js.map +1 -0
- package/dist/{server-multipart → server/multipart}/index.d.ts +2 -2
- package/dist/server/multipart/index.d.ts.map +1 -0
- package/dist/{server-multipart → server/multipart}/index.js +2 -2
- package/dist/server/multipart/index.js.map +1 -0
- package/dist/{server-proxy → server/proxy}/index.d.ts +3 -3
- package/dist/server/proxy/index.d.ts.map +1 -0
- package/dist/{server-proxy → server/proxy}/index.js +3 -3
- package/dist/server/proxy/index.js.map +1 -0
- package/dist/{server-rate-limit → server/rate-limit}/index.d.ts +4 -4
- package/dist/server/rate-limit/index.d.ts.map +1 -0
- package/dist/{server-rate-limit → server/rate-limit}/index.js +4 -4
- package/dist/server/rate-limit/index.js.map +1 -0
- package/dist/{server-security → server/security}/index.browser.js +1 -1
- package/dist/server/security/index.browser.js.map +1 -0
- package/dist/{server-security → server/security}/index.d.ts +4 -4
- package/dist/server/security/index.d.ts.map +1 -0
- package/dist/{server-security → server/security}/index.js +4 -4
- package/dist/server/security/index.js.map +1 -0
- package/dist/{server-static → server/static}/index.d.ts +3 -3
- package/dist/server/static/index.d.ts.map +1 -0
- package/dist/{server-static → server/static}/index.js +3 -3
- package/dist/server/static/index.js.map +1 -0
- package/dist/{server-swagger → server/swagger}/index.d.ts +3 -3
- package/dist/server/swagger/index.d.ts.map +1 -0
- package/dist/{server-swagger → server/swagger}/index.js +4 -4
- package/dist/server/swagger/index.js.map +1 -0
- package/dist/sms/index.d.ts.map +1 -0
- package/dist/thread/index.d.ts.map +1 -0
- package/dist/topic/{index.d.ts → core/index.d.ts} +6 -6
- package/dist/topic/core/index.d.ts.map +1 -0
- package/dist/topic/{index.js → core/index.js} +6 -6
- package/dist/topic/core/index.js.map +1 -0
- package/dist/{topic-redis → topic/redis}/index.d.ts +2 -2
- package/dist/topic/redis/index.d.ts.map +1 -0
- package/dist/{topic-redis → topic/redis}/index.js +2 -2
- package/dist/topic/redis/index.js.map +1 -0
- package/dist/vite/index.d.ts +21 -2
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +48 -19
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts.map +1 -0
- package/package.json +162 -158
- package/src/{api-files → api/files}/index.ts +1 -0
- package/src/{api-parameters → api/parameters}/index.ts +1 -1
- package/src/{api-users → api/users}/primitives/$userRealm.ts +1 -1
- package/src/{api-users → api/users}/providers/UserRealmProvider.ts +6 -7
- package/src/{api-verifications → api/verifications}/index.ts +2 -0
- package/src/cli/apps/AlephaCli.ts +2 -0
- package/src/cli/apps/AlephaPackageBuilderCli.ts +83 -54
- package/src/cli/assets/appRouterTs.ts +1 -1
- package/src/cli/assets/biomeJson.ts +1 -1
- package/src/cli/assets/indexHtml.ts +1 -1
- package/src/cli/assets/mainBrowserTs.ts +1 -1
- package/src/cli/assets/mainTs.ts +9 -10
- package/src/cli/assets/viteConfigTs.ts +1 -1
- package/src/cli/commands/ChangelogCommands.ts +389 -0
- package/src/cli/commands/CoreCommands.ts +10 -6
- package/src/cli/commands/DrizzleCommands.ts +204 -4
- package/src/cli/commands/VerifyCommands.ts +4 -1
- package/src/cli/commands/ViteCommands.ts +46 -25
- package/src/cli/services/AlephaCliUtils.ts +52 -164
- package/src/command/providers/CliProvider.ts +76 -5
- package/src/core/providers/SchemaValidator.ts +24 -2
- package/src/mcp/errors/McpError.ts +72 -0
- package/src/mcp/helpers/jsonrpc.ts +163 -0
- package/src/mcp/index.ts +132 -0
- package/src/mcp/interfaces/McpTypes.ts +248 -0
- package/src/mcp/primitives/$prompt.ts +188 -0
- package/src/mcp/primitives/$resource.ts +171 -0
- package/src/mcp/primitives/$tool.ts +285 -0
- package/src/mcp/providers/McpServerProvider.ts +382 -0
- package/src/mcp/transports/SseMcpTransport.ts +172 -0
- package/src/mcp/transports/StdioMcpTransport.ts +126 -0
- package/src/orm/index.ts +12 -0
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +164 -0
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +3 -1
- package/src/queue/{index.ts → core/index.ts} +2 -3
- package/src/queue/{primitives → core/primitives}/$queue.ts +17 -162
- package/src/queue/core/providers/MemoryQueueProvider.ts +19 -0
- package/src/queue/core/providers/QueueProvider.ts +23 -0
- package/src/queue/core/providers/WorkerProvider.ts +244 -0
- package/src/queue/redis/providers/RedisQueueProvider.ts +31 -0
- package/src/server/{index.ts → core/index.ts} +1 -0
- package/src/{server-rate-limit → server/rate-limit}/index.ts +1 -1
- package/src/{server-swagger → server/swagger}/providers/ServerSwaggerProvider.ts +1 -0
- package/src/vite/plugins/viteAlephaBuild.ts +8 -2
- package/src/vite/plugins/viteAlephaDev.ts +6 -2
- package/src/vite/tasks/buildServer.ts +1 -1
- package/src/vite/tasks/copyAssets.ts +32 -8
- package/src/vite/tasks/generateCloudflare.ts +43 -15
- package/src/vite/tasks/runAlepha.ts +1 -0
- package/dist/api-audits/index.browser.js.map +0 -1
- package/dist/api-audits/index.js.map +0 -1
- package/dist/api-files/index.browser.js.map +0 -1
- package/dist/api-files/index.js.map +0 -1
- package/dist/api-jobs/index.browser.js.map +0 -1
- package/dist/api-jobs/index.js.map +0 -1
- package/dist/api-notifications/index.browser.js.map +0 -1
- package/dist/api-notifications/index.d.ts +0 -327
- package/dist/api-notifications/index.js.map +0 -1
- package/dist/api-parameters/index.browser.js.map +0 -1
- package/dist/api-parameters/index.js.map +0 -1
- package/dist/api-users/index.browser.js.map +0 -1
- package/dist/api-users/index.js.map +0 -1
- package/dist/api-verifications/index.browser.js.map +0 -1
- package/dist/api-verifications/index.d.ts +0 -229
- package/dist/api-verifications/index.js.map +0 -1
- package/dist/cache/index.js.map +0 -1
- package/dist/cache-redis/index.js.map +0 -1
- package/dist/lock/index.js.map +0 -1
- package/dist/lock-redis/index.js.map +0 -1
- package/dist/queue/index.d.ts +0 -1265
- package/dist/queue/index.js +0 -1037
- package/dist/queue/index.js.map +0 -1
- package/dist/queue-redis/index.d.ts +0 -82
- package/dist/queue-redis/index.js +0 -872
- package/dist/queue-redis/index.js.map +0 -1
- package/dist/server/index.browser.js.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/server-auth/index.browser.js.map +0 -1
- package/dist/server-auth/index.js +0 -1973
- package/dist/server-auth/index.js.map +0 -1
- package/dist/server-cache/index.js.map +0 -1
- package/dist/server-compress/index.js.map +0 -1
- package/dist/server-cookies/index.browser.js.map +0 -1
- package/dist/server-cookies/index.js.map +0 -1
- package/dist/server-cors/index.js.map +0 -1
- package/dist/server-health/index.js.map +0 -1
- package/dist/server-helmet/index.js.map +0 -1
- package/dist/server-links/index.browser.js.map +0 -1
- package/dist/server-links/index.js.map +0 -1
- package/dist/server-metrics/index.js +0 -4532
- package/dist/server-metrics/index.js.map +0 -1
- package/dist/server-multipart/index.js.map +0 -1
- package/dist/server-proxy/index.js.map +0 -1
- package/dist/server-rate-limit/index.js.map +0 -1
- package/dist/server-security/index.browser.js.map +0 -1
- package/dist/server-security/index.js.map +0 -1
- package/dist/server-static/index.js.map +0 -1
- package/dist/server-swagger/index.js.map +0 -1
- package/dist/topic/index.js.map +0 -1
- package/dist/topic-redis/index.js.map +0 -1
- package/src/queue/interfaces/QueueJob.ts +0 -459
- package/src/queue/providers/MemoryQueueProvider.ts +0 -850
- package/src/queue/providers/QueueProvider.ts +0 -319
- package/src/queue/providers/WorkerProvider.ts +0 -344
- package/src/queue-redis/providers/RedisQueueProvider.ts +0 -1209
- /package/src/{api-audits → api/audits}/controllers/AuditController.ts +0 -0
- /package/src/{api-audits → api/audits}/entities/audits.ts +0 -0
- /package/src/{api-audits → api/audits}/index.browser.ts +0 -0
- /package/src/{api-audits → api/audits}/index.ts +0 -0
- /package/src/{api-audits → api/audits}/primitives/$audit.ts +0 -0
- /package/src/{api-audits → api/audits}/schemas/auditQuerySchema.ts +0 -0
- /package/src/{api-audits → api/audits}/schemas/auditResourceSchema.ts +0 -0
- /package/src/{api-audits → api/audits}/schemas/createAuditSchema.ts +0 -0
- /package/src/{api-audits → api/audits}/services/AuditService.ts +0 -0
- /package/src/{api-files → api/files}/controllers/FileController.ts +0 -0
- /package/src/{api-files → api/files}/controllers/StorageStatsController.ts +0 -0
- /package/src/{api-files → api/files}/entities/files.ts +0 -0
- /package/src/{api-files → api/files}/index.browser.ts +0 -0
- /package/src/{api-files → api/files}/jobs/FileJobs.ts +0 -0
- /package/src/{api-files → api/files}/schemas/fileQuerySchema.ts +0 -0
- /package/src/{api-files → api/files}/schemas/fileResourceSchema.ts +0 -0
- /package/src/{api-files → api/files}/schemas/storageStatsSchema.ts +0 -0
- /package/src/{api-files → api/files}/services/FileService.ts +0 -0
- /package/src/{api-jobs → api/jobs}/controllers/JobController.ts +0 -0
- /package/src/{api-jobs → api/jobs}/entities/jobExecutions.ts +0 -0
- /package/src/{api-jobs → api/jobs}/index.browser.ts +0 -0
- /package/src/{api-jobs → api/jobs}/index.ts +0 -0
- /package/src/{api-jobs → api/jobs}/primitives/$job.ts +0 -0
- /package/src/{api-jobs → api/jobs}/providers/JobProvider.ts +0 -0
- /package/src/{api-jobs → api/jobs}/schemas/jobExecutionQuerySchema.ts +0 -0
- /package/src/{api-jobs → api/jobs}/schemas/jobExecutionResourceSchema.ts +0 -0
- /package/src/{api-jobs → api/jobs}/schemas/triggerJobSchema.ts +0 -0
- /package/src/{api-jobs → api/jobs}/services/JobService.ts +0 -0
- /package/src/{api-notifications → api/notifications}/controllers/NotificationController.ts +0 -0
- /package/src/{api-notifications → api/notifications}/entities/notifications.ts +0 -0
- /package/src/{api-notifications → api/notifications}/index.browser.ts +0 -0
- /package/src/{api-notifications → api/notifications}/index.ts +0 -0
- /package/src/{api-notifications → api/notifications}/jobs/NotificationJobs.ts +0 -0
- /package/src/{api-notifications → api/notifications}/primitives/$notification.ts +0 -0
- /package/src/{api-notifications → api/notifications}/queues/NotificationQueues.ts +0 -0
- /package/src/{api-notifications → api/notifications}/schemas/notificationContactPreferencesSchema.ts +0 -0
- /package/src/{api-notifications → api/notifications}/schemas/notificationContactSchema.ts +0 -0
- /package/src/{api-notifications → api/notifications}/schemas/notificationCreateSchema.ts +0 -0
- /package/src/{api-notifications → api/notifications}/schemas/notificationQuerySchema.ts +0 -0
- /package/src/{api-notifications → api/notifications}/services/NotificationSenderService.ts +0 -0
- /package/src/{api-notifications → api/notifications}/services/NotificationService.ts +0 -0
- /package/src/{api-parameters → api/parameters}/controllers/ConfigController.ts +0 -0
- /package/src/{api-parameters → api/parameters}/entities/parameters.ts +0 -0
- /package/src/{api-parameters → api/parameters}/index.browser.ts +0 -0
- /package/src/{api-parameters → api/parameters}/primitives/$config.ts +0 -0
- /package/src/{api-parameters → api/parameters}/schedulers/ConfigActivationScheduler.ts +0 -0
- /package/src/{api-parameters → api/parameters}/services/ConfigStore.ts +0 -0
- /package/src/{api-users → api/users}/atoms/realmAuthSettingsAtom.ts +0 -0
- /package/src/{api-users → api/users}/controllers/IdentityController.ts +0 -0
- /package/src/{api-users → api/users}/controllers/SessionController.ts +0 -0
- /package/src/{api-users → api/users}/controllers/UserController.ts +0 -0
- /package/src/{api-users → api/users}/controllers/UserRealmController.ts +0 -0
- /package/src/{api-users → api/users}/entities/identities.ts +0 -0
- /package/src/{api-users → api/users}/entities/sessions.ts +0 -0
- /package/src/{api-users → api/users}/entities/users.ts +0 -0
- /package/src/{api-users → api/users}/index.browser.ts +0 -0
- /package/src/{api-users → api/users}/index.ts +0 -0
- /package/src/{api-users → api/users}/notifications/UserNotifications.ts +0 -0
- /package/src/{api-users → api/users}/schemas/completePasswordResetRequestSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/completeRegistrationRequestSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/createUserSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/identityQuerySchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/identityResourceSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/loginSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/passwordResetIntentResponseSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/registerQuerySchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/registerRequestSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/registerResponseSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/registerSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/registrationIntentResponseSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/resetPasswordSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/sessionQuerySchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/sessionResourceSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/updateUserSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/userQuerySchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/userRealmConfigSchema.ts +0 -0
- /package/src/{api-users → api/users}/schemas/userResourceSchema.ts +0 -0
- /package/src/{api-users → api/users}/services/CredentialService.ts +0 -0
- /package/src/{api-users → api/users}/services/IdentityService.ts +0 -0
- /package/src/{api-users → api/users}/services/RegistrationService.ts +0 -0
- /package/src/{api-users → api/users}/services/SessionCrudService.ts +0 -0
- /package/src/{api-users → api/users}/services/SessionService.ts +0 -0
- /package/src/{api-users → api/users}/services/UserService.ts +0 -0
- /package/src/{api-verifications → api/verifications}/controllers/VerificationController.ts +0 -0
- /package/src/{api-verifications → api/verifications}/entities/verifications.ts +0 -0
- /package/src/{api-verifications → api/verifications}/index.browser.ts +0 -0
- /package/src/{api-verifications → api/verifications}/jobs/VerificationJobs.ts +0 -0
- /package/src/{api-verifications → api/verifications}/parameters/VerificationParameters.ts +0 -0
- /package/src/{api-verifications → api/verifications}/schemas/requestVerificationCodeResponseSchema.ts +0 -0
- /package/src/{api-verifications → api/verifications}/schemas/validateVerificationCodeResponseSchema.ts +0 -0
- /package/src/{api-verifications → api/verifications}/schemas/verificationSettingsSchema.ts +0 -0
- /package/src/{api-verifications → api/verifications}/schemas/verificationTypeEnumSchema.ts +0 -0
- /package/src/{api-verifications → api/verifications}/services/VerificationService.ts +0 -0
- /package/src/cache/{errors → core/errors}/CacheError.ts +0 -0
- /package/src/cache/{index.ts → core/index.ts} +0 -0
- /package/src/cache/{primitives → core/primitives}/$cache.ts +0 -0
- /package/src/cache/{providers → core/providers}/CacheProvider.ts +0 -0
- /package/src/cache/{providers → core/providers}/MemoryCacheProvider.ts +0 -0
- /package/src/{cache-redis → cache/redis}/index.ts +0 -0
- /package/src/{cache-redis → cache/redis}/providers/RedisCacheProvider.ts +0 -0
- /package/src/lock/{index.ts → core/index.ts} +0 -0
- /package/src/lock/{primitives → core/primitives}/$lock.ts +0 -0
- /package/src/lock/{providers → core/providers}/LockProvider.ts +0 -0
- /package/src/lock/{providers → core/providers}/LockTopicProvider.ts +0 -0
- /package/src/lock/{providers → core/providers}/MemoryLockProvider.ts +0 -0
- /package/src/{lock-redis → lock/redis}/index.ts +0 -0
- /package/src/{lock-redis → lock/redis}/providers/RedisLockProvider.ts +0 -0
- /package/src/queue/{primitives → core/primitives}/$consumer.ts +0 -0
- /package/src/{queue-redis → queue/redis}/index.ts +0 -0
- /package/src/{server-auth → server/auth}/constants/routes.ts +0 -0
- /package/src/{server-auth → server/auth}/index.browser.ts +0 -0
- /package/src/{server-auth → server/auth}/index.shared.ts +0 -0
- /package/src/{server-auth → server/auth}/index.ts +0 -0
- /package/src/{server-auth → server/auth}/primitives/$auth.ts +0 -0
- /package/src/{server-auth → server/auth}/primitives/$authApple.ts +0 -0
- /package/src/{server-auth → server/auth}/primitives/$authCredentials.ts +0 -0
- /package/src/{server-auth → server/auth}/primitives/$authGithub.ts +0 -0
- /package/src/{server-auth → server/auth}/primitives/$authGoogle.ts +0 -0
- /package/src/{server-auth → server/auth}/providers/ServerAuthProvider.ts +0 -0
- /package/src/{server-auth → server/auth}/schemas/authenticationProviderSchema.ts +0 -0
- /package/src/{server-auth → server/auth}/schemas/tokenResponseSchema.ts +0 -0
- /package/src/{server-auth → server/auth}/schemas/tokensSchema.ts +0 -0
- /package/src/{server-auth → server/auth}/schemas/userinfoResponseSchema.ts +0 -0
- /package/src/{server-cache → server/cache}/index.ts +0 -0
- /package/src/{server-cache → server/cache}/providers/ServerCacheProvider.ts +0 -0
- /package/src/{server-compress → server/compress}/index.ts +0 -0
- /package/src/{server-compress → server/compress}/providers/ServerCompressProvider.ts +0 -0
- /package/src/{server-cookies → server/cookies}/index.browser.ts +0 -0
- /package/src/{server-cookies → server/cookies}/index.ts +0 -0
- /package/src/{server-cookies → server/cookies}/primitives/$cookie.browser.ts +0 -0
- /package/src/{server-cookies → server/cookies}/primitives/$cookie.ts +0 -0
- /package/src/{server-cookies → server/cookies}/providers/ServerCookiesProvider.ts +0 -0
- /package/src/{server-cookies → server/cookies}/services/CookieParser.ts +0 -0
- /package/src/server/{constants → core/constants}/routeMethods.ts +0 -0
- /package/src/server/{errors → core/errors}/BadRequestError.ts +0 -0
- /package/src/server/{errors → core/errors}/ConflictError.ts +0 -0
- /package/src/server/{errors → core/errors}/ForbiddenError.ts +0 -0
- /package/src/server/{errors → core/errors}/HttpError.ts +0 -0
- /package/src/server/{errors → core/errors}/NotFoundError.ts +0 -0
- /package/src/server/{errors → core/errors}/UnauthorizedError.ts +0 -0
- /package/src/server/{errors → core/errors}/ValidationError.ts +0 -0
- /package/src/server/{helpers → core/helpers}/ServerReply.ts +0 -0
- /package/src/server/{helpers → core/helpers}/isMultipart.ts +0 -0
- /package/src/server/{index.browser.ts → core/index.browser.ts} +0 -0
- /package/src/server/{index.shared.ts → core/index.shared.ts} +0 -0
- /package/src/server/{interfaces → core/interfaces}/ServerRequest.ts +0 -0
- /package/src/server/{primitives → core/primitives}/$action.ts +0 -0
- /package/src/server/{primitives → core/primitives}/$route.ts +0 -0
- /package/src/server/{providers → core/providers}/BunHttpServerProvider.ts +0 -0
- /package/src/server/{providers → core/providers}/NodeHttpServerProvider.ts +0 -0
- /package/src/server/{providers → core/providers}/ServerBodyParserProvider.ts +0 -0
- /package/src/server/{providers → core/providers}/ServerLoggerProvider.ts +0 -0
- /package/src/server/{providers → core/providers}/ServerNotReadyProvider.ts +0 -0
- /package/src/server/{providers → core/providers}/ServerProvider.ts +0 -0
- /package/src/server/{providers → core/providers}/ServerRouterProvider.ts +0 -0
- /package/src/server/{providers → core/providers}/ServerTimingProvider.ts +0 -0
- /package/src/server/{schemas → core/schemas}/errorSchema.ts +0 -0
- /package/src/server/{schemas → core/schemas}/okSchema.ts +0 -0
- /package/src/server/{services → core/services}/HttpClient.ts +0 -0
- /package/src/server/{services → core/services}/ServerRequestParser.ts +0 -0
- /package/src/server/{services → core/services}/UserAgentParser.ts +0 -0
- /package/src/{server-cors → server/cors}/index.ts +0 -0
- /package/src/{server-cors → server/cors}/primitives/$cors.ts +0 -0
- /package/src/{server-cors → server/cors}/providers/ServerCorsProvider.ts +0 -0
- /package/src/{server-health → server/health}/index.ts +0 -0
- /package/src/{server-health → server/health}/providers/ServerHealthProvider.ts +0 -0
- /package/src/{server-health → server/health}/schemas/healthSchema.ts +0 -0
- /package/src/{server-helmet → server/helmet}/index.ts +0 -0
- /package/src/{server-helmet → server/helmet}/providers/ServerHelmetProvider.ts +0 -0
- /package/src/{server-links → server/links}/index.browser.ts +0 -0
- /package/src/{server-links → server/links}/index.ts +0 -0
- /package/src/{server-links → server/links}/primitives/$client.ts +0 -0
- /package/src/{server-links → server/links}/primitives/$remote.ts +0 -0
- /package/src/{server-links → server/links}/providers/LinkProvider.ts +0 -0
- /package/src/{server-links → server/links}/providers/RemotePrimitiveProvider.ts +0 -0
- /package/src/{server-links → server/links}/providers/ServerLinksProvider.ts +0 -0
- /package/src/{server-links → server/links}/schemas/apiLinksResponseSchema.ts +0 -0
- /package/src/{server-metrics → server/metrics}/index.ts +0 -0
- /package/src/{server-metrics → server/metrics}/providers/ServerMetricsProvider.ts +0 -0
- /package/src/{server-multipart → server/multipart}/index.ts +0 -0
- /package/src/{server-multipart → server/multipart}/providers/ServerMultipartProvider.ts +0 -0
- /package/src/{server-proxy → server/proxy}/index.ts +0 -0
- /package/src/{server-proxy → server/proxy}/primitives/$proxy.ts +0 -0
- /package/src/{server-proxy → server/proxy}/providers/ServerProxyProvider.ts +0 -0
- /package/src/{server-rate-limit → server/rate-limit}/primitives/$rateLimit.ts +0 -0
- /package/src/{server-rate-limit → server/rate-limit}/providers/ServerRateLimitProvider.ts +0 -0
- /package/src/{server-security → server/security}/index.browser.ts +0 -0
- /package/src/{server-security → server/security}/index.ts +0 -0
- /package/src/{server-security → server/security}/primitives/$basicAuth.ts +0 -0
- /package/src/{server-security → server/security}/providers/ServerBasicAuthProvider.ts +0 -0
- /package/src/{server-security → server/security}/providers/ServerSecurityProvider.ts +0 -0
- /package/src/{server-static → server/static}/index.ts +0 -0
- /package/src/{server-static → server/static}/primitives/$serve.ts +0 -0
- /package/src/{server-static → server/static}/providers/ServerStaticProvider.ts +0 -0
- /package/src/{server-swagger → server/swagger}/index.ts +0 -0
- /package/src/{server-swagger → server/swagger}/primitives/$swagger.ts +0 -0
- /package/src/topic/{errors → core/errors}/TopicTimeoutError.ts +0 -0
- /package/src/topic/{index.ts → core/index.ts} +0 -0
- /package/src/topic/{primitives → core/primitives}/$subscriber.ts +0 -0
- /package/src/topic/{primitives → core/primitives}/$topic.ts +0 -0
- /package/src/topic/{providers → core/providers}/MemoryTopicProvider.ts +0 -0
- /package/src/topic/{providers → core/providers}/TopicProvider.ts +0 -0
- /package/src/{topic-redis → topic/redis}/index.ts +0 -0
- /package/src/{topic-redis → topic/redis}/providers/RedisTopicProvider.ts +0 -0
|
@@ -1,1209 +0,0 @@
|
|
|
1
|
-
import { $env, $hook, $inject, type Static, t } from "alepha";
|
|
2
|
-
import { $logger } from "alepha/logger";
|
|
3
|
-
import {
|
|
4
|
-
type QueueAcquiredJob,
|
|
5
|
-
type QueueCleanOptions,
|
|
6
|
-
type QueueGetJobsOptions,
|
|
7
|
-
type QueueJob,
|
|
8
|
-
type QueueJobCounts,
|
|
9
|
-
type QueueJobOptions,
|
|
10
|
-
type QueueJobStatus,
|
|
11
|
-
QueueProvider,
|
|
12
|
-
} from "alepha/queue";
|
|
13
|
-
import { type RedisClient, RedisProvider } from "alepha/redis";
|
|
14
|
-
|
|
15
|
-
// Default job options
|
|
16
|
-
const DEFAULT_MAX_ATTEMPTS = 1;
|
|
17
|
-
const DEFAULT_LOCK_DURATION = 30000; // 30 seconds
|
|
18
|
-
const DEFAULT_BACKOFF_DELAY = 1000; // 1 second
|
|
19
|
-
const DEFAULT_BACKOFF_MAX_DELAY = 30000; // 30 seconds
|
|
20
|
-
|
|
21
|
-
const envSchema = t.object({
|
|
22
|
-
REDIS_QUEUE_PREFIX: t.text({
|
|
23
|
-
default: "queue",
|
|
24
|
-
}),
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Lua script for atomic job acquisition
|
|
28
|
-
// This script atomically:
|
|
29
|
-
// 1. Gets the highest priority job from waiting ZSET
|
|
30
|
-
// 2. Removes it from waiting
|
|
31
|
-
// 3. Adds it to active SET
|
|
32
|
-
// 4. Updates job state in HASH
|
|
33
|
-
// Returns: job data as JSON string, or nil if no job available
|
|
34
|
-
const ACQUIRE_JOB_SCRIPT = `
|
|
35
|
-
local waitingKey = KEYS[1]
|
|
36
|
-
local activeKey = KEYS[2]
|
|
37
|
-
local jobKeyPrefix = KEYS[3]
|
|
38
|
-
local workerId = ARGV[1]
|
|
39
|
-
local now = tonumber(ARGV[2])
|
|
40
|
-
local lockDuration = tonumber(ARGV[3])
|
|
41
|
-
|
|
42
|
-
-- Get highest priority job (lowest score)
|
|
43
|
-
local jobs = redis.call('ZRANGE', waitingKey, 0, 0)
|
|
44
|
-
if #jobs == 0 then
|
|
45
|
-
return nil
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
local jobId = jobs[1]
|
|
49
|
-
local jobKey = jobKeyPrefix .. ':' .. jobId
|
|
50
|
-
|
|
51
|
-
-- Remove from waiting (atomic check)
|
|
52
|
-
local removed = redis.call('ZREM', waitingKey, jobId)
|
|
53
|
-
if removed == 0 then
|
|
54
|
-
return nil
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
-- Get current job data
|
|
58
|
-
local jobData = redis.call('HGETALL', jobKey)
|
|
59
|
-
if #jobData == 0 then
|
|
60
|
-
return nil
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
-- Parse job data into table
|
|
64
|
-
local job = {}
|
|
65
|
-
for i = 1, #jobData, 2 do
|
|
66
|
-
job[jobData[i]] = jobData[i + 1]
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
-- Parse current state
|
|
70
|
-
local state = cjson.decode(job['state'])
|
|
71
|
-
local options = cjson.decode(job['options'])
|
|
72
|
-
|
|
73
|
-
-- Update state
|
|
74
|
-
state['status'] = 'active'
|
|
75
|
-
state['attempts'] = state['attempts'] + 1
|
|
76
|
-
state['lockedBy'] = workerId
|
|
77
|
-
state['lockedUntil'] = now + (options['lockDuration'] or lockDuration)
|
|
78
|
-
state['processedAt'] = now
|
|
79
|
-
|
|
80
|
-
-- Save updated state
|
|
81
|
-
redis.call('HSET', jobKey, 'state', cjson.encode(state))
|
|
82
|
-
|
|
83
|
-
-- Add to active set
|
|
84
|
-
redis.call('SADD', activeKey, jobId)
|
|
85
|
-
|
|
86
|
-
-- Return job data
|
|
87
|
-
return cjson.encode({
|
|
88
|
-
id = job['id'],
|
|
89
|
-
queue = job['queue'],
|
|
90
|
-
payload = cjson.decode(job['payload']),
|
|
91
|
-
options = options,
|
|
92
|
-
state = state
|
|
93
|
-
})
|
|
94
|
-
`;
|
|
95
|
-
|
|
96
|
-
// Lua script for completing a job with removeOnComplete support
|
|
97
|
-
const COMPLETE_JOB_SCRIPT = `
|
|
98
|
-
local jobKey = KEYS[1]
|
|
99
|
-
local activeKey = KEYS[2]
|
|
100
|
-
local completedKey = KEYS[3]
|
|
101
|
-
local jobId = ARGV[1]
|
|
102
|
-
local now = tonumber(ARGV[2])
|
|
103
|
-
local result = ARGV[3]
|
|
104
|
-
|
|
105
|
-
-- Get job data
|
|
106
|
-
local jobData = redis.call('HGETALL', jobKey)
|
|
107
|
-
if #jobData == 0 then
|
|
108
|
-
return nil
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
-- Parse job data
|
|
112
|
-
local job = {}
|
|
113
|
-
for i = 1, #jobData, 2 do
|
|
114
|
-
job[jobData[i]] = jobData[i + 1]
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
local state = cjson.decode(job['state'])
|
|
118
|
-
local options = cjson.decode(job['options'])
|
|
119
|
-
local processedAt = state['processedAt'] or now
|
|
120
|
-
|
|
121
|
-
-- Remove from active
|
|
122
|
-
redis.call('SREM', activeKey, jobId)
|
|
123
|
-
|
|
124
|
-
-- Update state
|
|
125
|
-
state['status'] = 'completed'
|
|
126
|
-
state['completedAt'] = now
|
|
127
|
-
state['result'] = result ~= '' and cjson.decode(result) or nil
|
|
128
|
-
state['lockedBy'] = nil
|
|
129
|
-
state['lockedUntil'] = nil
|
|
130
|
-
|
|
131
|
-
local removeOnComplete = options['removeOnComplete']
|
|
132
|
-
|
|
133
|
-
if removeOnComplete == true then
|
|
134
|
-
-- Remove job immediately
|
|
135
|
-
redis.call('DEL', jobKey)
|
|
136
|
-
return cjson.encode({ removed = true, duration = now - processedAt })
|
|
137
|
-
else
|
|
138
|
-
-- Update job state
|
|
139
|
-
redis.call('HSET', jobKey, 'state', cjson.encode(state))
|
|
140
|
-
|
|
141
|
-
-- Add to completed list (newest first)
|
|
142
|
-
redis.call('LPUSH', completedKey, jobId)
|
|
143
|
-
|
|
144
|
-
-- If removeOnComplete is a number, trim the list (0 means keep none)
|
|
145
|
-
if type(removeOnComplete) == 'number' and removeOnComplete >= 0 then
|
|
146
|
-
-- Get jobs to remove
|
|
147
|
-
local toRemove = redis.call('LRANGE', completedKey, removeOnComplete, -1)
|
|
148
|
-
for _, oldJobId in ipairs(toRemove) do
|
|
149
|
-
redis.call('DEL', jobKey:gsub(jobId, oldJobId))
|
|
150
|
-
end
|
|
151
|
-
redis.call('LTRIM', completedKey, 0, removeOnComplete - 1)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
return cjson.encode({ removed = false, duration = now - processedAt })
|
|
155
|
-
end
|
|
156
|
-
`;
|
|
157
|
-
|
|
158
|
-
// Lua script for failing a job with retry support
|
|
159
|
-
const FAIL_JOB_SCRIPT = `
|
|
160
|
-
local jobKey = KEYS[1]
|
|
161
|
-
local activeKey = KEYS[2]
|
|
162
|
-
local delayedKey = KEYS[3]
|
|
163
|
-
local failedKey = KEYS[4]
|
|
164
|
-
local jobId = ARGV[1]
|
|
165
|
-
local now = tonumber(ARGV[2])
|
|
166
|
-
local errorMsg = ARGV[3]
|
|
167
|
-
local stackTrace = ARGV[4]
|
|
168
|
-
local backoffDelay = tonumber(ARGV[5])
|
|
169
|
-
|
|
170
|
-
-- Get job data
|
|
171
|
-
local jobData = redis.call('HGETALL', jobKey)
|
|
172
|
-
if #jobData == 0 then
|
|
173
|
-
return nil
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
-- Parse job data
|
|
177
|
-
local job = {}
|
|
178
|
-
for i = 1, #jobData, 2 do
|
|
179
|
-
job[jobData[i]] = jobData[i + 1]
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
local state = cjson.decode(job['state'])
|
|
183
|
-
local options = cjson.decode(job['options'])
|
|
184
|
-
|
|
185
|
-
-- Remove from active
|
|
186
|
-
redis.call('SREM', activeKey, jobId)
|
|
187
|
-
|
|
188
|
-
local maxAttempts = options['maxAttempts'] or 1
|
|
189
|
-
local hasMoreAttempts = state['attempts'] < maxAttempts
|
|
190
|
-
|
|
191
|
-
if hasMoreAttempts then
|
|
192
|
-
-- Schedule for retry
|
|
193
|
-
state['status'] = 'delayed'
|
|
194
|
-
state['availableAt'] = now + backoffDelay
|
|
195
|
-
state['error'] = errorMsg
|
|
196
|
-
state['stackTrace'] = stackTrace ~= '' and stackTrace or nil
|
|
197
|
-
state['lockedBy'] = nil
|
|
198
|
-
state['lockedUntil'] = nil
|
|
199
|
-
|
|
200
|
-
redis.call('HSET', jobKey, 'state', cjson.encode(state))
|
|
201
|
-
redis.call('ZADD', delayedKey, now + backoffDelay, jobId)
|
|
202
|
-
|
|
203
|
-
return cjson.encode({ status = 'retrying', delay = backoffDelay, attempt = state['attempts'] + 1 })
|
|
204
|
-
else
|
|
205
|
-
-- Permanently failed
|
|
206
|
-
state['status'] = 'failed'
|
|
207
|
-
state['failedAt'] = now
|
|
208
|
-
state['error'] = errorMsg
|
|
209
|
-
state['stackTrace'] = stackTrace ~= '' and stackTrace or nil
|
|
210
|
-
state['lockedBy'] = nil
|
|
211
|
-
state['lockedUntil'] = nil
|
|
212
|
-
|
|
213
|
-
local removeOnFail = options['removeOnFail']
|
|
214
|
-
|
|
215
|
-
if removeOnFail == true then
|
|
216
|
-
redis.call('DEL', jobKey)
|
|
217
|
-
return cjson.encode({ status = 'failed', removed = true, attempts = state['attempts'] })
|
|
218
|
-
else
|
|
219
|
-
redis.call('HSET', jobKey, 'state', cjson.encode(state))
|
|
220
|
-
redis.call('LPUSH', failedKey, jobId)
|
|
221
|
-
|
|
222
|
-
if type(removeOnFail) == 'number' and removeOnFail >= 0 then
|
|
223
|
-
local toRemove = redis.call('LRANGE', failedKey, removeOnFail, -1)
|
|
224
|
-
for _, oldJobId in ipairs(toRemove) do
|
|
225
|
-
redis.call('DEL', jobKey:gsub(jobId, oldJobId))
|
|
226
|
-
end
|
|
227
|
-
redis.call('LTRIM', failedKey, 0, removeOnFail - 1)
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
return cjson.encode({ status = 'failed', removed = false, attempts = state['attempts'] })
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
`;
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Redis-based queue provider with full job support.
|
|
237
|
-
*
|
|
238
|
-
* Features:
|
|
239
|
-
* - Atomic job acquisition using Lua scripts
|
|
240
|
-
* - Blocking wait using Redis BZPOPMIN (no polling)
|
|
241
|
-
* - Event emission for job lifecycle
|
|
242
|
-
* - removeOnComplete/removeOnFail support
|
|
243
|
-
*
|
|
244
|
-
* Uses the following Redis data structures:
|
|
245
|
-
* - HASH `{prefix}:job:{queue}:{id}` - Job data
|
|
246
|
-
* - ZSET `{prefix}:waiting:{queue}` - Waiting jobs (score = priority)
|
|
247
|
-
* - ZSET `{prefix}:delayed:{queue}` - Delayed jobs (score = availableAt timestamp)
|
|
248
|
-
* - SET `{prefix}:active:{queue}` - Active jobs
|
|
249
|
-
* - LIST `{prefix}:completed:{queue}` - Completed jobs (newest first)
|
|
250
|
-
* - LIST `{prefix}:failed:{queue}` - Failed jobs (newest first)
|
|
251
|
-
* - LIST `{prefix}:messages:{queue}` - Simple message queue (backward compat)
|
|
252
|
-
* - LIST `{prefix}:notify:{queue}` - Notification list for blocking wait
|
|
253
|
-
*/
|
|
254
|
-
export class RedisQueueProvider extends QueueProvider {
|
|
255
|
-
protected readonly log = $logger();
|
|
256
|
-
protected readonly env: Static<typeof envSchema> = $env(envSchema);
|
|
257
|
-
protected readonly redisProvider: RedisProvider = $inject(RedisProvider);
|
|
258
|
-
|
|
259
|
-
// Dedicated connection for blocking operations
|
|
260
|
-
protected blockingClient: RedisClient | undefined;
|
|
261
|
-
protected shouldStop = false;
|
|
262
|
-
|
|
263
|
-
// Loaded Lua script SHAs
|
|
264
|
-
protected acquireJobSha: string | undefined;
|
|
265
|
-
protected completeJobSha: string | undefined;
|
|
266
|
-
protected failJobSha: string | undefined;
|
|
267
|
-
|
|
268
|
-
protected readonly start = $hook({
|
|
269
|
-
on: "start",
|
|
270
|
-
handler: async () => {
|
|
271
|
-
this.shouldStop = false;
|
|
272
|
-
this.blockingClient = this.redisProvider.duplicate();
|
|
273
|
-
await this.blockingClient.connect();
|
|
274
|
-
|
|
275
|
-
// Load Lua scripts
|
|
276
|
-
const redis = this.redisProvider.publisher;
|
|
277
|
-
const acquireSha = await redis.scriptLoad(ACQUIRE_JOB_SCRIPT);
|
|
278
|
-
const completeSha = await redis.scriptLoad(COMPLETE_JOB_SCRIPT);
|
|
279
|
-
const failSha = await redis.scriptLoad(FAIL_JOB_SCRIPT);
|
|
280
|
-
this.acquireJobSha = acquireSha.toString();
|
|
281
|
-
this.completeJobSha = completeSha.toString();
|
|
282
|
-
this.failJobSha = failSha.toString();
|
|
283
|
-
},
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
protected readonly stop = $hook({
|
|
287
|
-
on: "stop",
|
|
288
|
-
handler: async () => {
|
|
289
|
-
this.shouldStop = true;
|
|
290
|
-
if (this.blockingClient?.isOpen) {
|
|
291
|
-
await this.blockingClient.close();
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// ===========================================
|
|
297
|
-
// Key helpers
|
|
298
|
-
// ===========================================
|
|
299
|
-
|
|
300
|
-
protected key(type: string, queue: string, id?: string): string {
|
|
301
|
-
const base = `${this.env.REDIS_QUEUE_PREFIX}:${type}:${queue}`;
|
|
302
|
-
return id ? `${base}:${id}` : base;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
protected messageKey(queue: string): string {
|
|
306
|
-
return `${this.env.REDIS_QUEUE_PREFIX}:${queue}`;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
protected notifyKey(queue: string): string {
|
|
310
|
-
return `${this.env.REDIS_QUEUE_PREFIX}:notify:${queue}`;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// ===========================================
|
|
314
|
-
// Simple Message API (backward compatible)
|
|
315
|
-
// ===========================================
|
|
316
|
-
|
|
317
|
-
public async push(queue: string, message: string): Promise<void> {
|
|
318
|
-
await this.redisProvider.publisher.LPUSH(this.messageKey(queue), message);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
public async pop(queue: string): Promise<string | undefined> {
|
|
322
|
-
const value = await this.redisProvider.publisher.RPOP(
|
|
323
|
-
this.messageKey(queue),
|
|
324
|
-
);
|
|
325
|
-
if (value == null) return undefined;
|
|
326
|
-
return String(value);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
public async popBlocking(
|
|
330
|
-
queues: string[],
|
|
331
|
-
timeoutSeconds: number,
|
|
332
|
-
): Promise<{ queue: string; message: string } | undefined> {
|
|
333
|
-
if (queues.length === 0 || !this.blockingClient) {
|
|
334
|
-
return undefined;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const prefixedQueues = queues.map((q) => this.messageKey(q));
|
|
338
|
-
const result = await this.blockingClient.BRPOP(
|
|
339
|
-
prefixedQueues,
|
|
340
|
-
timeoutSeconds,
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
if (result == null) return undefined;
|
|
344
|
-
|
|
345
|
-
const key = result.key.toString();
|
|
346
|
-
const prefixLength = this.env.REDIS_QUEUE_PREFIX.length + 1;
|
|
347
|
-
const queue = key.substring(prefixLength);
|
|
348
|
-
|
|
349
|
-
return { queue, message: result.element.toString() };
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// ===========================================
|
|
353
|
-
// Job API Implementation
|
|
354
|
-
// ===========================================
|
|
355
|
-
|
|
356
|
-
protected async generateJobId(): Promise<string> {
|
|
357
|
-
const counter = await this.redisProvider.publisher.INCR(
|
|
358
|
-
`${this.env.REDIS_QUEUE_PREFIX}:job_counter`,
|
|
359
|
-
);
|
|
360
|
-
return `job_${counter}_${Date.now()}`;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
protected serializeJob(job: QueueJob): Record<string, string> {
|
|
364
|
-
return {
|
|
365
|
-
id: job.id,
|
|
366
|
-
queue: job.queue,
|
|
367
|
-
payload: JSON.stringify(job.payload),
|
|
368
|
-
options: JSON.stringify(job.options),
|
|
369
|
-
state: JSON.stringify(job.state),
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
protected deserializeJob(data: Record<string, string>): QueueJob | undefined {
|
|
374
|
-
if (!data.id) return undefined;
|
|
375
|
-
return {
|
|
376
|
-
id: data.id,
|
|
377
|
-
queue: data.queue,
|
|
378
|
-
payload: JSON.parse(data.payload),
|
|
379
|
-
options: JSON.parse(data.options),
|
|
380
|
-
state: JSON.parse(data.state),
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
public async addJob<T>(
|
|
385
|
-
queue: string,
|
|
386
|
-
payload: T,
|
|
387
|
-
options?: QueueJobOptions,
|
|
388
|
-
): Promise<QueueJob<T>> {
|
|
389
|
-
const redis = this.redisProvider.publisher;
|
|
390
|
-
const now = Date.now();
|
|
391
|
-
const delay = options?.delay ?? 0;
|
|
392
|
-
const isDelayed = delay > 0;
|
|
393
|
-
|
|
394
|
-
const job: QueueJob<T> = {
|
|
395
|
-
id: await this.generateJobId(),
|
|
396
|
-
queue,
|
|
397
|
-
payload,
|
|
398
|
-
options: {
|
|
399
|
-
priority: options?.priority ?? 0,
|
|
400
|
-
delay: options?.delay ?? 0,
|
|
401
|
-
maxAttempts: options?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
|
|
402
|
-
backoff: options?.backoff,
|
|
403
|
-
lockDuration: options?.lockDuration ?? DEFAULT_LOCK_DURATION,
|
|
404
|
-
removeOnComplete: options?.removeOnComplete,
|
|
405
|
-
removeOnFail: options?.removeOnFail,
|
|
406
|
-
},
|
|
407
|
-
state: {
|
|
408
|
-
status: isDelayed ? "delayed" : "waiting",
|
|
409
|
-
attempts: 0,
|
|
410
|
-
createdAt: now,
|
|
411
|
-
availableAt: isDelayed ? now + delay : now,
|
|
412
|
-
},
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
// Store job data
|
|
416
|
-
await redis.HSET(this.key("job", queue, job.id), this.serializeJob(job));
|
|
417
|
-
|
|
418
|
-
// Add to appropriate queue
|
|
419
|
-
if (isDelayed) {
|
|
420
|
-
await redis.ZADD(this.key("delayed", queue), {
|
|
421
|
-
score: job.state.availableAt!,
|
|
422
|
-
value: job.id,
|
|
423
|
-
});
|
|
424
|
-
} else {
|
|
425
|
-
await redis.ZADD(this.key("waiting", queue), {
|
|
426
|
-
score: job.options.priority ?? 0,
|
|
427
|
-
value: job.id,
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
// Notify blocking waiters by pushing to notify list
|
|
431
|
-
await redis.LPUSH(this.notifyKey(queue), job.id);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
this.log.debug(`Added job ${job.id} to queue ${queue}`, {
|
|
435
|
-
status: job.state.status,
|
|
436
|
-
priority: job.options.priority,
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
// Emit waiting event
|
|
440
|
-
if (!isDelayed) {
|
|
441
|
-
await this.emit({
|
|
442
|
-
type: "waiting",
|
|
443
|
-
queue,
|
|
444
|
-
jobId: job.id,
|
|
445
|
-
timestamp: now,
|
|
446
|
-
job,
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return job;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
public async acquireJob(
|
|
454
|
-
queues: string[],
|
|
455
|
-
workerId: string,
|
|
456
|
-
timeoutSeconds: number,
|
|
457
|
-
): Promise<QueueAcquiredJob | undefined> {
|
|
458
|
-
if (!this.blockingClient || this.shouldStop) {
|
|
459
|
-
return undefined;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const redis = this.redisProvider.publisher;
|
|
463
|
-
const now = Date.now();
|
|
464
|
-
const endTime = now + timeoutSeconds * 1000;
|
|
465
|
-
|
|
466
|
-
while (Date.now() < endTime && !this.shouldStop) {
|
|
467
|
-
// Try to acquire a job from each queue using Lua script
|
|
468
|
-
for (const queue of queues) {
|
|
469
|
-
try {
|
|
470
|
-
const result = await redis.evalSha(this.acquireJobSha!, {
|
|
471
|
-
keys: [
|
|
472
|
-
this.key("waiting", queue),
|
|
473
|
-
this.key("active", queue),
|
|
474
|
-
this.key("job", queue),
|
|
475
|
-
],
|
|
476
|
-
arguments: [
|
|
477
|
-
workerId,
|
|
478
|
-
String(Date.now()),
|
|
479
|
-
String(DEFAULT_LOCK_DURATION),
|
|
480
|
-
],
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
if (result) {
|
|
484
|
-
const job = JSON.parse(result as string) as QueueJob;
|
|
485
|
-
|
|
486
|
-
this.log.debug(`Worker ${workerId} acquired job ${job.id}`, {
|
|
487
|
-
queue,
|
|
488
|
-
attempt: job.state.attempts,
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
// Emit active event
|
|
492
|
-
await this.emit({
|
|
493
|
-
type: "active",
|
|
494
|
-
queue,
|
|
495
|
-
jobId: job.id,
|
|
496
|
-
timestamp: Date.now(),
|
|
497
|
-
workerId,
|
|
498
|
-
attempt: job.state.attempts,
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
return { queue, job };
|
|
502
|
-
}
|
|
503
|
-
} catch (error) {
|
|
504
|
-
// Script might fail if job data is corrupted, log and continue
|
|
505
|
-
this.log.warn(`Failed to acquire job from ${queue}`, error);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// No job found, wait for notification using BRPOP
|
|
510
|
-
// This blocks until a new job is added or timeout
|
|
511
|
-
const notifyKeys = queues.map((q) => this.notifyKey(q));
|
|
512
|
-
const remainingTimeout = Math.max(
|
|
513
|
-
1,
|
|
514
|
-
Math.ceil((endTime - Date.now()) / 1000),
|
|
515
|
-
);
|
|
516
|
-
|
|
517
|
-
try {
|
|
518
|
-
const notification = await this.blockingClient.BRPOP(
|
|
519
|
-
notifyKeys,
|
|
520
|
-
Math.min(remainingTimeout, 5), // Check every 5s max for shutdown
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
// If we got a notification, loop back to try acquiring
|
|
524
|
-
// The notification just signals that a job was added
|
|
525
|
-
if (notification) {
|
|
526
|
-
}
|
|
527
|
-
} catch {
|
|
528
|
-
// Blocking client closed during shutdown
|
|
529
|
-
if (this.shouldStop) {
|
|
530
|
-
return undefined;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
return undefined;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
protected bufferRecordToString(
|
|
539
|
-
record: Record<string, Buffer>,
|
|
540
|
-
): Record<string, string> {
|
|
541
|
-
const result: Record<string, string> = {};
|
|
542
|
-
for (const [key, value] of Object.entries(record)) {
|
|
543
|
-
result[key] = value?.toString() ?? "";
|
|
544
|
-
}
|
|
545
|
-
return result;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
public async completeJob(
|
|
549
|
-
queue: string,
|
|
550
|
-
jobId: string,
|
|
551
|
-
result?: unknown,
|
|
552
|
-
): Promise<void> {
|
|
553
|
-
const redis = this.redisProvider.publisher;
|
|
554
|
-
const now = Date.now();
|
|
555
|
-
|
|
556
|
-
try {
|
|
557
|
-
const luaResult = await redis.evalSha(this.completeJobSha!, {
|
|
558
|
-
keys: [
|
|
559
|
-
this.key("job", queue, jobId),
|
|
560
|
-
this.key("active", queue),
|
|
561
|
-
this.key("completed", queue),
|
|
562
|
-
],
|
|
563
|
-
arguments: [
|
|
564
|
-
jobId,
|
|
565
|
-
String(now),
|
|
566
|
-
result !== undefined ? JSON.stringify(result) : "",
|
|
567
|
-
],
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
if (!luaResult) {
|
|
571
|
-
this.log.warn(`Attempted to complete unknown job ${jobId}`);
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const { removed, duration } = JSON.parse(luaResult as string);
|
|
576
|
-
this.log.debug(`Job ${jobId} completed${removed ? " and removed" : ""}`, {
|
|
577
|
-
queue,
|
|
578
|
-
result,
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
// Emit completed event
|
|
582
|
-
await this.emit({
|
|
583
|
-
type: "completed",
|
|
584
|
-
queue,
|
|
585
|
-
jobId,
|
|
586
|
-
timestamp: now,
|
|
587
|
-
result,
|
|
588
|
-
duration,
|
|
589
|
-
});
|
|
590
|
-
} catch (error) {
|
|
591
|
-
// Fallback to non-atomic completion if Lua fails
|
|
592
|
-
this.log.warn(`Lua completeJob failed, using fallback`, error);
|
|
593
|
-
await this.completeJobFallback(queue, jobId, result);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
protected async completeJobFallback(
|
|
598
|
-
queue: string,
|
|
599
|
-
jobId: string,
|
|
600
|
-
result?: unknown,
|
|
601
|
-
): Promise<void> {
|
|
602
|
-
const redis = this.redisProvider.publisher;
|
|
603
|
-
const now = Date.now();
|
|
604
|
-
|
|
605
|
-
// Get job data
|
|
606
|
-
const jobData = await redis.HGETALL(this.key("job", queue, jobId));
|
|
607
|
-
const job = this.deserializeJob(this.bufferRecordToString(jobData));
|
|
608
|
-
if (!job) {
|
|
609
|
-
this.log.warn(`Attempted to complete unknown job ${jobId}`);
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const duration = now - (job.state.processedAt ?? now);
|
|
614
|
-
|
|
615
|
-
// Remove from active
|
|
616
|
-
await redis.SREM(this.key("active", queue), jobId);
|
|
617
|
-
|
|
618
|
-
// Update job state
|
|
619
|
-
job.state.status = "completed";
|
|
620
|
-
job.state.completedAt = now;
|
|
621
|
-
job.state.result = result;
|
|
622
|
-
job.state.lockedBy = undefined;
|
|
623
|
-
job.state.lockedUntil = undefined;
|
|
624
|
-
|
|
625
|
-
const removeOnComplete = job.options.removeOnComplete;
|
|
626
|
-
if (removeOnComplete === true) {
|
|
627
|
-
await redis.DEL(this.key("job", queue, jobId));
|
|
628
|
-
} else {
|
|
629
|
-
await redis.HSET(this.key("job", queue, jobId), {
|
|
630
|
-
state: JSON.stringify(job.state),
|
|
631
|
-
});
|
|
632
|
-
await redis.LPUSH(this.key("completed", queue), jobId);
|
|
633
|
-
|
|
634
|
-
if (typeof removeOnComplete === "number" && removeOnComplete >= 0) {
|
|
635
|
-
await this.cleanJobs(queue, "completed", {
|
|
636
|
-
maxCount: removeOnComplete,
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
this.log.debug(`Job ${jobId} completed`, { queue });
|
|
642
|
-
|
|
643
|
-
// Emit completed event
|
|
644
|
-
await this.emit({
|
|
645
|
-
type: "completed",
|
|
646
|
-
queue,
|
|
647
|
-
jobId,
|
|
648
|
-
timestamp: now,
|
|
649
|
-
result,
|
|
650
|
-
duration,
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
public async failJob(
|
|
655
|
-
queue: string,
|
|
656
|
-
jobId: string,
|
|
657
|
-
error: string,
|
|
658
|
-
stackTrace?: string,
|
|
659
|
-
): Promise<void> {
|
|
660
|
-
const redis = this.redisProvider.publisher;
|
|
661
|
-
const now = Date.now();
|
|
662
|
-
|
|
663
|
-
// Get job to calculate backoff
|
|
664
|
-
const jobData = await redis.HGETALL(this.key("job", queue, jobId));
|
|
665
|
-
const job = this.deserializeJob(this.bufferRecordToString(jobData));
|
|
666
|
-
if (!job) {
|
|
667
|
-
this.log.warn(`Attempted to fail unknown job ${jobId}`);
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
const backoffDelay = this.calculateBackoff(job);
|
|
672
|
-
|
|
673
|
-
try {
|
|
674
|
-
const luaResult = await redis.evalSha(this.failJobSha!, {
|
|
675
|
-
keys: [
|
|
676
|
-
this.key("job", queue, jobId),
|
|
677
|
-
this.key("active", queue),
|
|
678
|
-
this.key("delayed", queue),
|
|
679
|
-
this.key("failed", queue),
|
|
680
|
-
],
|
|
681
|
-
arguments: [
|
|
682
|
-
jobId,
|
|
683
|
-
String(now),
|
|
684
|
-
error,
|
|
685
|
-
stackTrace ?? "",
|
|
686
|
-
String(backoffDelay),
|
|
687
|
-
],
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
if (!luaResult) {
|
|
691
|
-
this.log.warn(`Attempted to fail unknown job ${jobId}`);
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
const result = JSON.parse(luaResult as string);
|
|
696
|
-
|
|
697
|
-
if (result.status === "retrying") {
|
|
698
|
-
this.log.debug(`Job ${jobId} failed, will retry in ${result.delay}ms`, {
|
|
699
|
-
queue,
|
|
700
|
-
attempt: job.state.attempts,
|
|
701
|
-
error,
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
// Emit retrying event
|
|
705
|
-
await this.emit({
|
|
706
|
-
type: "retrying",
|
|
707
|
-
queue,
|
|
708
|
-
jobId,
|
|
709
|
-
timestamp: now,
|
|
710
|
-
error,
|
|
711
|
-
attempt: result.attempt,
|
|
712
|
-
delay: result.delay,
|
|
713
|
-
});
|
|
714
|
-
} else {
|
|
715
|
-
this.log.debug(
|
|
716
|
-
`Job ${jobId} permanently failed${result.removed ? " and removed" : ""}`,
|
|
717
|
-
{ queue, error },
|
|
718
|
-
);
|
|
719
|
-
|
|
720
|
-
// Emit failed event
|
|
721
|
-
await this.emit({
|
|
722
|
-
type: "failed",
|
|
723
|
-
queue,
|
|
724
|
-
jobId,
|
|
725
|
-
timestamp: now,
|
|
726
|
-
error,
|
|
727
|
-
stackTrace,
|
|
728
|
-
attempts: result.attempts,
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
} catch (luaError) {
|
|
732
|
-
// Fallback to non-atomic fail if Lua fails
|
|
733
|
-
this.log.warn(`Lua failJob failed, using fallback`, luaError);
|
|
734
|
-
await this.failJobFallback(queue, jobId, error, stackTrace);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
protected async failJobFallback(
|
|
739
|
-
queue: string,
|
|
740
|
-
jobId: string,
|
|
741
|
-
error: string,
|
|
742
|
-
stackTrace?: string,
|
|
743
|
-
): Promise<void> {
|
|
744
|
-
const redis = this.redisProvider.publisher;
|
|
745
|
-
const now = Date.now();
|
|
746
|
-
|
|
747
|
-
const jobData = await redis.HGETALL(this.key("job", queue, jobId));
|
|
748
|
-
const job = this.deserializeJob(this.bufferRecordToString(jobData));
|
|
749
|
-
if (!job) {
|
|
750
|
-
this.log.warn(`Attempted to fail unknown job ${jobId}`);
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Remove from active
|
|
755
|
-
await redis.SREM(this.key("active", queue), jobId);
|
|
756
|
-
|
|
757
|
-
const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
758
|
-
const hasMoreAttempts = job.state.attempts < maxAttempts;
|
|
759
|
-
|
|
760
|
-
if (hasMoreAttempts) {
|
|
761
|
-
const backoffDelay = this.calculateBackoff(job);
|
|
762
|
-
|
|
763
|
-
job.state.status = "delayed";
|
|
764
|
-
job.state.availableAt = now + backoffDelay;
|
|
765
|
-
job.state.error = error;
|
|
766
|
-
job.state.stackTrace = stackTrace;
|
|
767
|
-
job.state.lockedBy = undefined;
|
|
768
|
-
job.state.lockedUntil = undefined;
|
|
769
|
-
|
|
770
|
-
await redis.HSET(this.key("job", queue, jobId), {
|
|
771
|
-
state: JSON.stringify(job.state),
|
|
772
|
-
});
|
|
773
|
-
await redis.ZADD(this.key("delayed", queue), {
|
|
774
|
-
score: job.state.availableAt,
|
|
775
|
-
value: jobId,
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
this.log.debug(`Job ${jobId} failed, will retry in ${backoffDelay}ms`, {
|
|
779
|
-
queue,
|
|
780
|
-
attempt: job.state.attempts,
|
|
781
|
-
maxAttempts,
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
// Emit retrying event
|
|
785
|
-
await this.emit({
|
|
786
|
-
type: "retrying",
|
|
787
|
-
queue,
|
|
788
|
-
jobId,
|
|
789
|
-
timestamp: now,
|
|
790
|
-
error,
|
|
791
|
-
attempt: job.state.attempts + 1,
|
|
792
|
-
delay: backoffDelay,
|
|
793
|
-
});
|
|
794
|
-
} else {
|
|
795
|
-
job.state.status = "failed";
|
|
796
|
-
job.state.failedAt = now;
|
|
797
|
-
job.state.error = error;
|
|
798
|
-
job.state.stackTrace = stackTrace;
|
|
799
|
-
job.state.lockedBy = undefined;
|
|
800
|
-
job.state.lockedUntil = undefined;
|
|
801
|
-
|
|
802
|
-
const removeOnFail = job.options.removeOnFail;
|
|
803
|
-
if (removeOnFail === true) {
|
|
804
|
-
await redis.DEL(this.key("job", queue, jobId));
|
|
805
|
-
} else {
|
|
806
|
-
await redis.HSET(this.key("job", queue, jobId), {
|
|
807
|
-
state: JSON.stringify(job.state),
|
|
808
|
-
});
|
|
809
|
-
await redis.LPUSH(this.key("failed", queue), jobId);
|
|
810
|
-
|
|
811
|
-
if (typeof removeOnFail === "number" && removeOnFail >= 0) {
|
|
812
|
-
await this.cleanJobs(queue, "failed", { maxCount: removeOnFail });
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
this.log.debug(`Job ${jobId} permanently failed`, { queue });
|
|
817
|
-
|
|
818
|
-
// Emit failed event
|
|
819
|
-
await this.emit({
|
|
820
|
-
type: "failed",
|
|
821
|
-
queue,
|
|
822
|
-
jobId,
|
|
823
|
-
timestamp: now,
|
|
824
|
-
error,
|
|
825
|
-
stackTrace,
|
|
826
|
-
attempts: job.state.attempts,
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
protected calculateBackoff(job: QueueJob): number {
|
|
832
|
-
const backoff = job.options.backoff;
|
|
833
|
-
const attempt = job.state.attempts;
|
|
834
|
-
|
|
835
|
-
if (!backoff) return DEFAULT_BACKOFF_DELAY;
|
|
836
|
-
|
|
837
|
-
const baseDelay = backoff.delay ?? DEFAULT_BACKOFF_DELAY;
|
|
838
|
-
const maxDelay = backoff.maxDelay ?? DEFAULT_BACKOFF_MAX_DELAY;
|
|
839
|
-
|
|
840
|
-
if (backoff.type === "fixed") return baseDelay;
|
|
841
|
-
|
|
842
|
-
const exponentialDelay = baseDelay * 2 ** (attempt - 1);
|
|
843
|
-
return Math.min(exponentialDelay, maxDelay);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
public async renewJobLock(
|
|
847
|
-
queue: string,
|
|
848
|
-
jobId: string,
|
|
849
|
-
workerId: string,
|
|
850
|
-
): Promise<boolean> {
|
|
851
|
-
const redis = this.redisProvider.publisher;
|
|
852
|
-
|
|
853
|
-
const jobData = await redis.HGETALL(this.key("job", queue, jobId));
|
|
854
|
-
const job = this.deserializeJob(this.bufferRecordToString(jobData));
|
|
855
|
-
if (!job || job.state.lockedBy !== workerId) {
|
|
856
|
-
return false;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
job.state.lockedUntil =
|
|
860
|
-
Date.now() + (job.options.lockDuration ?? DEFAULT_LOCK_DURATION);
|
|
861
|
-
await redis.HSET(this.key("job", queue, jobId), {
|
|
862
|
-
state: JSON.stringify(job.state),
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
return true;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
public async getJob(
|
|
869
|
-
queue: string,
|
|
870
|
-
jobId: string,
|
|
871
|
-
): Promise<QueueJob | undefined> {
|
|
872
|
-
const redis = this.redisProvider.publisher;
|
|
873
|
-
const jobData = await redis.HGETALL(this.key("job", queue, jobId));
|
|
874
|
-
return this.deserializeJob(this.bufferRecordToString(jobData));
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
public async getJobs(
|
|
878
|
-
queue: string,
|
|
879
|
-
status: QueueJobStatus,
|
|
880
|
-
options?: QueueGetJobsOptions,
|
|
881
|
-
): Promise<QueueJob[]> {
|
|
882
|
-
const redis = this.redisProvider.publisher;
|
|
883
|
-
const limit = options?.limit ?? 100;
|
|
884
|
-
const offset = options?.offset ?? 0;
|
|
885
|
-
|
|
886
|
-
let jobIds: string[];
|
|
887
|
-
|
|
888
|
-
switch (status) {
|
|
889
|
-
case "waiting": {
|
|
890
|
-
const results = await redis.ZRANGE(
|
|
891
|
-
this.key("waiting", queue),
|
|
892
|
-
offset,
|
|
893
|
-
offset + limit - 1,
|
|
894
|
-
);
|
|
895
|
-
jobIds = results.map((r) => r.toString());
|
|
896
|
-
break;
|
|
897
|
-
}
|
|
898
|
-
case "delayed": {
|
|
899
|
-
const results = await redis.ZRANGE(
|
|
900
|
-
this.key("delayed", queue),
|
|
901
|
-
offset,
|
|
902
|
-
offset + limit - 1,
|
|
903
|
-
);
|
|
904
|
-
jobIds = results.map((r) => r.toString());
|
|
905
|
-
break;
|
|
906
|
-
}
|
|
907
|
-
case "active": {
|
|
908
|
-
const results = await redis.SMEMBERS(this.key("active", queue));
|
|
909
|
-
jobIds = results.map((r) => r.toString()).slice(offset, offset + limit);
|
|
910
|
-
break;
|
|
911
|
-
}
|
|
912
|
-
case "completed": {
|
|
913
|
-
const results = await redis.LRANGE(
|
|
914
|
-
this.key("completed", queue),
|
|
915
|
-
offset,
|
|
916
|
-
offset + limit - 1,
|
|
917
|
-
);
|
|
918
|
-
jobIds = results.map((r) => r.toString());
|
|
919
|
-
break;
|
|
920
|
-
}
|
|
921
|
-
case "failed": {
|
|
922
|
-
const results = await redis.LRANGE(
|
|
923
|
-
this.key("failed", queue),
|
|
924
|
-
offset,
|
|
925
|
-
offset + limit - 1,
|
|
926
|
-
);
|
|
927
|
-
jobIds = results.map((r) => r.toString());
|
|
928
|
-
break;
|
|
929
|
-
}
|
|
930
|
-
default:
|
|
931
|
-
jobIds = [];
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
const jobs: QueueJob[] = [];
|
|
935
|
-
for (const jobId of jobIds) {
|
|
936
|
-
const job = await this.getJob(queue, jobId);
|
|
937
|
-
if (job) jobs.push(job);
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
return jobs;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
public async getJobCounts(queue: string): Promise<QueueJobCounts> {
|
|
944
|
-
const redis = this.redisProvider.publisher;
|
|
945
|
-
|
|
946
|
-
const [waiting, delayed, active, completed, failed] = await Promise.all([
|
|
947
|
-
redis.ZCARD(this.key("waiting", queue)),
|
|
948
|
-
redis.ZCARD(this.key("delayed", queue)),
|
|
949
|
-
redis.SCARD(this.key("active", queue)),
|
|
950
|
-
redis.LLEN(this.key("completed", queue)),
|
|
951
|
-
redis.LLEN(this.key("failed", queue)),
|
|
952
|
-
]);
|
|
953
|
-
|
|
954
|
-
return { waiting, delayed, active, completed, failed };
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
public async promoteDelayedJobs(queue: string): Promise<number> {
|
|
958
|
-
const redis = this.redisProvider.publisher;
|
|
959
|
-
const now = Date.now();
|
|
960
|
-
|
|
961
|
-
// Get jobs whose availableAt has passed
|
|
962
|
-
const results = await redis.ZRANGEBYSCORE(
|
|
963
|
-
this.key("delayed", queue),
|
|
964
|
-
"-inf",
|
|
965
|
-
now,
|
|
966
|
-
);
|
|
967
|
-
|
|
968
|
-
let promoted = 0;
|
|
969
|
-
for (const result of results) {
|
|
970
|
-
const jobId = result.toString();
|
|
971
|
-
|
|
972
|
-
// Remove from delayed
|
|
973
|
-
const removed = await redis.ZREM(this.key("delayed", queue), jobId);
|
|
974
|
-
if (removed === 0) continue;
|
|
975
|
-
|
|
976
|
-
// Get and update job
|
|
977
|
-
const job = await this.getJob(queue, jobId);
|
|
978
|
-
if (!job) continue;
|
|
979
|
-
|
|
980
|
-
job.state.status = "waiting";
|
|
981
|
-
await redis.HSET(this.key("job", queue, jobId), {
|
|
982
|
-
state: JSON.stringify(job.state),
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
// Add to waiting with priority score
|
|
986
|
-
await redis.ZADD(this.key("waiting", queue), {
|
|
987
|
-
score: job.options.priority ?? 0,
|
|
988
|
-
value: jobId,
|
|
989
|
-
});
|
|
990
|
-
|
|
991
|
-
// Notify waiting workers
|
|
992
|
-
await redis.LPUSH(this.notifyKey(queue), jobId);
|
|
993
|
-
|
|
994
|
-
promoted++;
|
|
995
|
-
this.log.debug(`Promoted delayed job ${jobId}`, { queue });
|
|
996
|
-
|
|
997
|
-
// Emit waiting event
|
|
998
|
-
await this.emit({
|
|
999
|
-
type: "waiting",
|
|
1000
|
-
queue,
|
|
1001
|
-
jobId,
|
|
1002
|
-
timestamp: now,
|
|
1003
|
-
job,
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
return promoted;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
public async recoverStalledJobs(
|
|
1011
|
-
queue: string,
|
|
1012
|
-
stalledThresholdMs: number,
|
|
1013
|
-
): Promise<string[]> {
|
|
1014
|
-
const redis = this.redisProvider.publisher;
|
|
1015
|
-
const now = Date.now();
|
|
1016
|
-
|
|
1017
|
-
const activeJobIds = await redis.SMEMBERS(this.key("active", queue));
|
|
1018
|
-
const stalledJobIds: string[] = [];
|
|
1019
|
-
|
|
1020
|
-
for (const result of activeJobIds) {
|
|
1021
|
-
const jobId = result.toString();
|
|
1022
|
-
const job = await this.getJob(queue, jobId);
|
|
1023
|
-
if (!job) continue;
|
|
1024
|
-
|
|
1025
|
-
const lockExpired =
|
|
1026
|
-
(job.state.lockedUntil ?? 0) + stalledThresholdMs < now;
|
|
1027
|
-
if (!lockExpired) continue;
|
|
1028
|
-
|
|
1029
|
-
stalledJobIds.push(jobId);
|
|
1030
|
-
const workerId = job.state.lockedBy;
|
|
1031
|
-
|
|
1032
|
-
// Remove from active
|
|
1033
|
-
await redis.SREM(this.key("active", queue), jobId);
|
|
1034
|
-
|
|
1035
|
-
const maxAttempts = job.options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
1036
|
-
const hasMoreAttempts = job.state.attempts < maxAttempts;
|
|
1037
|
-
|
|
1038
|
-
// Emit stalled event
|
|
1039
|
-
await this.emit({
|
|
1040
|
-
type: "stalled",
|
|
1041
|
-
queue,
|
|
1042
|
-
jobId,
|
|
1043
|
-
timestamp: now,
|
|
1044
|
-
workerId,
|
|
1045
|
-
willRetry: hasMoreAttempts,
|
|
1046
|
-
});
|
|
1047
|
-
|
|
1048
|
-
if (hasMoreAttempts) {
|
|
1049
|
-
job.state.status = "waiting";
|
|
1050
|
-
job.state.lockedBy = undefined;
|
|
1051
|
-
job.state.lockedUntil = undefined;
|
|
1052
|
-
job.state.error = "Job stalled (worker timeout)";
|
|
1053
|
-
|
|
1054
|
-
await redis.HSET(this.key("job", queue, jobId), {
|
|
1055
|
-
state: JSON.stringify(job.state),
|
|
1056
|
-
});
|
|
1057
|
-
await redis.ZADD(this.key("waiting", queue), {
|
|
1058
|
-
score: job.options.priority ?? 0,
|
|
1059
|
-
value: jobId,
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
// Notify waiting workers
|
|
1063
|
-
await redis.LPUSH(this.notifyKey(queue), jobId);
|
|
1064
|
-
|
|
1065
|
-
this.log.warn(`Recovered stalled job ${jobId}`, { queue });
|
|
1066
|
-
|
|
1067
|
-
// Emit waiting event
|
|
1068
|
-
await this.emit({
|
|
1069
|
-
type: "waiting",
|
|
1070
|
-
queue,
|
|
1071
|
-
jobId,
|
|
1072
|
-
timestamp: now,
|
|
1073
|
-
job,
|
|
1074
|
-
});
|
|
1075
|
-
} else {
|
|
1076
|
-
job.state.status = "failed";
|
|
1077
|
-
job.state.failedAt = now;
|
|
1078
|
-
job.state.lockedBy = undefined;
|
|
1079
|
-
job.state.lockedUntil = undefined;
|
|
1080
|
-
job.state.error =
|
|
1081
|
-
"Job stalled (worker timeout) - max attempts exceeded";
|
|
1082
|
-
|
|
1083
|
-
const removeOnFail = job.options.removeOnFail;
|
|
1084
|
-
if (removeOnFail === true) {
|
|
1085
|
-
await redis.DEL(this.key("job", queue, jobId));
|
|
1086
|
-
} else {
|
|
1087
|
-
await redis.HSET(this.key("job", queue, jobId), {
|
|
1088
|
-
state: JSON.stringify(job.state),
|
|
1089
|
-
});
|
|
1090
|
-
await redis.LPUSH(this.key("failed", queue), jobId);
|
|
1091
|
-
|
|
1092
|
-
if (typeof removeOnFail === "number" && removeOnFail >= 0) {
|
|
1093
|
-
await this.cleanJobs(queue, "failed", { maxCount: removeOnFail });
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
this.log.warn(`Stalled job ${jobId} permanently failed`, { queue });
|
|
1098
|
-
|
|
1099
|
-
// Emit failed event
|
|
1100
|
-
await this.emit({
|
|
1101
|
-
type: "failed",
|
|
1102
|
-
queue,
|
|
1103
|
-
jobId,
|
|
1104
|
-
timestamp: now,
|
|
1105
|
-
error: job.state.error,
|
|
1106
|
-
attempts: job.state.attempts,
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
return stalledJobIds;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
public async cleanJobs(
|
|
1115
|
-
queue: string,
|
|
1116
|
-
status: "completed" | "failed",
|
|
1117
|
-
options?: QueueCleanOptions,
|
|
1118
|
-
): Promise<number> {
|
|
1119
|
-
const redis = this.redisProvider.publisher;
|
|
1120
|
-
const listKey = this.key(status, queue);
|
|
1121
|
-
const maxAge = options?.maxAge;
|
|
1122
|
-
const maxCount = options?.maxCount;
|
|
1123
|
-
|
|
1124
|
-
let removed = 0;
|
|
1125
|
-
|
|
1126
|
-
// Remove by age
|
|
1127
|
-
if (maxAge !== undefined) {
|
|
1128
|
-
const now = Date.now();
|
|
1129
|
-
const cutoff = now - maxAge;
|
|
1130
|
-
|
|
1131
|
-
const jobIds = await redis.LRANGE(listKey, 0, -1);
|
|
1132
|
-
for (const result of jobIds) {
|
|
1133
|
-
const jobId = result.toString();
|
|
1134
|
-
const job = await this.getJob(queue, jobId);
|
|
1135
|
-
if (!job) continue;
|
|
1136
|
-
|
|
1137
|
-
const timestamp =
|
|
1138
|
-
status === "completed" ? job.state.completedAt : job.state.failedAt;
|
|
1139
|
-
|
|
1140
|
-
if (timestamp && timestamp < cutoff) {
|
|
1141
|
-
await redis.LREM(listKey, 1, jobId);
|
|
1142
|
-
await redis.DEL(this.key("job", queue, jobId));
|
|
1143
|
-
removed++;
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
// Remove by count
|
|
1149
|
-
if (maxCount !== undefined) {
|
|
1150
|
-
const currentLen = await redis.LLEN(listKey);
|
|
1151
|
-
if (currentLen > maxCount) {
|
|
1152
|
-
// Get jobs to remove (oldest ones)
|
|
1153
|
-
const toRemove = await redis.LRANGE(listKey, maxCount, -1);
|
|
1154
|
-
for (const result of toRemove) {
|
|
1155
|
-
const jobId = result.toString();
|
|
1156
|
-
await redis.DEL(this.key("job", queue, jobId));
|
|
1157
|
-
removed++;
|
|
1158
|
-
}
|
|
1159
|
-
// Trim the list
|
|
1160
|
-
await redis.LTRIM(listKey, 0, maxCount - 1);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
return removed;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
public async removeJob(queue: string, jobId: string): Promise<void> {
|
|
1168
|
-
const redis = this.redisProvider.publisher;
|
|
1169
|
-
const job = await this.getJob(queue, jobId);
|
|
1170
|
-
if (!job) return;
|
|
1171
|
-
|
|
1172
|
-
const previousStatus = job.state.status;
|
|
1173
|
-
|
|
1174
|
-
// Remove from appropriate list
|
|
1175
|
-
switch (job.state.status) {
|
|
1176
|
-
case "waiting":
|
|
1177
|
-
await redis.ZREM(this.key("waiting", queue), jobId);
|
|
1178
|
-
break;
|
|
1179
|
-
case "delayed":
|
|
1180
|
-
await redis.ZREM(this.key("delayed", queue), jobId);
|
|
1181
|
-
break;
|
|
1182
|
-
case "active":
|
|
1183
|
-
await redis.SREM(this.key("active", queue), jobId);
|
|
1184
|
-
break;
|
|
1185
|
-
case "completed":
|
|
1186
|
-
await redis.LREM(this.key("completed", queue), 1, jobId);
|
|
1187
|
-
break;
|
|
1188
|
-
case "failed":
|
|
1189
|
-
await redis.LREM(this.key("failed", queue), 1, jobId);
|
|
1190
|
-
break;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
// Delete job data
|
|
1194
|
-
await redis.DEL(this.key("job", queue, jobId));
|
|
1195
|
-
|
|
1196
|
-
// Emit removed event
|
|
1197
|
-
await this.emit({
|
|
1198
|
-
type: "removed",
|
|
1199
|
-
queue,
|
|
1200
|
-
jobId,
|
|
1201
|
-
timestamp: Date.now(),
|
|
1202
|
-
previousStatus,
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
public cancelWaiters(): void {
|
|
1207
|
-
this.shouldStop = true;
|
|
1208
|
-
}
|
|
1209
|
-
}
|