{"_id":"@birtalanrobert/jobs","_rev":"7-5eed9f9c7fe732a7edcb0b183adf8760","name":"@birtalanrobert/jobs","dist-tags":{"latest":"1.2.0"},"versions":{"0.1.0":{"name":"@birtalanrobert/jobs","version":"0.1.0","license":"AGPL-3.0-only","_id":"@birtalanrobert/jobs@0.1.0","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"homepage":"https://github.com/birtalanrobert/mortar#readme","bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"dist":{"shasum":"87c148be0149fb31b559b88ea16077f297247c65","tarball":"https://registry.npmjs.org/@birtalanrobert/jobs/-/jobs-0.1.0.tgz","fileCount":49,"integrity":"sha512-OhMFOQAsIx86V9NUS5Rsfrkp40cyUOQq/Gql+XFxjcMS2N9CKPoMI4eC9UvvXbF0g4GPwTpbOhsrjlYhUHxO1A==","signatures":[{"sig":"MEUCIGItRss7eQ3UL+F5EKlnRNsxvFTKXhRCgJH0R34rPP7vAiEAlIQ0QE9xzrIwAPVPjv8aZhJy8Yvuj1o/zXAN6brdwfw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":129562},"main":"./dist/index.js","type":"commonjs","_from":"file:birtalanrobert-jobs-0.1.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json"},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/c975a4e1290d1ae13ae75b80d9681f34/birtalanrobert-jobs-0.1.0.tgz","_integrity":"sha512-OhMFOQAsIx86V9NUS5Rsfrkp40cyUOQq/Gql+XFxjcMS2N9CKPoMI4eC9UvvXbF0g4GPwTpbOhsrjlYhUHxO1A==","repository":{"url":"git+https://github.com/birtalanrobert/mortar.git","type":"git","directory":"packages/jobs"},"_npmVersion":"11.13.0","description":"BullMQ conventions, context propagation and the forward-window scanner","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@birtalanrobert/redis":"0.1.0","@birtalanrobert/config":"0.1.0","@birtalanrobert/context":"0.1.0","@birtalanrobert/observability":"0.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"bullmq":"^5.0.0 || ^6.0.0","@nestjs/common":"^10.0.0 || ^11.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/jobs_0.1.0_1787664425370_0.27838312368442875","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@birtalanrobert/jobs","version":"0.2.0","license":"AGPL-3.0-only","_id":"@birtalanrobert/jobs@0.2.0","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"homepage":"https://github.com/birtalanrobert/mortar#readme","bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"dist":{"shasum":"0cd667bd46393a47e110e6fa2a66cf5ae811e525","tarball":"https://registry.npmjs.org/@birtalanrobert/jobs/-/jobs-0.2.0.tgz","fileCount":50,"integrity":"sha512-FA3uorW9IKorSEZq+OcAkXIRL694nnnpwNUzsUFL8AUg4rQ32Frcg6kdNCv1ewZ41+P9WDDWdFD/ngmblkTI6w==","signatures":[{"sig":"MEYCIQDjD+o3COKIj/Qg2hm9oVXIp+IgLQlu/zHvSPReGtzD5gIhAP0F3DPVxQNS9oPlXHxqn04/QFwuMZo8QdQ0Z6dFN1ar","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":137189},"main":"./dist/index.js","type":"commonjs","_from":"file:birtalanrobert-jobs-0.2.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json"},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/1ac7308ed6f8232ce953b528c84f0718/birtalanrobert-jobs-0.2.0.tgz","_integrity":"sha512-FA3uorW9IKorSEZq+OcAkXIRL694nnnpwNUzsUFL8AUg4rQ32Frcg6kdNCv1ewZ41+P9WDDWdFD/ngmblkTI6w==","repository":{"url":"git+https://github.com/birtalanrobert/mortar.git","type":"git","directory":"packages/jobs"},"_npmVersion":"11.13.0","description":"BullMQ conventions, context propagation and the forward-window scanner","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@birtalanrobert/redis":"^0.2.0","@birtalanrobert/config":"^0.2.0","@birtalanrobert/context":"^0.2.0","@birtalanrobert/observability":"^0.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"bullmq":"^5.0.0 || ^6.0.0","@nestjs/common":"^10.0.0 || ^11.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/jobs_0.2.0_1787732735967_0.35273853783861275","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@birtalanrobert/jobs","version":"1.0.0","license":"AGPL-3.0-only","_id":"@birtalanrobert/jobs@1.0.0","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"homepage":"https://github.com/birtalanrobert/mortar#readme","bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"dist":{"shasum":"47961c8b1f195dadcc8a638af9bc62c84789c3ea","tarball":"https://registry.npmjs.org/@birtalanrobert/jobs/-/jobs-1.0.0.tgz","fileCount":50,"integrity":"sha512-ANpD36kpvNA4MipqWXX9ufaR0a6WXXXWrtofObRoR5W9weOUl/NDCyb1IbN68zS7OMJ/n6dT/HfoF3CJ4h0O1Q==","signatures":[{"sig":"MEUCIAMoZOyR307F/DiEYldLC1ij+PZw6qhgmNvKXjBiqgetAiEA+1rd2p6fkFVMkGDbZg5e8UzdkDQatfa2FSCTnLhTqg4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":140189},"main":"./dist/index.js","type":"commonjs","_from":"file:birtalanrobert-jobs-1.0.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json"},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/889c77d699cede3fc4f7a13dcfd39535/birtalanrobert-jobs-1.0.0.tgz","_integrity":"sha512-ANpD36kpvNA4MipqWXX9ufaR0a6WXXXWrtofObRoR5W9weOUl/NDCyb1IbN68zS7OMJ/n6dT/HfoF3CJ4h0O1Q==","repository":{"url":"git+https://github.com/birtalanrobert/mortar.git","type":"git","directory":"packages/jobs"},"_npmVersion":"11.13.0","description":"BullMQ conventions, context propagation and the forward-window scanner","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@birtalanrobert/redis":"^1.0.0","@birtalanrobert/config":"^1.0.0","@birtalanrobert/context":"^1.0.0","@birtalanrobert/observability":"^1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"bullmq":"^5.0.0 || ^6.0.0","@nestjs/common":"^10.0.0 || ^11.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/jobs_1.0.0_1787735914411_0.15886435268114973","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@birtalanrobert/jobs","version":"1.1.0","license":"AGPL-3.0-only","_id":"@birtalanrobert/jobs@1.1.0","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"homepage":"https://github.com/birtalanrobert/mortar#readme","bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"dist":{"shasum":"011c3ab2caf1c5da1a99d802037f7a35fe17c7cd","tarball":"https://registry.npmjs.org/@birtalanrobert/jobs/-/jobs-1.1.0.tgz","fileCount":50,"integrity":"sha512-C/m7oBoqysx0jWS23V9YrnK0Fzv6k86oKkCsbWO2a3TtfOYA+HRzyK9jvEBi/R6yh0tWg7ixznvmSbCDIWbP/A==","signatures":[{"sig":"MEUCIDdLUGlRcSrCULE+22uUglq+NmHPjpW7F63eEnqRZbn1AiEAh0fBPuGcC32pUs373gsyFYptvoyIbAYSzMThucBr/so=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":154384},"main":"./dist/index.js","type":"commonjs","_from":"file:birtalanrobert-jobs-1.1.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json"},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/075a81bcd9b427662d72a3150b2582fb/birtalanrobert-jobs-1.1.0.tgz","_integrity":"sha512-C/m7oBoqysx0jWS23V9YrnK0Fzv6k86oKkCsbWO2a3TtfOYA+HRzyK9jvEBi/R6yh0tWg7ixznvmSbCDIWbP/A==","repository":{"url":"git+https://github.com/birtalanrobert/mortar.git","type":"git","directory":"packages/jobs"},"_npmVersion":"11.13.0","description":"BullMQ conventions, context propagation and the forward-window scanner","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@birtalanrobert/redis":"^1.0.0","@birtalanrobert/config":"^1.0.0","@birtalanrobert/context":"^1.0.0","@birtalanrobert/observability":"^1.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"bullmq":"^5.0.0 || ^6.0.0","@nestjs/common":"^10.0.0 || ^11.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/jobs_1.1.0_1787737861681_0.5601766024347781","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@birtalanrobert/jobs","version":"1.1.1","license":"AGPL-3.0-only","_id":"@birtalanrobert/jobs@1.1.1","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"homepage":"https://github.com/birtalanrobert/mortar#readme","bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"dist":{"shasum":"70a4226d24e59210795f724052855660f58dc005","tarball":"https://registry.npmjs.org/@birtalanrobert/jobs/-/jobs-1.1.1.tgz","fileCount":50,"integrity":"sha512-jqH2MBuHfa9ZytdD3UE4BX0Xep+IOOyBUM9Q8xyTOzKJmZtm00pXcY39j9gNR7RnwsXeIWoeP9Am13HZG+hxBQ==","signatures":[{"sig":"MEYCIQDVb72+TTggS5PFw0495xsOVZ8+JQ72fxOQeVE+5RCycAIhAOcrjuzu3M29mzhuHfc8+J5QAF6gPruqdAgB8UwJK/Zv","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":182357},"main":"./dist/index.js","type":"commonjs","_from":"file:birtalanrobert-jobs-1.1.1.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json"},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/dc1066447bd25230ff650cd81f446468/birtalanrobert-jobs-1.1.1.tgz","_integrity":"sha512-jqH2MBuHfa9ZytdD3UE4BX0Xep+IOOyBUM9Q8xyTOzKJmZtm00pXcY39j9gNR7RnwsXeIWoeP9Am13HZG+hxBQ==","repository":{"url":"git+https://github.com/birtalanrobert/mortar.git","type":"git","directory":"packages/jobs"},"_npmVersion":"11.13.0","description":"BullMQ conventions, context propagation and the forward-window scanner","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@birtalanrobert/redis":"^1.0.1","@birtalanrobert/config":"^1.0.0","@birtalanrobert/context":"^1.1.0","@birtalanrobert/observability":"^1.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"bullmq":"^5.0.0 || ^6.0.0","@nestjs/common":"^10.0.0 || ^11.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/jobs_1.1.1_1788435059601_0.1940954874031886","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@birtalanrobert/jobs","version":"1.1.2","license":"AGPL-3.0-only","_id":"@birtalanrobert/jobs@1.1.2","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"homepage":"https://github.com/birtalanrobert/mortar#readme","bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"dist":{"shasum":"7a33ce67f7b7166b06b24b68de7f28d579bbe593","tarball":"https://registry.npmjs.org/@birtalanrobert/jobs/-/jobs-1.1.2.tgz","fileCount":50,"integrity":"sha512-5N7Fjh7yFZGw2U+1FKens0gdQzPqgAk0OfeiBCclDg2vuHhiWclW2bk+U0RCZkUNb133AdZWtDbBpiwxfEyNMg==","signatures":[{"sig":"MEQCIHajlFFPmgJNbd2fBl/k65p8HW8Hs5p7X18G4go8uCHHAiALB/3bgtMO/9tJYkeAyCgC39V9mP+1iE2o8ikwZJRgcQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDrq3qA0oHM4QFCf9xeNpsjRVK1jrCvd2SIzSUKG24+CAIhAI4qVyxWyXb3VpqxbGiDIAbVgEYBfjPqb8m77HWD6Q+e","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":232803},"main":"./dist/index.js","type":"commonjs","_from":"file:birtalanrobert-jobs-1.1.2.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json"},"_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/f4474dce842ac56f57940ed393f529b5/birtalanrobert-jobs-1.1.2.tgz","_integrity":"sha512-5N7Fjh7yFZGw2U+1FKens0gdQzPqgAk0OfeiBCclDg2vuHhiWclW2bk+U0RCZkUNb133AdZWtDbBpiwxfEyNMg==","repository":{"url":"git+https://github.com/birtalanrobert/mortar.git","type":"git","directory":"packages/jobs"},"_npmVersion":"11.13.0","description":"BullMQ conventions, context propagation and the forward-window scanner","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@birtalanrobert/redis":"^1.0.2","@birtalanrobert/config":"^1.1.0","@birtalanrobert/context":"^1.2.0","@birtalanrobert/observability":"^1.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"bullmq":"^5.0.0 || ^6.0.0","@nestjs/common":"^10.0.0 || ^11.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/jobs_1.1.2_1789574075816_0.24337123133044658","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"_id":"@birtalanrobert/jobs@1.2.0","bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"dist":{"shasum":"6975b4b74b269881d73508a461847ba8e8bcf878","tarball":"https://registry.npmjs.org/@birtalanrobert/jobs/-/jobs-1.2.0.tgz","fileCount":50,"integrity":"sha512-MRly9wGvPaH7SzChWC2UPplqEN7ma7ryEWLmopoXC9W/vnxEIG52SQLrczGoanxQfDAPSad/3RR0pvXOiZazcQ==","signatures":[{"sig":"MEQCIC07tWKCvL6TmWhwCkCNU+JyjYFIPMo1JA+x1q8+wSmXAiBWJh5py+5Wd+8Yzmi6PvwPkwIvZcxhOZIhkUlF8V4nzQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAMPY2Z5+322BfszO2IDzftassI3h2puaCwKhMBlVJltAiBDjU3LF9ePkLNUnrNH2A504Tbw3oiA/E8aPFQzIjee+w=="}],"unpackedSize":277336},"main":"./dist/index.js","name":"@birtalanrobert/jobs","type":"commonjs","_from":"file:birtalanrobert-jobs-1.2.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"license":"AGPL-3.0-only","scripts":{"build":"tsc -p tsconfig.build.json","clean":"rm -rf dist *.tsbuildinfo","typecheck":"tsc -p tsconfig.json"},"version":"1.2.0","_npmUser":{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"},"homepage":"https://github.com/birtalanrobert/mortar#readme","_resolved":"/private/var/folders/zx/7dcyg3mn6kjfyzsymzgpx1jr0000gn/T/df8e3390596a5372af860bbe850d4985/birtalanrobert-jobs-1.2.0.tgz","_integrity":"sha512-MRly9wGvPaH7SzChWC2UPplqEN7ma7ryEWLmopoXC9W/vnxEIG52SQLrczGoanxQfDAPSad/3RR0pvXOiZazcQ==","repository":{"url":"git+https://github.com/birtalanrobert/mortar.git","type":"git","directory":"packages/jobs"},"_npmVersion":"11.13.0","description":"BullMQ conventions, context propagation and the forward-window scanner","directories":{},"maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"_nodeVersion":"24.16.0","dependencies":{"@birtalanrobert/redis":"^1.1.1","@birtalanrobert/config":"^1.3.0","@birtalanrobert/context":"^1.2.0","@birtalanrobert/observability":"^1.5.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"bullmq":"^5.0.0 || ^6.0.0","@nestjs/common":"^10.0.0 || ^11.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/jobs_1.2.0_1790583212954_0.8480431584137818"}}},"time":{"created":"2026-08-25T13:27:05.009Z","modified":"2026-09-28T08:13:33.225Z","0.1.0":"2026-08-25T13:27:05.518Z","0.2.0":"2026-08-26T08:25:36.145Z","1.0.0":"2026-08-26T09:18:34.568Z","1.1.0":"2026-08-26T09:51:01.821Z","1.1.1":"2026-09-03T11:30:59.733Z","1.1.2":"2026-09-16T15:54:35.912Z","1.2.0":"2026-09-28T08:13:33.050Z"},"bugs":{"url":"https://github.com/birtalanrobert/mortar/issues"},"license":"AGPL-3.0-only","homepage":"https://github.com/birtalanrobert/mortar#readme","repository":{"url":"git+https://github.com/birtalanrobert/mortar.git","type":"git","directory":"packages/jobs"},"description":"BullMQ conventions, context propagation and the forward-window scanner","maintainers":[{"name":"birtalanrobert","email":"birtalanrobert@gmail.com"}],"readme":"# @birtalanrobert/jobs\n\nBackground jobs on BullMQ: typed definitions, workers, scheduled tasks and a\nforward-window scanner.\n\n## Using it in a NestJS application\n\n```ts\nimport { JobsModule } from '@birtalanrobert/jobs';\n\n@Module({\n  imports: [\n    ConfigModule.forRoot({ schema: envSchema }),\n    LoggerModule.forRootAsync({/* … */}),\n    DatabaseModule.forRootAsync({/* … */}),\n    RedisModule.forRootAsync({/* … */}),\n\n    JobsModule.forRootAsync({\n      inject: [ConfigModule.token(), AuditService],\n      useFactory: (config: AppConfig, audit: AuditService) => ({\n        redis: { url: config.REDIS_URL },\n        prefix: config.QUEUE_PREFIX,\n        concurrency: config.QUEUE_CONCURRENCY,\n        /**\n         * What happens when a job has exhausted every attempt. BullMQ keeps\n         * the failed job, but keeping it is not the same as anybody knowing.\n         */\n        onDeadLetter: async (job, error) => {\n          await audit.record({\n            action: 'job.dead_lettered',\n            entityType: 'job',\n            entityId: String(job.id),\n            metadata: { queue: job.queueName, error: error.message },\n          });\n        },\n      }),\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n`@Global()`. Register it after Redis, which it connects through.\n\n## Defining a job\n\nA definition is the shared vocabulary between whoever enqueues and whoever\nruns — the queue, the name, the retry policy and the payload type in one place.\n**Both sides import the same definition**; a copied contract eventually becomes\na different contract.\n\n```ts\nimport { defineJob } from '@birtalanrobert/jobs';\n\nexport const sendReminder = defineJob<{ reminderId: string; tenantId: string }>({\n  name: 'reminder.send',\n  queue: 'notifications',\n  // A stable id makes enqueuing idempotent: BullMQ ignores a second job with\n  // an id it already holds. No colons — BullMQ reserves them as key separators.\n  idFor: (payload) => `reminder-${payload.reminderId}`,\n  options: {\n    attempts: 5,\n    // Exponential, not fixed: the usual reason a notification fails is that a\n    // provider is briefly unwell, and a fixed interval is indistinguishable\n    // from hammering it.\n    backoff: { type: 'exponential', delay: 5_000 },\n  },\n});\n```\n\n## Enqueuing\n\n```ts\nconstructor(private readonly queues: JobQueues) {}\n\nawait this.queues.enqueue(sendReminder, { reminderId, tenantId });\n```\n\n## Handling\n\n```ts\n@Injectable()\nexport class RemindersHandlers implements OnApplicationBootstrap {\n  constructor(private readonly workers: JobWorkers) {}\n\n  onApplicationBootstrap() {\n    this.workers.register(sendReminder, async (payload) => {\n      /* … */\n    });\n  }\n}\n```\n\nThe runner records `job_duration_ms`, `jobs_total` labelled by outcome, and\n`jobs_dead_lettered_total` — in the runner rather than in each handler, because\nthose are properties of the runner and identical in every service.\n\n## Scheduled tasks\n\n```ts\nthis.scheduler.register({\n  name: 'reminders.purge',\n  intervalMs: 60 * 60 * 1000,\n  run: async () => this.queues.enqueue(purgeReminders, { olderThan }),\n  // On start, it runs only if this hour has not run anywhere in the fleet yet;\n  // without it, the first run is at the next hour.\n  runOnStart: false,\n});\n```\n\n`TaskScheduler` runs a task **once per interval across the fleet**. Intervals\nare counted from the epoch, so every replica ticks at the start of each one —\n12:00, 13:00, 14:00 for an hour — and the first to tick claims it. The claim is\nnever released, only left to expire, so a replica that reaches the same interval\nlater finds it taken; and a run still going when the next interval starts is not\njoined by a second. Keep the callback short — it enqueues rather than doing the\nwork.\n\n## The scanner\n\nFor work that comes due rather than work that is requested. A **forward\nwindow** — \"what falls due in the next few minutes\" — scanned periodically,\ninstead of one scheduled job per item.\n\nThe difference matters at scale: a million reminders is a million timers under\nthe second design, and one query under this one. It also survives a deadline\nmoving, which a scheduled timer does not.\n\n## Queues are concurrency pools\n\nGive housekeeping its own queue. Maintenance must never sit behind a backlog of\nnotifications, and notifications must never wait behind a slow sweep.\n","readmeFilename":"README.md"}