{"_id":"@ballistix.digital/task-scheduler","_rev":"11-f5d6f9df0fd560ad34916cc0845ed77f","name":"@ballistix.digital/task-scheduler","dist-tags":{"latest":"0.10.0"},"versions":{"0.1.0":{"name":"@ballistix.digital/task-scheduler","version":"0.1.0","license":"MIT","_id":"@ballistix.digital/task-scheduler@0.1.0","maintainers":[{"name":"brecht-p7s-ballistix","email":"brecht.pallemans@ballistix.digital"},{"name":"maartenraes","email":"maarten@ballistix.digital"},{"name":"jmostaer","email":"jens@ballistix.digital"},{"name":"aaron-ballistix","email":"aaron@ballistix.digital"},{"name":"aude-ballistix","email":"aude@ballistix.digital"}],"homepage":"https://github.com/layeredprints/task-scheduler#readme","bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"dist":{"shasum":"a1cb81976842b2e72aabce341326ecad3e7c8b87","tarball":"https://registry.npmjs.org/@ballistix.digital/task-scheduler/-/task-scheduler-0.1.0.tgz","fileCount":36,"integrity":"sha512-GIUXKH1wGL4UN0krT38oDaIQjG9XhJeEDanIbrwX3R+pDSBAg/frb/bvep30cE2neGo6xfbPjggydbVKzpvapg==","signatures":[{"sig":"MEQCIAmouLd3ewVyKktz2X9Rb75q76BYKrBQlC00NanMrIz7AiAFIdaPTin5nmFOgx2WLX3+nIGgdj8EyrnKgm3+qwjpZw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":66195},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}},"scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest --selectProjects unit integration --runInBand","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:unit":"jest --selectProjects unit","test:watch":"jest --selectProjects unit --watch","test:coverage":"jest --selectProjects unit integration --runInBand --coverage","test:packaging":"npm run build && jest --selectProjects packaging","test:integration":"jest --selectProjects integration --runInBand"},"_npmUser":{"name":"jmostaer","email":"jens@ballistix.digital"},"repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"_npmVersion":"11.12.1","description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","directories":{},"_nodeVersion":"24.15.0","dependencies":{"pg-boss":"^12.28.1"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"testing":["dist/testing/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.16.0","jest":"^30.0.0","rxjs":"^7.8.2","eslint":"^8.57.1","ts-jest":"^29.4.0","typeorm":"^0.3.27","prettier":"^3.6.0","@types/pg":"^8.15.0","typescript":"^5.9.0","@types/jest":"^30.0.0","@types/node":"^22.15.0","@nestjs/core":"^11.1.6","@nestjs/common":"^11.1.6","@nestjs/testing":"^11.1.6","reflect-metadata":"^0.2.2","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.5.0","@typescript-eslint/parser":"^8.44.0","@typescript-eslint/eslint-plugin":"^8.44.0"},"peerDependencies":{"typeorm":"^0.3","@nestjs/core":"^11","@nestjs/common":"^11","reflect-metadata":"^0.2"},"_npmOperationalInternal":{"tmp":"tmp/task-scheduler_0.1.0_1788805351678_0.2846553441705102","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@ballistix.digital/task-scheduler","version":"0.2.0","license":"MIT","_id":"@ballistix.digital/task-scheduler@0.2.0","maintainers":[{"name":"brecht-p7s-ballistix","email":"brecht.pallemans@ballistix.digital"},{"name":"maartenraes","email":"maarten@ballistix.digital"},{"name":"jmostaer","email":"jens@ballistix.digital"},{"name":"aaron-ballistix","email":"aaron@ballistix.digital"},{"name":"aude-ballistix","email":"aude@ballistix.digital"}],"homepage":"https://github.com/layeredprints/task-scheduler#readme","bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"dist":{"shasum":"aedd46b5743b18467dedfc1deac05609dab97eae","tarball":"https://registry.npmjs.org/@ballistix.digital/task-scheduler/-/task-scheduler-0.2.0.tgz","fileCount":34,"integrity":"sha512-3DEuEnob43jcvwoj5w9nxWFM9iG19eljtKlOrc9Yro4U8qHm44ouhZzMBZSenwPnW5yBLR2eOjdJXnaSGE7k6A==","signatures":[{"sig":"MEUCIFHnJC4sTmT1XqDr6e4MCZJlvEj9om73fUDSzaugIrJXAiEAgZMRpepeqSAmDOtcbCdzcg7QdSkJ9Ri8mbVEd4oAgSk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIGbDlVtGMGeZuAOalwrSUf4r4p/KnA6KFcorSIWPLRwwAiEAl9uLd03i150H3iFCGy9nWPYbVsD3M/0z0MUizXgvte0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":64477},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}},"gitHead":"652ae890bad15e1ce473927e625b9a95a157716d","scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest --selectProjects unit integration --runInBand","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:unit":"jest --selectProjects unit","test:watch":"jest --selectProjects unit --watch","test:coverage":"jest --selectProjects unit integration --runInBand --coverage","test:packaging":"npm run build && jest --selectProjects packaging","test:integration":"jest --selectProjects integration --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:38a6b5fb-b777-4232-8d00-e8e41175945f"}},"repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"_npmVersion":"12.0.2","description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"pg-boss":"^12.28.1"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"testing":["dist/testing/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.16.0","jest":"^30.0.0","rxjs":"^7.8.2","luxon":"^3.7.2","eslint":"^8.57.1","lodash":"^4.17.21","ts-jest":"^29.4.0","typeorm":"^1.1.0","prettier":"^3.6.0","@types/pg":"^8.15.0","typescript":"^5.9.0","@types/jest":"^30.0.0","@types/node":"^22.15.0","@nestjs/core":"^11.1.6","@nestjs/common":"^11.1.6","@nestjs/swagger":"^11.0.6","@nestjs/testing":"^11.1.6","class-validator":"0.15.1","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.5.0","@typescript-eslint/parser":"^8.44.0","@typescript-eslint/eslint-plugin":"^8.44.0","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"peerDependencies":{"typeorm":">=0.3.0 <2.0.0","@nestjs/core":"^11","@nestjs/common":"^11","reflect-metadata":"^0.2","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/task-scheduler_0.2.0_1788872022273_0.5814465934553332","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@ballistix.digital/task-scheduler","version":"0.3.0","license":"MIT","_id":"@ballistix.digital/task-scheduler@0.3.0","maintainers":[{"name":"brecht-p7s-ballistix","email":"brecht.pallemans@ballistix.digital"},{"name":"maartenraes","email":"maarten@ballistix.digital"},{"name":"jmostaer","email":"jens@ballistix.digital"},{"name":"aaron-ballistix","email":"aaron@ballistix.digital"},{"name":"aude-ballistix","email":"aude@ballistix.digital"}],"homepage":"https://github.com/layeredprints/task-scheduler#readme","bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"dist":{"shasum":"0317aca1af2a4d0c6a29de585b28137d0800abc2","tarball":"https://registry.npmjs.org/@ballistix.digital/task-scheduler/-/task-scheduler-0.3.0.tgz","fileCount":40,"integrity":"sha512-acKvYicuVh4vd/9PJq6RA2hunVJWFj1rRUUzFn4tuEsPAzqbob+lEE3qshlCaN5GSSvg6dmsqlJPUm1LRtZAvA==","signatures":[{"sig":"MEQCIBz5Lx5Ys4O4KYnj0b6U57Yqft6zd0wLnzarPcum96cnAiBYs2fetYMjL1VyS1HiWxbd41LJpdOHMx3vq7RbylB44w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQC2vVtmy/gCdMtHkn3MvHKB6hAOhTJ9JT1Zy7uA0i1lxAIhAPj2pudUv4ka7DGvuYLAq8Nw7tJaaSjH5xVFwlKgs94f","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":77060},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}},"gitHead":"2b6cb3730c6bbe82b5074be45c584e0844bec982","scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest --selectProjects unit integration --runInBand","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:unit":"jest --selectProjects unit","test:watch":"jest --selectProjects unit --watch","test:coverage":"jest --selectProjects unit integration --runInBand --coverage","test:packaging":"npm run build && jest --selectProjects packaging","test:integration":"jest --selectProjects integration --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:38a6b5fb-b777-4232-8d00-e8e41175945f"}},"repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"_npmVersion":"12.0.2","description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"pg-boss":"^12.28.1"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"testing":["dist/testing/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.16.0","jest":"^30.0.0","rxjs":"^7.8.2","luxon":"^3.7.2","eslint":"^8.57.1","lodash":"^4.17.21","ts-jest":"^29.4.0","typeorm":"^1.1.0","prettier":"^3.6.0","@types/pg":"^8.15.0","typescript":"^5.9.0","@types/jest":"^30.0.0","@types/node":"^22.15.0","@nestjs/core":"^11.1.6","@nestjs/common":"^11.1.6","@nestjs/swagger":"^11.0.6","@nestjs/testing":"^11.1.6","class-validator":"0.15.1","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.5.0","@typescript-eslint/parser":"^8.44.0","@typescript-eslint/eslint-plugin":"^8.44.0","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"peerDependencies":{"typeorm":">=0.3.0 <2.0.0","@nestjs/core":"^11","@nestjs/common":"^11","reflect-metadata":"^0.2","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/task-scheduler_0.3.0_1788879097292_0.22694261281771144","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@ballistix.digital/task-scheduler","version":"0.4.0","license":"MIT","_id":"@ballistix.digital/task-scheduler@0.4.0","maintainers":[{"name":"brecht-p7s-ballistix","email":"brecht.pallemans@ballistix.digital"},{"name":"maartenraes","email":"maarten@ballistix.digital"},{"name":"jmostaer","email":"jens@ballistix.digital"},{"name":"aaron-ballistix","email":"aaron@ballistix.digital"},{"name":"aude-ballistix","email":"aude@ballistix.digital"}],"homepage":"https://github.com/layeredprints/task-scheduler#readme","bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"dist":{"shasum":"d61c13cbfb3a4e3ad1e6ce7b0372a701800fdeaf","tarball":"https://registry.npmjs.org/@ballistix.digital/task-scheduler/-/task-scheduler-0.4.0.tgz","fileCount":42,"integrity":"sha512-yGSkq6rxOCSNZme25cklEoByL4jGYaWBQ8FNrUm9zVdkpjuZ6/d30PaYqgpamzf7qn4pEftBOvgt1htSVdBY7Q==","signatures":[{"sig":"MEYCIQDPCSvQcmEQzkEwgLWcxoxK57Lbl9K90GAr84Hr7hTFmgIhAMgqjqD3pQsZTsLfxvnBuTBpgwRHi30lQXrvNPtmJwd0","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQD5taoUAernDCALHFNrAUh6u9XRpab9cem8ULRs03dPUgIgBQT9V4CN0Owwljwu+mC3+ikSc1obQ2Ij6RIQewWmyQ0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":78469},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./pg-boss":{"types":"./dist/module/pgBoss/index.d.ts","default":"./dist/module/pgBoss/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}},"gitHead":"c5796231dd6d12d1d94bc859698c4f034167cd40","scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest --selectProjects unit integration --runInBand","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:unit":"jest --selectProjects unit","test:watch":"jest --selectProjects unit --watch","test:coverage":"jest --selectProjects unit integration --runInBand --coverage","test:packaging":"npm run build && jest --selectProjects packaging","test:integration":"jest --selectProjects integration --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:38a6b5fb-b777-4232-8d00-e8e41175945f"}},"repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"_npmVersion":"12.0.2","description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"pg-boss":["dist/module/pgBoss/index.d.ts"],"testing":["dist/testing/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.16.0","jest":"^30.0.0","rxjs":"^7.8.2","luxon":"^3.7.2","eslint":"^8.57.1","lodash":"^4.17.21","pg-boss":"^12.28.1","ts-jest":"^29.4.0","typeorm":"^1.1.0","prettier":"^3.6.0","@types/pg":"^8.15.0","typescript":"^5.9.0","@types/jest":"^30.0.0","@types/node":"^22.15.0","@nestjs/core":"^11.1.6","@nestjs/common":"^11.1.6","@nestjs/swagger":"^11.0.6","@nestjs/testing":"^11.1.6","class-validator":"0.15.1","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.5.0","@typescript-eslint/parser":"^8.44.0","@typescript-eslint/eslint-plugin":"^8.44.0","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"peerDependencies":{"pg-boss":"^12","typeorm":">=0.3.0 <2.0.0","@nestjs/core":"^11","@nestjs/common":"^11","reflect-metadata":"^0.2","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"peerDependenciesMeta":{"pg-boss":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/task-scheduler_0.4.0_1788884037640_0.7223083269662092","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@ballistix.digital/task-scheduler","version":"0.5.0","license":"MIT","_id":"@ballistix.digital/task-scheduler@0.5.0","maintainers":[{"name":"brecht-p7s-ballistix","email":"brecht.pallemans@ballistix.digital"},{"name":"maartenraes","email":"maarten@ballistix.digital"},{"name":"jmostaer","email":"jens@ballistix.digital"},{"name":"aaron-ballistix","email":"aaron@ballistix.digital"},{"name":"aude-ballistix","email":"aude@ballistix.digital"}],"homepage":"https://github.com/layeredprints/task-scheduler#readme","bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"dist":{"shasum":"e62f47e1844ebadd760d42e4a43250304bb8bf6b","tarball":"https://registry.npmjs.org/@ballistix.digital/task-scheduler/-/task-scheduler-0.5.0.tgz","fileCount":43,"integrity":"sha512-t2bNxNoMMzt7McilfuwqO9r0y32eKkyy0B13YefM5AVTQv4XIJqR8ZZg9Vci56Xzmwpz4nq/xF6DqzRJoih6nQ==","signatures":[{"sig":"MEUCIQDR7ogLdeBy/cAThZn4dqHfk0RTi/6our89OLYdwE0H6QIgVufNa8DtiZrR4MorF88Cowrjzd5Nqvx9Ku5SaGNkg24=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIAPXhHiC851ip/rTBNoWwQlIWpIkIdfnLWu9+fLh06aoAiBt4g+LjO6V4VxKiVnJfccHHDuby+GR/CNb5u+ebSm/sw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":81689},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./types":{"types":"./dist/types/index.d.ts","default":"./dist/types/index.js"},"./pg-boss":{"types":"./dist/module/pgBoss/index.d.ts","default":"./dist/module/pgBoss/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}},"gitHead":"af1c0626234b9f1abd63e8bcc6e292c283c0830a","scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest --selectProjects unit integration --runInBand","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:unit":"jest --selectProjects unit","test:watch":"jest --selectProjects unit --watch","test:coverage":"jest --selectProjects unit integration --runInBand --coverage","test:packaging":"npm run build && jest --selectProjects packaging","test:integration":"jest --selectProjects integration --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:38a6b5fb-b777-4232-8d00-e8e41175945f"}},"repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"_npmVersion":"12.0.2","description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"types":["dist/types/index.d.ts"],"pg-boss":["dist/module/pgBoss/index.d.ts"],"testing":["dist/testing/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.16.0","jest":"^30.0.0","rxjs":"^7.8.2","luxon":"^3.7.2","eslint":"^8.57.1","lodash":"^4.17.21","pg-boss":"^12.28.1","ts-jest":"^29.4.0","typeorm":"^1.1.0","prettier":"^3.6.0","@types/pg":"^8.15.0","typescript":"^5.9.0","@types/jest":"^30.0.0","@types/node":"^22.15.0","@nestjs/core":"^11.1.6","@nestjs/common":"^11.1.6","@nestjs/swagger":"^11.0.6","@nestjs/testing":"^11.1.6","class-validator":"0.15.1","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.5.0","@typescript-eslint/parser":"^8.44.0","@typescript-eslint/eslint-plugin":"^8.44.0","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"peerDependencies":{"pg-boss":"^12","typeorm":">=0.3.0 <2.0.0","@nestjs/core":"^11","@nestjs/common":"^11","reflect-metadata":"^0.2","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"peerDependenciesMeta":{"pg-boss":{"optional":true},"typeorm":{"optional":true},"@nestjs/core":{"optional":true},"@nestjs/common":{"optional":true},"reflect-metadata":{"optional":true},"@ballistix.digital/exception-types":{"optional":true},"@ballistix.digital/exception-mapper":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/task-scheduler_0.5.0_1789135002181_0.26121380522601867","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@ballistix.digital/task-scheduler","version":"0.6.0","license":"MIT","_id":"@ballistix.digital/task-scheduler@0.6.0","maintainers":[{"name":"brecht-p7s-ballistix","email":"brecht.pallemans@ballistix.digital"},{"name":"maartenraes","email":"maarten@ballistix.digital"},{"name":"jmostaer","email":"jens@ballistix.digital"},{"name":"aaron-ballistix","email":"aaron@ballistix.digital"},{"name":"aude-ballistix","email":"aude@ballistix.digital"}],"homepage":"https://github.com/layeredprints/task-scheduler#readme","bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"dist":{"shasum":"b186a8c8e1238077df0f08aad1af3953252efbfb","tarball":"https://registry.npmjs.org/@ballistix.digital/task-scheduler/-/task-scheduler-0.6.0.tgz","fileCount":43,"integrity":"sha512-9zjVOOpnkExUzW5xutRZwNpPCg0pcK/mV9xW2+6T79kVtszxw9q/vIw39D61UxO8nDaUlgAA2j3nkJmDuACyNw==","signatures":[{"sig":"MEQCIGzLQGrbgJpbeJPFQzXgXS7ydStDq6/5bMqRywkGNpDAAiBBrqtWbTYzuDNOIZ+95g1GVdrAmLnwt3r05eW3EOSMQw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQD58iv4Z1chCFZKuhg4JJEUGz03ItcwmfC4xGmSQe+2SgIgKmT2SWcf+ulU46moTX1qNnHBGBLOO5gCSM0/NXpq4q4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84525},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./types":{"types":"./dist/types/index.d.ts","default":"./dist/types/index.js"},"./dist/*":{"types":"./dist/*.d.ts","default":"./dist/*.js"},"./pg-boss":{"types":"./dist/module/pgBoss/index.d.ts","default":"./dist/module/pgBoss/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}},"gitHead":"2f85c88345f2f67fa8eb71945177e121f939d82d","scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest --selectProjects unit integration --runInBand","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:unit":"jest --selectProjects unit","test:watch":"jest --selectProjects unit --watch","test:coverage":"jest --selectProjects unit integration --runInBand --coverage","test:packaging":"npm run build && jest --selectProjects packaging","test:integration":"jest --selectProjects integration --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:38a6b5fb-b777-4232-8d00-e8e41175945f"}},"repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"_npmVersion":"12.0.2","description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"types":["dist/types/index.d.ts"],"pg-boss":["dist/module/pgBoss/index.d.ts"],"testing":["dist/testing/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.16.0","jest":"^30.0.0","rxjs":"^7.8.2","luxon":"^3.7.2","eslint":"^8.57.1","lodash":"^4.17.21","pg-boss":"^12.28.1","ts-jest":"^29.4.0","typeorm":"^1.1.0","prettier":"^3.6.0","@types/pg":"^8.15.0","typescript":"^5.9.0","@types/jest":"^30.0.0","@types/node":"^22.15.0","@nestjs/core":"^11.1.6","@nestjs/common":"^11.1.6","@nestjs/swagger":"^11.0.6","@nestjs/testing":"^11.1.6","class-validator":"0.15.1","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.5.0","@typescript-eslint/parser":"^8.44.0","@typescript-eslint/eslint-plugin":"^8.44.0","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"peerDependencies":{"pg-boss":"^12","typeorm":">=0.3.0 <2.0.0","@nestjs/core":"^11","@nestjs/common":"^11","reflect-metadata":"^0.2","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"peerDependenciesMeta":{"pg-boss":{"optional":true},"typeorm":{"optional":true},"@nestjs/core":{"optional":true},"@nestjs/common":{"optional":true},"reflect-metadata":{"optional":true},"@ballistix.digital/exception-types":{"optional":true},"@ballistix.digital/exception-mapper":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/task-scheduler_0.6.0_1789137555691_0.39169632970067325","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@ballistix.digital/task-scheduler","version":"0.7.0","license":"MIT","_id":"@ballistix.digital/task-scheduler@0.7.0","maintainers":[{"name":"brecht-p7s-ballistix","email":"brecht.pallemans@ballistix.digital"},{"name":"maartenraes","email":"maarten@ballistix.digital"},{"name":"jmostaer","email":"jens@ballistix.digital"},{"name":"aaron-ballistix","email":"aaron@ballistix.digital"},{"name":"aude-ballistix","email":"aude@ballistix.digital"}],"homepage":"https://github.com/layeredprints/task-scheduler#readme","bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"dist":{"shasum":"e6e947a5566224eee086c4f55233fcd825de2549","tarball":"https://registry.npmjs.org/@ballistix.digital/task-scheduler/-/task-scheduler-0.7.0.tgz","fileCount":43,"integrity":"sha512-p3QB3ANpfO3GWLjVBNV1fel+L3Sc+A0ZOdjGd3pQ4SrOYzxY6yrG03oPHTzoWYvcBIAhS3OUOh0fft3yyjTFgg==","signatures":[{"sig":"MEUCIH1/cTdu3qVqXIJ7H7h30F2Ga62iwfIxRC1YUgzsCdj+AiEAkYk9Yh8OZor3nIqd/9XUIVSDNk5XEEuFabMUMCOs/XM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIARMr0lgyqVGJvy10o+iiLL6pu8hKIxys6LSHblNg05/AiAqyoKU2/k9TNLtTIhLZMKe0kwE4Rv04j+A+ZDtxzPJ8A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84559},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./types":{"types":"./dist/types/index.d.ts","default":"./dist/types/index.js"},"./dist/*":{"types":"./dist/*.d.ts","default":"./dist/*.js"},"./pg-boss":{"types":"./dist/module/pgBoss/index.d.ts","default":"./dist/module/pgBoss/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}},"gitHead":"d483206ea88d1446b2db0e8044815be0b86ee319","scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest --selectProjects unit integration --runInBand","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:unit":"jest --selectProjects unit","test:watch":"jest --selectProjects unit --watch","test:coverage":"jest --selectProjects unit integration --runInBand --coverage","test:packaging":"npm run build && jest --selectProjects packaging","test:integration":"jest --selectProjects integration --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:38a6b5fb-b777-4232-8d00-e8e41175945f"}},"repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"_npmVersion":"12.0.2","description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"types":["dist/types/index.d.ts"],"pg-boss":["dist/module/pgBoss/index.d.ts"],"testing":["dist/testing/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.16.0","jest":"^30.0.0","rxjs":"^7.8.2","luxon":"^3.7.2","eslint":"^8.57.1","lodash":"^4.17.21","pg-boss":"^12.28.1","ts-jest":"^29.4.0","typeorm":"^1.1.0","prettier":"^3.6.0","@types/pg":"^8.15.0","typescript":"^5.9.0","@types/jest":"^30.0.0","@types/node":"^22.15.0","@nestjs/core":"^11.1.6","@nestjs/common":"^11.1.6","@nestjs/swagger":"^11.0.6","@nestjs/testing":"^11.1.6","class-validator":"0.15.1","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.5.0","@typescript-eslint/parser":"^8.44.0","@typescript-eslint/eslint-plugin":"^8.44.0","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"peerDependencies":{"pg-boss":"^12","typeorm":">=0.3.0 <2.0.0","@nestjs/core":"^11 || ^12","@nestjs/common":"^11 || ^12","reflect-metadata":"^0.2","@ballistix.digital/exception-types":"^0.1.0 || ^0.2.0","@ballistix.digital/exception-mapper":"^0.1.0 || ^0.2.0"},"peerDependenciesMeta":{"pg-boss":{"optional":true},"typeorm":{"optional":true},"@nestjs/core":{"optional":true},"@nestjs/common":{"optional":true},"reflect-metadata":{"optional":true},"@ballistix.digital/exception-types":{"optional":true},"@ballistix.digital/exception-mapper":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/task-scheduler_0.7.0_1789398287150_0.8271115554535251","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"@ballistix.digital/task-scheduler","version":"0.8.0","license":"MIT","_id":"@ballistix.digital/task-scheduler@0.8.0","maintainers":[{"name":"brecht-p7s-ballistix","email":"brecht.pallemans@ballistix.digital"},{"name":"maartenraes","email":"maarten@ballistix.digital"},{"name":"jmostaer","email":"jens@ballistix.digital"},{"name":"aaron-ballistix","email":"aaron@ballistix.digital"},{"name":"aude-ballistix","email":"aude@ballistix.digital"}],"homepage":"https://github.com/layeredprints/task-scheduler#readme","bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"dist":{"shasum":"7a54c39d21d0e400c93d352d5adde597a349b4d9","tarball":"https://registry.npmjs.org/@ballistix.digital/task-scheduler/-/task-scheduler-0.8.0.tgz","fileCount":43,"integrity":"sha512-YSeOmcTN8hUgNpuKY5+9jYohNX7AbpawAYqRexaE8bhhIlN/QjuzfmLqFAXxZXDImtgTRskd4NP13Kqm9PcePQ==","signatures":[{"sig":"MEUCIQDyOFUf32IjMBE9X/VHlT21riDLr/5rM7+PTIAsnB0Y0wIgCG7p6HevcEU2Z8Qgar9QH1+LNhUYc3hh1rqAn+LvpWg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIEYVXBdR5ARSh2uakmQktnVQyw2DP9JBQTpFF/7TC7KSAiEAlIyag8fZmrIy6olomlvG8UAOKJqxq8d5shnyjzausL0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":85320},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./types":{"types":"./dist/types/index.d.ts","default":"./dist/types/index.js"},"./dist/*":{"types":"./dist/*.d.ts","default":"./dist/*.js"},"./pg-boss":{"types":"./dist/module/pgBoss/index.d.ts","default":"./dist/module/pgBoss/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}},"gitHead":"47e995cea977aa5ee1bceae5b8f7051e822dab44","scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest --selectProjects unit integration --runInBand","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:unit":"jest --selectProjects unit","test:watch":"jest --selectProjects unit --watch","test:coverage":"jest --selectProjects unit integration --runInBand --coverage","test:packaging":"npm run build && jest --selectProjects packaging","test:integration":"jest --selectProjects integration --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:38a6b5fb-b777-4232-8d00-e8e41175945f"}},"repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"_npmVersion":"12.0.2","description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"types":["dist/types/index.d.ts"],"pg-boss":["dist/module/pgBoss/index.d.ts"],"testing":["dist/testing/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.16.0","jest":"^30.0.0","rxjs":"^7.8.2","luxon":"^3.7.2","eslint":"^8.57.1","lodash":"^4.17.21","pg-boss":"^12.28.1","ts-jest":"^29.4.0","typeorm":"^1.1.0","prettier":"^3.6.0","@types/pg":"^8.15.0","typescript":"^5.9.0","@types/jest":"^30.0.0","@types/node":"^22.15.0","@nestjs/core":"^11.1.6","@nestjs/common":"^11.1.6","@nestjs/swagger":"^11.0.6","@nestjs/testing":"^11.1.6","class-validator":"0.15.1","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.5.0","@typescript-eslint/parser":"^8.44.0","@typescript-eslint/eslint-plugin":"^8.44.0","@ballistix.digital/exception-types":"^0.1.0","@ballistix.digital/exception-mapper":"^0.1.0"},"peerDependencies":{"pg-boss":"^12","typeorm":">=0.3.0 <2.0.0","@nestjs/core":"^11 || ^12","@nestjs/common":"^11 || ^12","reflect-metadata":"^0.2","@ballistix.digital/exception-types":"^0.1.0 || ^0.2.0","@ballistix.digital/exception-mapper":"^0.1.0 || ^0.2.0"},"peerDependenciesMeta":{"pg-boss":{"optional":true},"typeorm":{"optional":true},"@nestjs/core":{"optional":true},"@nestjs/common":{"optional":true},"reflect-metadata":{"optional":true},"@ballistix.digital/exception-types":{"optional":true},"@ballistix.digital/exception-mapper":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/task-scheduler_0.8.0_1789481967173_0.681484140748825","host":"s3://npm-registry-packages-npm-production"}},"0.9.0":{"name":"@ballistix.digital/task-scheduler","version":"0.9.0","license":"MIT","_id":"@ballistix.digital/task-scheduler@0.9.0","maintainers":[{"name":"brecht-p7s-ballistix","email":"brecht.pallemans@ballistix.digital"},{"name":"maartenraes","email":"maarten@ballistix.digital"},{"name":"jmostaer","email":"jens@ballistix.digital"},{"name":"aaron-ballistix","email":"aaron@ballistix.digital"},{"name":"aude-ballistix","email":"aude@ballistix.digital"}],"homepage":"https://github.com/layeredprints/task-scheduler#readme","bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"dist":{"shasum":"b2cfdfed8abe6a20d5159f4bc80879a266855d44","tarball":"https://registry.npmjs.org/@ballistix.digital/task-scheduler/-/task-scheduler-0.9.0.tgz","fileCount":67,"integrity":"sha512-OjRrWohrijv+xRkJJn0CVZrebk0qxOZmjySSJ+35N2W2qKmpB/H0HLjqFPsrJJLO1z2VbXbo/13fZU/Ti7BeKw==","signatures":[{"sig":"MEQCIFU/TW3QVoQn2WIowGAutKR0lUEyTRYAsBF8wfaTdOrrAiAYneLsKv3ZdCuN3OTsq6caNyp30OleCsEq50KXeGUY3w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCa1LmxsHaM3RBHafbxOqYJ/ttItE867UcDScin+hChrQIgYAzWOSzQe2TmfQCfKbKu1u0x5pqr/22rhUtk7PlqXJo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":115692},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./types":{"types":"./dist/types/index.d.ts","default":"./dist/types/index.js"},"./dist/*":{"types":"./dist/*.d.ts","default":"./dist/*.js"},"./pg-boss":{"types":"./dist/module/pgBoss/index.d.ts","default":"./dist/module/pgBoss/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}},"gitHead":"e20903e67b1f12449812ce8c607b9cd9159327a0","scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest --selectProjects unit integration --runInBand","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:unit":"jest --selectProjects unit","test:watch":"jest --selectProjects unit --watch","test:coverage":"jest --selectProjects unit integration --runInBand --coverage","test:packaging":"npm run build && jest --selectProjects packaging","test:integration":"jest --selectProjects integration --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:38a6b5fb-b777-4232-8d00-e8e41175945f"}},"repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"_npmVersion":"12.0.2","description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"types":["dist/types/index.d.ts"],"pg-boss":["dist/module/pgBoss/index.d.ts"],"testing":["dist/testing/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.16.0","jest":"^30.0.0","rxjs":"^7.8.2","luxon":"^3.7.2","eslint":"^8.57.1","lodash":"^4.17.21","pg-boss":"^12.28.1","ts-jest":"^29.4.0","ts-node":"^10.9.2","typeorm":"^1.1.0","prettier":"^3.6.0","@types/pg":"^8.15.0","typescript":"^5.9.0","@types/jest":"^30.0.0","@types/node":"^22.15.0","@nestjs/core":"^11.1.6","@nestjs/common":"^11.1.6","@nestjs/swagger":"^11.0.6","@nestjs/testing":"^11.1.6","class-validator":"0.15.1","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.5.0","@typescript-eslint/parser":"^8.44.0","@typescript-eslint/eslint-plugin":"^8.44.0","@ballistix.digital/exception-types":"^0.4.0","@ballistix.digital/exception-mapper":"^0.4.0"},"peerDependencies":{"pg-boss":"^12","typeorm":">=0.3.0 <2.0.0","@nestjs/core":"^11 || ^12","@nestjs/common":"^11 || ^12","reflect-metadata":"^0.2","@ballistix.digital/exception-types":"^0.4.0","@ballistix.digital/exception-mapper":"^0.3.0 || ^0.4.0"},"peerDependenciesMeta":{"pg-boss":{"optional":true},"typeorm":{"optional":true},"@nestjs/core":{"optional":true},"@nestjs/common":{"optional":true},"reflect-metadata":{"optional":true},"@ballistix.digital/exception-types":{"optional":true},"@ballistix.digital/exception-mapper":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/task-scheduler_0.9.0_1789578347289_0.6333210835244869","host":"s3://npm-registry-packages-npm-production"}},"0.10.0":{"name":"@ballistix.digital/task-scheduler","version":"0.10.0","license":"MIT","_id":"@ballistix.digital/task-scheduler@0.10.0","maintainers":[{"name":"brecht-p7s-ballistix","email":"brecht.pallemans@ballistix.digital"},{"name":"maartenraes","email":"maarten@ballistix.digital"},{"name":"jmostaer","email":"jens@ballistix.digital"},{"name":"aaron-ballistix","email":"aaron@ballistix.digital"},{"name":"aude-ballistix","email":"aude@ballistix.digital"}],"homepage":"https://github.com/layeredprints/task-scheduler#readme","bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"dist":{"shasum":"54448ad9e74861c62894d84c32f4021037545a07","tarball":"https://registry.npmjs.org/@ballistix.digital/task-scheduler/-/task-scheduler-0.10.0.tgz","fileCount":67,"integrity":"sha512-CZDtwp8D+jTxQyCkL6hu/Q12ZtpWRloVIQncjsy2m23+cj24wxQxugQheD8OEHfnwGmc+f71HYUBxwwWzzniJA==","signatures":[{"sig":"MEYCIQCkJngkDUA72/MRLifSGNg0SBxRsDlgI8taPDEEOUahGwIhAMWw/Kgk3hh+6pZNkiPg7NGyErbfCy+IGtIEpF6mdkNH","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQD9ZiEOoh/cP00NBql5flxU1/86LxYx84RN3oDFIrACtgIgGdSsQ6cQlJcNkPhA1m4BJH2x5mmjRz060e3dG7stIBA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":115699},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./types":{"types":"./dist/types/index.d.ts","default":"./dist/types/index.js"},"./dist/*":{"types":"./dist/*.d.ts","default":"./dist/*.js"},"./pg-boss":{"types":"./dist/module/pgBoss/index.d.ts","default":"./dist/module/pgBoss/index.js"},"./testing":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.js"}},"gitHead":"d8d7f59605ad68c7e5b186ab44d2938824bf516e","scripts":{"lint":"eslint \"src/**/*.ts\" \"test/**/*.ts\"","test":"jest --selectProjects unit integration --runInBand","build":"rm -rf dist && tsc -p tsconfig.build.json","format":"prettier --write \"src/**/*.ts\" \"test/**/*.ts\"","test:unit":"jest --selectProjects unit","test:watch":"jest --selectProjects unit --watch","test:coverage":"jest --selectProjects unit integration --runInBand --coverage","test:packaging":"npm run build && jest --selectProjects packaging","test:integration":"jest --selectProjects integration --runInBand"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:38a6b5fb-b777-4232-8d00-e8e41175945f"}},"repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"_npmVersion":"12.1.0","description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","directories":{},"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"types":["dist/types/index.d.ts"],"pg-boss":["dist/module/pgBoss/index.d.ts"],"testing":["dist/testing/index.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"pg":"^8.16.0","jest":"^30.0.0","rxjs":"^7.8.2","luxon":"^3.7.2","eslint":"^8.57.1","lodash":"^4.17.21","pg-boss":"^12.28.1","ts-jest":"^29.4.0","ts-node":"^10.9.2","typeorm":"^1.1.0","prettier":"^3.6.0","@types/pg":"^8.15.0","typescript":"^5.9.0","@types/jest":"^30.0.0","@types/node":"^22.15.0","@nestjs/core":"^11.1.6","@nestjs/common":"^11.1.6","@nestjs/swagger":"^11.0.6","@nestjs/testing":"^11.1.6","class-validator":"0.15.1","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.5.0","@typescript-eslint/parser":"^8.44.0","@typescript-eslint/eslint-plugin":"^8.44.0","@ballistix.digital/exception-types":"^0.5.0","@ballistix.digital/exception-mapper":"^0.5.0"},"peerDependencies":{"pg-boss":"^12","typeorm":">=0.3.0 <2.0.0","@nestjs/core":"^11 || ^12","@nestjs/common":"^11 || ^12","reflect-metadata":"^0.2","@ballistix.digital/exception-types":">=0.4.0 <1.0.0","@ballistix.digital/exception-mapper":">=0.3.0 <1.0.0"},"peerDependenciesMeta":{"pg-boss":{"optional":true},"typeorm":{"optional":true},"@nestjs/core":{"optional":true},"@nestjs/common":{"optional":true},"reflect-metadata":{"optional":true},"@ballistix.digital/exception-types":{"optional":true},"@ballistix.digital/exception-mapper":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/task-scheduler_0.10.0_1790241416544_0.6398785824891784","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-09-07T18:22:31.465Z","modified":"2026-10-01T12:25:26.848Z","0.1.0":"2026-09-07T18:22:31.800Z","0.2.0":"2026-09-08T12:53:42.421Z","0.3.0":"2026-09-08T14:51:37.374Z","0.4.0":"2026-09-08T16:13:57.732Z","0.5.0":"2026-09-11T13:56:42.264Z","0.6.0":"2026-09-11T14:39:15.788Z","0.7.0":"2026-09-14T15:04:47.248Z","0.8.0":"2026-09-15T14:19:27.261Z","0.9.0":"2026-09-16T17:05:47.367Z","0.10.0":"2026-09-24T09:16:56.644Z"},"bugs":{"url":"https://github.com/layeredprints/task-scheduler/issues"},"license":"MIT","homepage":"https://github.com/layeredprints/task-scheduler#readme","repository":{"url":"git+https://github.com/layeredprints/task-scheduler.git","type":"git"},"description":"NestJS module that runs background work as durable task rows behind a pluggable queue engine.","maintainers":[{"email":"brecht.pallemans@ballistix.digital","name":"brecht-p7s-ballistix"},{"email":"maarten@ballistix.digital","name":"maartenraes"},{"email":"jens@ballistix.digital","name":"jmostaer"},{"email":"aaron@ballistix.digital","name":"aaron-ballistix"},{"email":"aude@ballistix.digital","name":"aude-ballistix"},{"email":"lukas@ballistix.digital","name":"lukaskindt"}],"readme":"# @ballistix.digital/task-scheduler\n\nA NestJS module that runs background work as durable task rows behind a\npluggable queue engine. A task is a database row with a processing status, and\nthe queue only transports the message that names that row. One engine ships,\non pg-boss, behind its own entry, so an application installs pg-boss only when\nit uses that engine.\n\nThis file is the consumer guide. The pages that describe how the package is\nbuilt live in the [Ballistix wiki](https://github.com/layeredprints/ballistix-okf-wiki/blob/main/general/libraries/task-scheduler/).\n\n## The model\n\nThe `processingStatus` column of the row is the lifecycle.\n\n```mermaid\nstateDiagram-v2\n    classDef waiting fill:#FFD700,stroke:#333,color:#000\n    classDef active fill:#87CEEB,stroke:#333,color:#00008B\n    classDef done fill:#90EE90,stroke:#333,color:#006400\n    classDef failed fill:#FFB6C1,stroke:#DC143C,color:#000\n\n    [*] --> PENDING: trigger() saves the row\n    PENDING --> PROCESSING: claim (only when PENDING)\n    PROCESSING --> COMPLETED: handle() returns\n    PROCESSING --> PENDING: handle() throws, status >= 500, attempts left\n    PROCESSING --> FAILED: handle() throws, status < 500 or attempts exhausted\n    COMPLETED --> [*]\n    FAILED --> [*]\n\n    class PENDING waiting\n    class PROCESSING active\n    class COMPLETED done\n    class FAILED failed\n```\n\nRead the two exits of `PROCESSING`: a retry returns the row to `PENDING`, and\nevery other failure settles it `FAILED`.\n\nThree mechanisms carry the guarantees:\n\n- **Transactional outbox.** The task row and its task message commit in one\n  transaction, or neither commits.\n- **Claim.** The processor sets `PROCESSING` with a conditional update on\n  `PENDING`. A repeated delivery loses the claim and stops.\n- **Retry guard.** A thrown error with `status >= 500` sends the row back to\n  `PENDING` while attempts remain. Any other error settles the row `FAILED`.\n  The queue owns the retry policy: the guard reads the `retryLimit` of the\n  message and the `processingMaxRetries` of the row, and the lower one wins.\n\n### One run, step by step\n\n```mermaid\nsequenceDiagram\n    box rgb(230,230,250) Application\n        participant App as Application service\n        participant P as ReportTaskProcessor\n    end\n    box rgb(144,238,144) Engine\n        participant E as TaskEngine\n    end\n    box rgb(211,211,211) Postgres\n        participant DB as Postgres\n    end\n\n    rect rgb(255,250,205)\n        Note over App,DB: 1. trigger: row and message in one transaction\n        App->>P: trigger({ reportId }, transaction)\n        P->>DB: INSERT report_task (PENDING)\n        P->>E: enqueue('report', { id }, transaction)\n        E->>DB: INSERT task message (same transaction)\n        P-->>App: the saved row\n    end\n\n    rect rgb(224,255,224)\n        Note over App,DB: 2. delivery: claim, handle, land\n        E->>P: deliver { id }, attempt 0\n        P->>DB: UPDATE status = PROCESSING WHERE id AND status = PENDING\n        P->>P: handle(task)\n        P->>DB: UPDATE status = COMPLETED\n        P->>P: afterCompleted(task)\n    end\n```\n\nLook at the first block: the row and the message travel on one transaction, so\na rollback leaves neither.\n\n### Two processors\n\nThe difference between the two base classes is who creates the task row.\n\n| | Task processor | Scheduled task processor |\n| --- | --- | --- |\n| Base class | `AbstractTaskProcessor` | `AbstractScheduledTaskProcessor` |\n| Who creates the row | Your code, with `trigger()` | The processor itself, on every tick, with the same `trigger()` |\n| When it runs | When a row is triggered | On the cron expression, and when a row is triggered |\n| Typical use | An upload to parse, an invitation to send | A nightly sync, an hourly cleanup |\n| Extra members | none | `cronExpression`, a `queueOptions` default that adds the `singleton` policy |\n\nA tick is a message without a task id. The scheduled processor answers it with\nits own `trigger()`, and the `{ id }` message that enqueues runs the work like\na manual run. The queue of a scheduled processor runs one message at a time\nacross every replica, so a tick never overlaps a manual run.\n\n## Install\n\n```bash\nnpm install @ballistix.digital/task-scheduler pg-boss\n```\n\nA browser build that reads the `./types` entry alone installs the package by\nitself:\n\n```bash\nnpm install @ballistix.digital/task-scheduler\n```\n\nThe package needs Node 22.12 or later and has no dependencies of its own.\nEvery peer dependency is optional, so npm installs none of them for you:\ninstall in the application what the entries it imports need.\n\n| Package | Range | Needed by |\n| --- | --- | --- |\n| `@nestjs/common` | `^11` | the root entry |\n| `@nestjs/core` | `^11` | the root entry |\n| `typeorm` | `>=0.3.0 <2.0.0` | the root entry |\n| `reflect-metadata` | `^0.2` | the root entry |\n| `@ballistix.digital/exception-mapper` | `^0.3.0 \\|\\| ^0.4.0` | the root entry |\n| `@ballistix.digital/exception-types` | `^0.4.0` | the root entry |\n| `pg-boss` | `^12` | the `./pg-boss` entry |\n\n| Entry | Peers it needs |\n| --- | --- |\n| `@ballistix.digital/task-scheduler` | all of them except pg-boss |\n| `@ballistix.digital/task-scheduler/pg-boss` | pg-boss, next to the peers of the root entry |\n| `@ballistix.digital/task-scheduler/testing` | the peers of the root entry |\n| `@ballistix.digital/task-scheduler/types` | none |\n\nOnly the `./pg-boss` entry loads pg-boss, so an application with an engine of\nits own, or a package that only tests against `TaskEngineMock`, leaves pg-boss\nout. The `./types` entry imports no framework at all: it carries\n`ProcessingStatusEnum` alone, so a browser application that shares DTO types\nwith the backend re-exports the enum from that entry instead of copying it.\n\nThe two exception packages carry the error envelope and the mappers that build\nit. A processor stores that envelope in `processingError` and reads its\n`status` to decide the retry. A worker thread maps what its worker function\nthrew through the same mappers and sends the envelope back.\n\nA worker file that runs from its TypeScript source, which is what a Jest suite\ndoes, loads in its thread through `ts-node/register`. Install `ts-node` as a\ndev dependency for that, pass other loader flags through\n`WorkerModuleOptions.execArgv`, or test with `WorkerRunnerMock`, which needs\nno loader. See [Run CPU-bound work in a worker thread](#run-cpu-bound-work-in-a-worker-thread).\n\n## Register the module\n\n```ts\nimport { ExceptionModule } from '@ballistix.digital/exception-mapper';\nimport { Module } from '@nestjs/common';\nimport { TaskModule } from '@ballistix.digital/task-scheduler';\nimport { PgBossEngineService } from '@ballistix.digital/task-scheduler/pg-boss';\n\n@Module({\n\timports: [\n\t\tExceptionModule.forRoot(),\n\t\tTaskModule.forRoot({\n\t\t\tengine: new PgBossEngineService({\n\t\t\t\tconnection: { host: 'localhost', port: 5432, user: 'app', password: 'secret', database: 'app' },\n\t\t\t\tschema: 'pgboss',\n\t\t\t\tpoolSize: 5,\n\t\t\t}),\n\t\t}),\n\t],\n})\nexport class AppModule {}\n```\n\nIf the options need injection, use `TaskModule.forRootAsync({ imports, inject,\nuseFactory })`. The factory returns the same options object.\n\nBoth modules are global. A processor and a service alike inject `TaskRuntime`\nwithout a further import; nothing else of the package is injectable.\n`TaskModule` starts the engine in `onModuleInit` and stops it in\n`onApplicationShutdown`. Nest runs that hook after the HTTP server has\ndrained its in-flight requests, so a request that is still running can\nenqueue until the end. Call `app.enableShutdownHooks()` in the bootstrap of\nthe application: without it a `SIGTERM` ends the process at once and neither\nthe drain nor the stop happens. A test of the application swaps the engine by\noverriding the `TASK_ENGINE` token the module provides it under; see\n[Swap the engine in tests](#swap-the-engine-in-tests).\n\nThe engine keeps its own pool and its own schema, apart from the TypeORM\nconnection of the application. It reads no environment variables. The\n[pg-boss engine page](https://github.com/layeredprints/ballistix-okf-wiki/blob/main/general/libraries/task-scheduler/engines/pg-boss.md) gives the other options.\n\n## Give the task a row\n\nCompose `ProcessableTaskMixin` onto the entity base class of the application:\n\n```ts\nimport { ProcessableTaskMixin } from '@ballistix.digital/task-scheduler';\nimport { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';\n\n@Entity({ name: 'report_task' })\nexport class ReportTask extends ProcessableTaskMixin(BaseEntity) {\n\t@PrimaryGeneratedColumn('uuid')\n\tid: string;\n\n\t@Column({ type: 'uuid', nullable: false })\n\treportId: string;\n}\n```\n\nThe mixin contributes five columns:\n\n| Column | Type | Default |\n| --- | --- | --- |\n| `processingStatus` | enum `processing_status`, indexed | `PENDING` |\n| `processingError` | `jsonb`, nullable | `null` |\n| `processingRetryCount` | `int` | `0` |\n| `processingMaxRetries` | `int` | `5` (`DEFAULT_RETRY_LIMIT`) |\n| `processingDuration` | `int`, nullable, milliseconds | `null` |\n\n`processingError` holds an `ExceptionDto`. Import that type from\n`@ballistix.digital/exception-types`: this package does not re-export it.\n\nEvery entity on the mixin shares the one Postgres enum type\n`processing_status`. Generate a migration for the new table.\n\n## Write a task processor\n\n```ts\nimport { GenericBadRequestException } from '@ballistix.digital/exception-types';\nimport { Injectable } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { AbstractTaskProcessor, TaskRuntime } from '@ballistix.digital/task-scheduler';\nimport { Repository } from 'typeorm';\nimport { ReportTask } from './report.task.entity';\nimport { RenderService } from './render.service';\n\n@Injectable()\nexport class ReportTaskProcessor extends AbstractTaskProcessor<ReportTask> {\n\tprotected readonly queue = 'report';\n\n\tconstructor(\n\t\ttaskRuntime: TaskRuntime,\n\t\t@InjectRepository(ReportTask) reportTaskRepository: Repository<ReportTask>,\n\t\tprivate readonly renderService: RenderService,\n\t) {\n\t\tsuper(taskRuntime, reportTaskRepository);\n\t}\n\n\t/** A 400 settles the task on the first attempt: the input cannot become valid on a retry. */\n\tprotected async handle(task: ReportTask): Promise<void> {\n\t\tif (!task.reportId) {\n\t\t\tthrow new GenericBadRequestException('Task carries no reportId');\n\t\t}\n\n\t\tawait this.renderService.render(task.reportId);\n\t}\n\n\t/** Runs after COMPLETED has committed. A failure here never reverts COMPLETED. */\n\tprotected async afterCompleted(task: ReportTask): Promise<void> {\n\t\tawait this.renderService.publish(task.reportId);\n\t}\n}\n```\n\nRegister the class as a provider of the owning module. On bootstrap the base\ncreates the queue with `queueOptions` and subscribes to it.\n\n### Queue options\n\nThe queue is the one place that holds the retry policy. A processor declares\n`retryLimit`, `retryDelaySeconds` and `retryBackoff` in `queueOptions`, the\nengine applies them on bootstrap, and every message on the queue inherits\nthem: the message of `trigger()`, the message of a schedule tick, and the\nmessage a service enqueues for a row it saved itself. No enqueue of the\npackage passes retry options of its own, so every run on one queue retries the\nsame way.\n\nA processor that declares nothing keeps the default of the base,\n`DEFAULT_QUEUE_OPTIONS`: `retryLimit: 5` (`DEFAULT_RETRY_LIMIT`) with\n`retryBackoff: true`. A processor that must not repeat a run declares\n`retryLimit: 0`: the first throw settles the row `FAILED`. A processor whose\nwork needs a pause before the next attempt adds `retryDelaySeconds`:\n\n```ts\nprotected readonly queueOptions: QueueOptions = {\n\t...DEFAULT_QUEUE_OPTIONS,\n\tretryDelaySeconds: 30,\n};\n```\n\nEvery start reconciles the queue with `queueOptions`. A changed\n`expireInSeconds`, `retryLimit`, `retryDelaySeconds` or `retryBackoff` reaches\na queue an earlier release created. A changed `policy` does not: a policy is\nfixed when the queue is created, so the engine keeps the queue and logs a\nwarning that names both policies. Give a processor that needs another policy a\nnew queue name.\n\n`super()` takes two arguments. The first is the `TaskRuntime` the module\nprovides. Pass it up and forget it: the type carries `enqueue()` and nothing\nelse, the class behind it is not exported, and a new dependency of the base\nchanges that class and no processor. The second is the repository. Name the\nparameter after the entity, such as `reportTaskRepository`. If the subclass\nneeds the repository itself, keep it in a private field with that name.\n\nYou implement `handle()`. You can also override:\n\n| Member | Default | Override when |\n| --- | --- | --- |\n| `afterCompleted(task)` | nothing | work must follow a committed `COMPLETED`, such as a notification |\n| `buildTask(input)` | `repository.create(input ?? {})` | the row needs values the entity defaults cannot give |\n| `onMessage(message)` | process the id, warn when there is none | you need another message shape (rare) |\n\n`handle()` runs outside any held transaction, so a slow run holds no row lock.\nIt opens its own transactions when it needs them. The queue is a plain string.\nKeep the queue names of the application in one enum.\n\n## Write a scheduled task processor\n\nThe constructor follows the task processor above.\n\n```ts\n@Injectable()\nexport class AfasSyncTaskProcessor extends AbstractScheduledTaskProcessor<AfasSyncTask> {\n\tprotected readonly queue = 'afas-sync';\n\tprotected readonly cronExpression = '5 */6 * * *';\n\n\t/** This sync must not repeat a partial run, so the queue allows no retry. */\n\tprotected readonly queueOptions: QueueOptions = { policy: 'singleton', expireInSeconds: 1800, retryLimit: 0 };\n\n\tprotected async handle(task: AfasSyncTask): Promise<void> {\n\t\tawait this.afasService.syncCustomers();\n\t\tawait this.afasService.syncProjects();\n\t}\n}\n```\n\nA scheduled processor is a task processor plus `cronExpression`. Cron\nexpressions are UTC. The base creates the queue with the `singleton` policy, so\none run is active at a time across every replica.\n\n`queueOptions` holds the time budget of one run (`expireInSeconds`, default\n600) and the retry policy of the queue, which starts from\n`DEFAULT_QUEUE_OPTIONS`. A tick and a manual run inherit the same policy. The\nexample gives the queue a budget of 30 minutes and no retry: `retryLimit: 0`\nsettles every run `FAILED` on its first throw, whatever `processingMaxRetries`\nthe row carries.\n\nA tick creates the row with `trigger()` and no input. If the entity has a\ncolumn the tick cannot leave empty, override `buildTask()`.\n\nSchedules tick in every environment. Keep a processor safe to run from staging\nthrough its own configuration, not through the absence of a schedule.\n\n## Trigger a run from a service\n\n```ts\n@Injectable()\nexport class ReportService {\n\tconstructor(\n\t\tprivate readonly dataSource: DataSource,\n\t\tprivate readonly processor: ReportTaskProcessor,\n\t) {}\n\n\tpublic async requestReport(reportId: string): Promise<ReportTask> {\n\t\treturn this.dataSource.transaction(async (transaction) => {\n\t\t\tawait transaction.update(Report, reportId, { requestedAt: new Date() });\n\n\t\t\treturn this.processor.trigger({ reportId }, transaction);\n\t\t});\n\t}\n}\n```\n\n`trigger(input, transaction)` saves the row and enqueues `{ id }` on the same\ntransaction, with no enqueue options: the message inherits the retry policy of\nthe queue. Without a transaction the processor opens one. A rolled-back\ntransaction leaves no row and no task message.\n\n`trigger()` returns the saved row, so the caller can poll `processingStatus`\nand `processingError`. A manual `trigger()` on a scheduled processor works the\nsame way, because the schedule and the manual runs share the one subscriber.\n\nA processor never reads an application authentication context. Put audit values\nin the `trigger()` input.\n\n### Enqueue a row you saved yourself\n\nA service that owns the row, such as an upload flow that marks an attachment\n`PENDING`, enqueues the message itself through `TaskRuntime`:\n\n```ts\n@Injectable()\nexport class UploadService {\n\tconstructor(private readonly taskRuntime: TaskRuntime) {}\n\n\tpublic async onUploadReady(upload: Upload, transaction: EntityManager): Promise<void> {\n\t\tawait transaction.update(Upload, upload.id, { processingStatus: ProcessingStatusEnum.PENDING });\n\t\tawait this.taskRuntime.enqueue('upload', { id: upload.id }, transaction, { dedupeKey: upload.id });\n\t}\n}\n```\n\n`enqueue()` is the one call `TaskRuntime` exposes. The message rides the\ntransaction you pass, so the row and the message commit together. Pass a\n`dedupeKey` when the policy of the queue needs one, and no retry options: the\nmessage inherits the retry policy of the queue, the same as a `trigger()`. The\n[consumer surface](https://github.com/layeredprints/ballistix-okf-wiki/blob/main/general/libraries/task-scheduler/architecture.md#what-a-consumer-sees) diagram shows\nwhat sits behind the token and why you cannot reach it.\n\n## Errors and retries\n\nWhen `handle()` throws, the `ExceptionMapperRegistry` normalizes the thrown\nvalue into an `ExceptionDto`. The processor stores that envelope in\n`processingError`, and the `status` of the envelope decides what happens next.\n\n```mermaid\nflowchart TD\n    T[\"handle() throws\"] --> N[\"error = exceptionMapperRegistry.toExceptionDto(thrown)\"]\n    N --> S{\"error.status >= 500?\"}\n    S -- no --> F[\"FAILED, processingError stored, no rethrow, telemetry line\"]\n    S -- yes --> A{\"attempt < min(message.retryLimit, processingMaxRetries)?\"}\n    A -- no --> F\n    A -- yes --> R[\"PENDING, processingError stored, rethrow so the engine retries with backoff\"]\n\n    classDef decision fill:#FFD700,stroke:#333,stroke-width:2px,color:#000\n    classDef retry fill:#87CEEB,stroke:#333,stroke-width:2px,color:#00008B\n    classDef terminal fill:#FFB6C1,stroke:#DC143C,stroke-width:2px,color:#000\n    classDef step fill:#F5F5F5,stroke:#333,stroke-width:1px,color:#000\n    class S,A decision\n    class R retry\n    class F terminal\n    class T,N step\n```\n\nLook at the first decision: the registry normalizes every thrown value, so the\nguard reads one field whatever the code threw.\n\nIf the code matters, throw a `BaseException` of the types package. Otherwise\nthrow any value that carries a `status`:\n\n| Thrown value | Stored `ExceptionDto` | Retried? |\n| --- | --- | --- |\n| `new GenericBadRequestException('bad input')` | `title: 'bad input'`, `status: 400`, `code: 'GENERIC_ERROR'` | no, the input is wrong |\n| `new QueryFailedError(...)` with SQLSTATE `23505` | `status: 400`, `code: 'DATABASE_ERROR'` | no, the statement is wrong |\n| `new QueryFailedError(...)` with SQLSTATE `40P01` | `status: 500`, `code: 'DATABASE_ERROR'` | yes, while attempts remain |\n| `{ message, status: 400 }` | `title: message`, `status: 400`, `code: 'GENERIC_ERROR'` | no, the input is wrong |\n| `{ message, status: 503 }` | `title: message`, `status: 503`, `code: 'GENERIC_ERROR'` | yes, while attempts remain |\n| `new Error('boom')` | `title: 'boom'`, `status: 500`, `code: 'GENERIC_ERROR'` | yes, while attempts remain |\n| `'a string'` | `title: 'a string'`, `status: 500` | yes, while attempts remain |\n\nThe registry tries its mappers in order and the generic mapper is last. A\nthrown value that names no code therefore becomes `GENERIC_ERROR`, and one\n`errors` detail carries the message.\n\nThe queue decides the retry budget through its `retryLimit`, and the engine\nstamps that limit on every message it delivers. `processingMaxRetries` on the\nrow is a row-level budget that can only lower the budget of the queue: the\nguard retries while `attempt` is below both numbers. A row that asks for more\nretries than its queue allows gets the retries of the queue, because the\nengine stops redelivering at its own limit and a row sent back to `PENDING`\npast that limit would wait for a message that never comes. Both numbers\ndefault to 5 (`DEFAULT_RETRY_LIMIT`).\n\n`processingRetryCount` records how many attempts ran. `processingDuration`\nrecords how long the last attempt of `handle()` ran, in milliseconds, whatever\nits outcome. The engine schedules the retries with the delay and the backoff of\nthe queue. The row is the durable record you read.\n\n## Run CPU-bound work in a worker thread\n\nNode runs JavaScript on one thread. A render of a document or an email blocks\nthe event loop for its full duration, and while it runs the API answers\nnothing. `WorkerRunnerService` runs one function in a thread of its own, and\nthe caller awaits the result the way it awaits any promise.\n\n```mermaid\nflowchart LR\n    S[\"service or handle()<br/>workerRunner.run(__dirname, 'render.worker', input)\"] --> R[\"WorkerRunnerService<br/>one thread per run\"]\n    R --> B[\"thread<br/>loads the worker file,<br/>calls its default export\"]\n    B -->|\"result\"| R\n    B -->|\"error envelope\"| R\n    R -->|\"resolve\"| OK[\"the result\"]\n    R -->|\"reject\"| E[\"WorkerException<br/>status, code, details kept\"]\n\n    classDef main fill:#87CEEB,stroke:#333,stroke-width:2px,color:#00008B\n    classDef thread fill:#FFD700,stroke:#333,stroke-width:2px,color:#000\n    classDef done fill:#90EE90,stroke:#333,color:#006400\n    classDef failed fill:#FFB6C1,stroke:#DC143C,color:#000\n    class S,R main\n    class B thread\n    class OK done\n    class E failed\n```\n\nRegister the module once, at the root. It is global, so any feature module\ninjects the runner without importing anything.\n\n```ts\nimport { WorkerModule } from '@ballistix.digital/task-scheduler';\n\nWorkerModule.forRootAsync({\n\tinject: [ConfigService],\n\tuseFactory: (config: ConfigService<EnvModel>) => ({\n\t\ttimeoutSeconds: config.get('WORKER_TIMEOUT_SECONDS'),\n\t}),\n});\n```\n\n`timeoutSeconds` is the timeout of every run that passes none of its own.\nThe default is 300. `execArgv` is the list of flags a thread starts with when\nit loads a `.ts` worker file; the default is `['-r', 'ts-node/register']`.\n\n### Write a worker file\n\nA worker file is a plain module next to the service that calls it, named\n`<name>.worker.ts`. It exports the worker function as its default export, and\nit imports nothing from this package. The function takes one input and returns\none result. Both cross the thread as structured clones, so a class instance\narrives as a plain object and a Node `Buffer` arrives as a `Uint8Array`.\n\n```ts\n// render.worker.ts\nexport interface RenderInput {\n\ttemplate: Uint8Array;\n\tdata: Record<string, unknown>;\n}\n\nexport interface RenderOutput {\n\tfile: Uint8Array;\n}\n\nexport default async function render(input: RenderInput): Promise<RenderOutput> {\n\tconst file = renderWithSomeLibrary(input.template, input.data);\n\n\treturn { file };\n}\n\n// Optional: the buffers of the result to move instead of copy.\nexport const transfer = (output: RenderOutput): ArrayBuffer[] => [output.file.buffer as ArrayBuffer];\n```\n\nThe worker function is a pure function of its input: no Nest injection, no\nTypeORM, no request context. Everything else, the database reads, the blob\nstorage and the task lifecycle, stays on the main thread. Export the input and\nthe output types, so the caller imports the types and nothing else from the\nfile.\n\n### Call the runner\n\n```ts\nimport { WorkerRunnerService } from '@ballistix.digital/task-scheduler';\nimport type { RenderInput, RenderOutput } from './render.worker';\n\n@Injectable()\nexport class ReportService {\n\tconstructor(private readonly workerRunner: WorkerRunnerService) {}\n\n\tasync render(template: Uint8Array, data: Record<string, unknown>): Promise<Uint8Array> {\n\t\tconst output = await this.workerRunner.run<RenderInput, RenderOutput>(\n\t\t\t__dirname,\n\t\t\t'render.worker',\n\t\t\t{ template, data },\n\t\t\t{ timeoutSeconds: 120, transfer: [template.buffer as ArrayBuffer] },\n\t\t);\n\n\t\treturn output.file;\n\t}\n}\n```\n\n`run(dir, workerName, input, options?)` finds `<workerName>.ts` in `dir` when\nthe source exists on disk and `<workerName>.js` otherwise, so the same call\nworks under a test runner and from `dist` in production. Pass `__dirname`, and\nthe worker file sits next to the caller in both trees.\n\nA run that passes `timeoutSeconds` wins over the value of the module. A run\nthat lists buffers in `transfer` moves them to the thread instead of copying\nthem: the buffer has byte length 0 on the calling side afterwards. Only a\nbuffer that owns its `ArrayBuffer` can move. A small Node `Buffer` shares a\npooled slab with others, so copy it out first, with `Buffer.allocUnsafeSlow`\nor `new Uint8Array(buffer)`.\n\nA task processor calls the runner from `handle()` the same way. The row, the\nclaim and the retry stay on the main thread; only the CPU-bound call moves.\n\n### What a failure becomes\n\nThe thread maps what the worker function threw through the exception mapper\nregistry, the same mappers a processor uses, and posts the envelope. The run\nrejects with a `WorkerException`, a `BaseException` restored from that\nenvelope, with the stack the error had in the thread. So the retry guard reads\na worker failure like any other: a 400 lands `FAILED`, a 500 retries.\n\n| What happened in the thread | The run rejects with | Status, code |\n| --- | --- | --- |\n| The worker function threw a `BaseException` | `WorkerException` | its own status and code, its details kept |\n| The worker function threw a class-validator `ValidationException` | `WorkerException` | 400, `VALIDATION_FAILED` |\n| The worker function threw anything else | `WorkerException` | 500, `GENERIC_ERROR`, the message as title |\n| An error escaped the promise chain, from a timer for example | `WorkerException` | as above, by what was thrown |\n| The thread could not start, for example on a buffer that cannot move | `GenericErrorException` | 500, `GENERIC_ERROR`, `Worker <name> could not start: <cause>` |\n| The run timed out | `GenericErrorException` | 500, `GENERIC_ERROR`, `Worker <name> timed out after <n> s` |\n| The thread exited before it posted a result | `GenericErrorException` | 500, `GENERIC_ERROR`, `Worker <name> exited with code <code> before it posted a result` |\n| The application shut down during the run | `GenericErrorException` | 500, `GENERIC_ERROR`, `Worker <name> was terminated because the runner is shut down` |\n\nThe run settles once, on the first of those events, and the runner terminates\nthe thread after every settle, also after a result. A worker function that\nleaves a timer or a socket open cannot keep a thread alive.\n\nOn shutdown the runner terminates every thread still running and rejects its\nrun, and it refuses every run that comes after. A task processor that was\ninside a run settles that attempt as a 500 and retries it after the restart.\n`app.enableShutdownHooks()` is what makes a signal reach the hook, the same as\nfor the engine.\n\n## Telemetry\n\nThe base processor writes one structured line per attempt through the Nest\n`Logger`, with a flat context object as the second argument. Dashboards and\nmonitors build on these fields.\n\n| Field | Value |\n| --- | --- |\n| message | `Task <id> on <queue> executed with status <status> in <n> ms` |\n| level | info for `completed`, warn for `pending`, error for `failed` |\n| context | `task_queue`, `task_id`, `task_status`, `task_duration_ms`, `task_scheduled`, `ddtags` |\n| context, conditional | `task_error_code` when the attempt failed |\n\nThe status of the line is the `ProcessingStatusEnum` member the landing wrote\non the row. The line lower-cases it, so the message, `task_status` and the\n`task.status` tag read `completed`, `pending` for a retry, or `failed` for a\nterminal failure. `task_duration_ms` is the `processingDuration` of the row, so\nthe line and the row never disagree.\n\n`task_scheduled` is true on every run of a processor that carries a\n`cronExpression`, whether a tick or a manual `trigger()` created the row, and\nfalse on every other run. The tag `task.scheduled:true` joins `ddtags` on the\ntrue runs only, because a tag exists to filter on. Query the field and the\nqueue together: `@task_scheduled:true @task_queue:<QUEUE>`.\n\nThe [architecture page](https://github.com/layeredprints/ballistix-okf-wiki/blob/main/general/libraries/task-scheduler/architecture.md) gives the `ddtags` format.\n\n## Test a processor\n\nThe `./testing` entry gives `TaskEngineMock`. It performs no I/O, records every\ncall, and delivers a message on demand.\n\n```ts\nimport { ExceptionModule } from '@ballistix.digital/exception-mapper';\nimport { Test } from '@nestjs/testing';\nimport { getRepositoryToken } from '@nestjs/typeorm';\nimport { ProcessingStatusEnum, TaskModule } from '@ballistix.digital/task-scheduler';\nimport { TaskEngineMock } from '@ballistix.digital/task-scheduler/testing';\n\nit('completes the task the engine delivers', async () => {\n\tconst engine = new TaskEngineMock();\n\tconst repository = testDataSource.getRepository(ReportTask);\n\n\tconst moduleRef = await Test.createTestingModule({\n\t\timports: [ExceptionModule.forRoot(), TaskModule.forRoot({ engine })],\n\t\tproviders: [ReportTaskProcessor, { provide: getRepositoryToken(ReportTask), useValue: repository }],\n\t}).compile();\n\tawait moduleRef.init();\n\n\tconst processor = moduleRef.get(ReportTaskProcessor);\n\tconst task = await processor.trigger({ reportId: 'r-1' });\n\texpect(engine.enqueued[0].data).toEqual({ id: task.id });\n\n\tawait engine.deliver('report', { id: task.id });\n\n\tconst settled = await repository.findOneByOrFail({ id: task.id });\n\texpect(settled.processingStatus).toBe(ProcessingStatusEnum.COMPLETED);\n});\n```\n\n`moduleRef.init()` runs the bootstrap hook, so the processor subscribes before\nthe first `deliver`. Read these members of the mock:\n\n| Member | Holds |\n| --- | --- |\n| `enqueued` | every `enqueue` call: `queue`, `data`, `options`, `transaction` |\n| `queues` | every `createQueue` call: `queue`, `options` |\n| `subscriptions` | the handler per queue |\n| `schedules` | every `schedule` call: `queue`, `cron` |\n| `unscheduled` | every queue that was unscheduled |\n| `deliver(queue, data, attempt, retryLimit?)` | hands a message to the subscribed handler |\n| `reset()` | empties the recordings and keeps the subscriptions |\n\nTo test a scheduled processor, deliver an empty object.\n`engine.deliver('afas-sync', {})` is a tick.\n\n`deliver()` stamps a `retryLimit` on the message the way an engine does. Left\nout, it is the `retryLimit` the queue was created with, so a processor under\ntest sees its own policy. A queue created without one gives infinity, so a spec\ndelivers as many attempts as it asks for.\n\nA spec that boots the module once and runs several cases on it empties the\nrecordings between them, and keeps the handlers the processors subscribed on\nbootstrap:\n\n```ts\nbeforeEach(() => engine.reset());\n```\n\n### Run a worker function without a thread\n\nThe `./testing` entry gives `WorkerRunnerMock`. It has the `run()` signature of\nthe service and runs the worker function on the calling thread: the test\nrunner transforms the `.ts` worker file the way it transforms any other\nmodule, so no loader is installed, and a thrown value becomes the same\n`WorkerException` a thread would send. It records every run in `runs` and\nempties them on `reset()`. There is no timeout, and a buffer listed in\n`transfer` stays readable.\n\n```ts\nimport { WorkerRunnerService } from '@ballistix.digital/task-scheduler';\nimport { WorkerRunnerMock } from '@ballistix.digital/task-scheduler/testing';\n\nconst moduleRef = await Test.createTestingModule({ imports: [AppModule] })\n\t.overrideProvider(WorkerRunnerService)\n\t.useClass(WorkerRunnerMock)\n\t.compile();\n\nconst runner = moduleRef.get<WorkerRunnerMock>(WorkerRunnerService);\nexpect(runner.runs).toEqual([{ dir: expect.any(String), workerName: 'render.worker', input, options: undefined }]);\n```\n\nA service built by hand takes `new WorkerRunnerMock()` where it takes the\nrunner. To run the real thread in a spec instead, install `ts-node` and use\nthe service as it is.\n\n### Swap the engine in tests\n\n`TaskEngine` is a type, not a provider token, so\n`overrideProvider(TaskEngine)` finds nothing to replace. `TASK_ENGINE` is the\ntoken the module holds the engine under, and it is exported for this: a test\nthat boots the whole application overrides it and needs no token of its own.\n\n```ts\nimport { TASK_ENGINE, TaskModule } from '@ballistix.digital/task-scheduler';\nimport { PgBossEngineService } from '@ballistix.digital/task-scheduler/pg-boss';\nimport { TaskEngineMock } from '@ballistix.digital/task-scheduler/testing';\n\n// AppModule\nTaskModule.forRootAsync({\n\tinject: [ConfigService],\n\tuseFactory: (config: ConfigService) => ({ engine: new PgBossEngineService({ ... }) }),\n});\n\n// A test\nconst moduleRef = await Test.createTestingModule({ imports: [AppModule] })\n\t.overrideProvider(TASK_ENGINE)\n\t.useClass(TaskEngineMock)\n\t.compile();\n```\n\nThe override replaces the provider the registration built, so the factory of\n`forRootAsync` never runs its engine and the application reaches the mock\neverywhere: `TaskModule` starts and stops it, every processor subscribes on\nit, and `TaskRuntime.enqueue()` records on it. Read it back with\n`moduleRef.get<TaskEngineMock>(TASK_ENGINE)`, and drive the processors with\n`engine.deliver()` the same way as above. `useValue(new TaskEngineMock())`\nworks as well, when the spec wants the instance before it compiles.\n\n## API reference\n\nEverything below comes from the package root, except `PgBossEngineService`\nand `PgBossEngineOptions`, which come from\n`@ballistix.digital/task-scheduler/pg-boss`, `TaskEngineMock`,\n`RecordedQueue`, `RecordedEnqueue`, `RecordedSchedule`, `WorkerRunnerMock` and\n`RecordedRun`, which come from `@ballistix.digital/task-scheduler/testing`, and\n`ExceptionDto`, which comes from `@ballistix.digital/exception-types`. `ProcessingStatusEnum` also comes\nfrom `@ballistix.digital/task-scheduler/types`, the entry that imports no\nframework.\n\n| Entry | Holds |\n| --- | --- |\n| `@ballistix.digital/task-scheduler` | The modules, the processors, `TaskRuntime`, the worker runner, the `TASK_ENGINE` token and the domain types |\n| `@ballistix.digital/task-scheduler/pg-boss` | `PgBossEngineService` and `PgBossEngineOptions` |\n| `@ballistix.digital/task-scheduler/testing` | `TaskEngineMock`, `WorkerRunnerMock` and their recorded-call types |\n| `@ballistix.digital/task-scheduler/types` | `ProcessingStatusEnum`, and nothing that imports a framework |\n\nEvery emitted module is also reachable by its path under `dist/`, because the\nNest Swagger CLI plugin rebuilds an enum-typed DTO property as a `require()` of\nthe declaration file the enum came from. Import through an entry in code you\nwrite.\n\n### Module\n\n| Export | Purpose |\n| --- | --- |\n| `TaskModule.forRoot(options)` | Registers the module with an engine instance |\n| `TaskModule.forRootAsync(options)` | The same, with `imports`, `inject` and a `useFactory` |\n| `TaskModuleOptions` | `{ engine: TaskEngine }` |\n| `TaskModuleAsyncOptions` | `{ imports?, inject?, useFactory }` |\n| `TASK_ENGINE` | The token the module holds the engine under. A test overrides it to swap the engine |\n| `PgBossEngineService` | The pg-boss engine, constructed with `PgBossEngineOptions`; from the `./pg-boss` entry |\n| `PgBossEngineOptions` | `{ connection, schema, poolSize?, superviseIntervalSeconds?, monitorIntervalSeconds?, queueCacheIntervalSeconds? }` |\n\n### Processors\n\n| Export | Purpose |\n| --- | --- |\n| `AbstractTaskProcessor<T>` | Base class of a task processor |\n| `AbstractScheduledTaskProcessor<T>` | Base class of a scheduled task processor |\n| `TaskRuntime` | The injectable of the task layer: the first argument of `super()` in a processor, and `enqueue()` for a service |\n| `TaskEngine` | The engine contract an application passes to `TaskModule`; a type, not something you inject |\n\n`AbstractTaskProcessor<T>` declares two abstract members, `queue` and\n`handle(task)`. `trigger(input?, transaction?)` is its only public method. The\nprotected members a subclass reads or overrides are `queueOptions`,\n`afterCompleted(task)`, `buildTask(input)`, `onMessage(message)`, `repository`\nand `logger`. The engine, the telemetry and the exception mapper registry sit\nbehind `TaskRuntime`, and only the base reads them.\n\n`AbstractTaskProcessor<T>` sets `queueOptions` to `DEFAULT_QUEUE_OPTIONS`.\n`AbstractScheduledTaskProcessor<T>` adds the abstract `cronExpression` and sets\n`queueOptions` to `{ ...DEFAULT_QUEUE_OPTIONS, policy: 'singleton', expireInSeconds: 600 }`.\n\n### Domain\n\n| Export | Purpose |\n| --- | --- |\n| `ProcessableTaskMixin(Base)` | Adds the five processing columns to an entity class |\n| `ProcessableTask` | The interface of a row with those columns |\n| `ProcessingStatusEnum` | `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`. Also on the `./types` entry, for a browser build that shares the status |\n| `ExceptionDto` | `{ title, status, code, errors }`, stored in `processingError`. Import it from `@ballistix.digital/exception-types` |\n| `TaskMessage<T>` | `{ data, attempt, retryLimit }`, one delivery. `retryLimit` is how many redeliveries the engine allows |\n| `EnqueueOptions` | `dedupeKey`, `retryLimit`, `retryBackoff`, `retryDelaySeconds`, `expireInSeconds`. The retry fields are for a message that must differ from its queue |\n| `QueueOptions` | `policy`, `expireInSeconds`, `retryLimit`, `retryDelaySeconds`, `retryBackoff` |\n| `DEFAULT_QUEUE_OPTIONS` | `{ retryLimit: DEFAULT_RETRY_LIMIT, retryBackoff: true }`, the `queueOptions` a processor starts from |\n| `DEFAULT_RETRY_LIMIT` | `5`, the default of `processingMaxRetries` and of the `retryLimit` of a queue |\n| `QueuePolicy` | `standard`, `short`, `singleton`, `stately` |\n| `Constructor<T>` | The constructor type the mixin accepts |\n\n### Worker\n\n| Export | Purpose |\n| --- | --- |\n| `WorkerModule.forRoot(options?)` | Registers the worker runner with its options |\n| `WorkerModule.forRootAsync(options)` | The same, with `imports`, `inject` and a `useFactory` |\n| `WorkerModuleOptions` | `{ timeoutSeconds?, execArgv? }`: the timeout of a run without one, default 300 s, and the flags of a thread that loads a `.ts` worker file, default `-r ts-node/register` |\n| `WorkerModuleAsyncOptions` | `{ imports?, inject?, useFactory }` |\n| `WorkerRunnerService` | The injectable: `run<TIn, TOut>(dir, workerName, input, options?)` resolves with the result of the worker function |\n| `WorkerRunOptions` | `{ timeoutSeconds?, transfer? }`: the timeout of this run, and the buffers of the input to move |\n| `WorkerException` | What a run rejects with when the worker function threw: a `BaseException` restored from the envelope the thread posted, with the stack of the thread |\n| `DEFAULT_WORKER_TIMEOUT_SECONDS` | `300` |\n| `DEFAULT_TYPESCRIPT_EXEC_ARGV` | `['-r', 'ts-node/register']` |\n| `WorkerRunnerMock` | The runner that runs the worker function on the calling thread and records each run; from the `./testing` entry |\n| `RecordedRun` | `{ dir, workerName, input, options }`, one recorded run |\n\nA worker file exports its worker function as `default` and, when it moves\nresult buffers, `transfer(output): ArrayBuffer[]`. It imports nothing from this\npackage.\n\n### Engine contract\n\n`TaskEngine` is abstract. An application constructs one implementation and\nhands it to `TaskModule`; the module and the processors call it. It is not\ninjectable: a service enqueues through `TaskRuntime`.\n\n| Method | Purpose |\n| --- | --- |\n| `start()` | Opens the connection and starts delivering |\n| `stop()` | Closes the connection. Safe to call more than once |\n| `createQueue(queue, options?)` | Creates the queue with these options, and reconciles the options an existing queue still accepts. The `policy` is not one of them |\n| `enqueue(queue, data, transaction?, options?)` | Puts one message on a queue. Returns the message id, or `null` when the policy of the queue collapsed it |\n| `subscribe(queue, handler)` | Registers the handler of a queue |\n| `schedule(queue, cron)` | Registers a recurring tick that inherits the options of the queue |\n| `unschedule(queue)` | Removes a schedule. Never throws for an unknown name |\n\n## Guarantees\n\n- The task row and its task message commit together. A fire-and-forget enqueue\n  does not exist.\n- Every run is a row. \"Last run\" is a query, not a log search.\n- The claim is atomic. A second delivery of the same task stops at the claim.\n- One queue per processor. A tick and a manual run never overlap on one\n  replica.\n- The queue owns the retry policy. `processingMaxRetries` on the row can lower\n  the budget of the queue and never raise it. An error with `status` below 500\n  settles the task `FAILED` on the first attempt.\n- `afterCompleted()` is best effort. Its failure is logged and never reverts\n  `COMPLETED`.\n- A processor never reads an application authentication context. Audit values\n  travel in the `trigger()` input.\n- A worker run settles once, and its thread is terminated after every settle.\n  A failure of the worker function keeps its status, code and details across\n  the thread.\n","readmeFilename":"README.md"}