{"_id":"@benjosivo/automation","_rev":"12-a90307c90d668c1cab9425b40e088309","name":"@benjosivo/automation","dist-tags":{"latest":"1.11.0"},"versions":{"1.0.0":{"name":"@benjosivo/automation","version":"1.0.0","license":"MIT","_id":"@benjosivo/automation@1.0.0","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"bb6bd4a69e2bc657dc5188b98d14a7ca1703c610","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.0.0.tgz","fileCount":21,"integrity":"sha512-IhuL4CnnYR2hvj2VBdHeilakE0UL/3GnC6PXoLe1G3+TqNpFX1/fhb/3BYkj0FhZ4wX/Gcep6Lf/2hONMiC7XQ==","signatures":[{"sig":"MEYCIQCFHlq6qa82mU3ohJlxn/reWzHfd9UqIs8eLNa0XawGYQIhAJe+LOkmo/PrOJ4r91Qh60NKZenIacTa/AB9y/6EvwCX","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63452},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"548af09e9fdc14324c52a4082b2384287ae1c3dd","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"@benjosivo/mysql":"^1.2.0"},"_npmOperationalInternal":{"tmp":"tmp/automation_1.0.0_1788695366447_0.9298551547342582","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@benjosivo/automation","version":"1.1.0","license":"MIT","_id":"@benjosivo/automation@1.1.0","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"f90ea373cf88278b571859c3964449db4d32481b","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.1.0.tgz","fileCount":50,"integrity":"sha512-OqUgw68UVjd/Q4AxJDRIS7v3AliHa+aEwMC4JsoG1qIXFnJYOabl+Mt7rmyiiBsCw9WFm8f2KJAhe9vuSBodUA==","signatures":[{"sig":"MEQCIE070+rso35SUiDv6hiZeyZZP+ImHPcSxSeCyslkuLezAiB3KR+hbTSo9MB2QpEorFJyfA9rlkiR309m4KrPFuTqmQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":202556},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"446920d998da3b8d0e2eb9b9d096b40d63f31b2a","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/automation_1.1.0_1788760099387_0.25354091959225133","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@benjosivo/automation","version":"1.1.1","license":"MIT","_id":"@benjosivo/automation@1.1.1","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"e9763cdb68968c71997f178a23866e3cf7a4a489","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.1.1.tgz","fileCount":50,"integrity":"sha512-SO7X+jxzt6e3i+JZ4iSBhdMQeyO2zFSUPBfwZN9FtVuxz1Ck6qK4ZiQ5sWgVbeHvH9vvwx9ekIblGYy6VmB0oQ==","signatures":[{"sig":"MEYCIQC/5p7VhZ8m7pK1OqJtoDWYJUWyyQoeMXIjwu2L6+Y1pQIhAPXIry9wVvJ/M2wLgRXAazJYrBbiMxZ+1ofE6ddoprg9","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":206066},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"891f7a9c6f26b2190f4ad5110b6bddf276c7e73a","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/automation_1.1.1_1788891750477_0.16749938623978688","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@benjosivo/automation","version":"1.3.0","license":"MIT","_id":"@benjosivo/automation@1.3.0","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"2e37cbd7d930ff0602c13d8895590ce503a396df","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.3.0.tgz","fileCount":50,"integrity":"sha512-TPmfj4ZgIiglXas0f1MkmE9VqOed9UeSY5cHk9LI8IMS7vRJ4TipRsPOlyUUUwP6PIpLigpeFdy9ApffueFPNg==","signatures":[{"sig":"MEQCIHZBymBRbDSKeCNOlsW3QuVbkbVMNWeZAMCNvNSl+vhtAiAqKDXiqr0KDF4KUUyr8Tkg2pNgdDIWGOwq0YG8M4r3ow==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIHIdEXHl3cSZUtsyq3TcEtZf1oMp9GHcVCrEZyoGFF/mAiEAzBBvR7DsaztHZY0iPshb7Ekdw/r+cmBebAe8FglaXL4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":248802},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"dc6fb4a4b0f07f2de0303901fc3662bc9f6ca1c3","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/automation_1.3.0_1789682197788_0.8527986917274732","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"@benjosivo/automation","version":"1.4.0","license":"MIT","_id":"@benjosivo/automation@1.4.0","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"da843762554ea70d61d1b4f3ef6f16200e51bd58","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.4.0.tgz","fileCount":50,"integrity":"sha512-HCivADbGPNi88gQnguAzvDsoxXfueFsifE7e4p4EAv5suSsuloc/FNwujHhiiRFdfg34HZkpt1H2+N6RslNesA==","signatures":[{"sig":"MEYCIQDWPagXWMzxaW4FyCTKzKCh3k1cwEpuMOyA3+d7sp6HiQIhAIImYusefNyP9sr0bBGgf+6EG4RXXHzKsg+4x/UcdH8j","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCX/oRmsNqdMGc2BmYHucuEJ/Ic0R86nBuVB5af75cVdAIgXA7xWJjjfNfEUba6IjU/fiyLAZJmAEmqTxNHysbYcSY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":248694},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"2058173668a240939906a2bd2d40f32a0e3b0884","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/automation_1.4.0_1789711413366_0.24308347950084963","host":"s3://npm-registry-packages-npm-production"}},"1.5.0":{"name":"@benjosivo/automation","version":"1.5.0","license":"MIT","_id":"@benjosivo/automation@1.5.0","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"f404f9ce78d4cfbe13ac1563011d9652122d4ab9","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.5.0.tgz","fileCount":50,"integrity":"sha512-4+7wcNaqGpMJK+kd6J+9RO+eDAb24YIHtauQITQjHWhT0f/2qxBNs4YMOLOvZzFVUvE3JjIoY9HxDxhodk4oCA==","signatures":[{"sig":"MEUCIQC7Ux6I2xtcViHWAMw8APQ0/ZdDrcW75ABjXerOOmHdQwIgLmOYFucn/ivC/RXXZSL/FiGwYnCccIsCxUGxMz9pmVA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIHB1OctF9GdzpeZy0aC/hqXTmJOj2i5EkJQqu4U15fhIAiEA6sJgQeeq8nOl0MDqMEVvZb/UhknHQhj0ixNp1ssTmSs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":248917},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"09db26a6db6d87bc8b62b253303066e06f7ad061","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/automation_1.5.0_1789713167863_0.008194320294030888","host":"s3://npm-registry-packages-npm-production"}},"1.6.0":{"name":"@benjosivo/automation","version":"1.6.0","license":"MIT","_id":"@benjosivo/automation@1.6.0","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"cf697a073a8734e60ba9253c387346dd5746d27a","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.6.0.tgz","fileCount":50,"integrity":"sha512-Q9Ejx+Fi59qAytUw7BtB4/uYUT8sx8ru/J0Q84q0wcMBaOh4ock0FETNlsR9+hwwY4iFagkCDEKTBiBuCizdJA==","signatures":[{"sig":"MEUCIQCazYaqjhIdtoxa37e7HRwnEPFcOJwsJqphILQgqO6nMAIgYE03vYq3Vg1YlXZaaqG1YNzb9MyEw+iqP3dXmBOc5p0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDS2QxXX8PTJJwJrQEeSaXnGtbWW85HVCwbO6E2D2Z9kwIhAJL+B3ltllEPaPmOd7RNA9Yw9RE2NQDOUgsxOMzZjEkX","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":249048},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"9c7d1d1770569d55ea577259f0b48b798415e339","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/automation_1.6.0_1789714242939_0.6185505709426973","host":"s3://npm-registry-packages-npm-production"}},"1.7.0":{"name":"@benjosivo/automation","version":"1.7.0","license":"MIT","_id":"@benjosivo/automation@1.7.0","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"92da3c4f9a2b81a6419c157739f1ff2f3e51c69b","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.7.0.tgz","fileCount":50,"integrity":"sha512-dvr0bh4yqDjbbe8MF+/3Okpba+UXJHZwPmZKFWueZYGbIa5Tu0xE7kXWCMQSaeNyUjuQVbteep/xwHYjucjZyA==","signatures":[{"sig":"MEYCIQD7a0QUogn6kehHtYYXf/8TjYT8BGGYosVbPYyY5tLgmgIhAP0a0U+ZAw8uoXiGrx3elURsDgKc/GFOlsXiIq6KewnA","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQDULECKcg0oFKwffxC0CoA9B3QuYGcI5lxFasw26R25TAIgJqlZMRhStQYFvkVtTUieVYWtfI8OOcZkMbV/2KGE7WY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":252868},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"5b2145f87dafa9ce26fb805e4df1a6c51b6ec50c","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/automation_1.7.0_1790250818175_0.7603430729692666","host":"s3://npm-registry-packages-npm-production"}},"1.8.0":{"name":"@benjosivo/automation","version":"1.8.0","license":"MIT","_id":"@benjosivo/automation@1.8.0","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"016a8975cce436547df5fe975344b51670ef4381","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.8.0.tgz","fileCount":52,"integrity":"sha512-atOpuRWfXraFvwKWCCzonakUt8lOW/CQHdIUX+6IFzDxnoEdxFUc07yyd0RTg+XL9Tqko4pMRYelVRuyRUDvPQ==","signatures":[{"sig":"MEUCIA0nB+qOUILU/6UP1trx/0nWPv+F7kwpKI6dsn4lGLKQAiEAoCb9fR7DRC/sxi58xDWfseao8PRzKBzvRONBFr1Qtfk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCd4MyCfNEMs/0nQ94Jn3I8hBRTdLe/lKdvvBPDOD/VewIgfwssgIVeWetXG1gCXz2nfzsk1YGsdfJzIXZHeqEphXs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":281109},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"a437316e4e8b1c5e6315e2ef932c289cbbd44c57","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/automation_1.8.0_1790257515274_0.3214883712272465","host":"s3://npm-registry-packages-npm-production"}},"1.9.0":{"name":"@benjosivo/automation","version":"1.9.0","license":"MIT","_id":"@benjosivo/automation@1.9.0","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"33a629ec62a2c214501b529cd85098de8fd6bcd0","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.9.0.tgz","fileCount":52,"integrity":"sha512-gcRG+25qUK1wdJtsCnQqhj08mqO6P+Kw051QGizUQuF0Bt9/6/XD7OQa8Wz9XQoOaMhYJCFCoUMho7YS34lvwg==","signatures":[{"sig":"MEQCIBOZgIaSrxLgSCgo93JnuwD6ahuzfhwKnstQtDN1HTJIAiBFlJhiKQF3Yee/bPwo/RYgjZ3+X4/4KWCvqbeWR3rQeQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDtus5T2iXGyMndTl4VVHlYYuWd45xOjXkekVD1O/nTrQIhAOxS9s7yUTvVpcDg/WHFJyXot1t0MLNMLah10C0B6eby","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":281247},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"aa0efc538eb18a6fa5e254732eb12729465f1b57","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/automation_1.9.0_1790264351149_0.6111686001546084","host":"s3://npm-registry-packages-npm-production"}},"1.10.0":{"name":"@benjosivo/automation","version":"1.10.0","license":"MIT","_id":"@benjosivo/automation@1.10.0","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"homepage":"https://github.com/benjosivo/automation#readme","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"d78aee651afc2a2b13f0dd02235010d387845f50","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.10.0.tgz","fileCount":54,"integrity":"sha512-31Vhx1iLFHk18cydYLrAInvIhmkFX6WvYIoMD0/emwJC+DgitObOZ9ihbia4IZmwKS3Mu8YbggpoBj6Fk78a+w==","signatures":[{"sig":"MEUCIEzekuvn2LuPNJQllRHWRkOqy0drsEYFblIiUKzkAYQ4AiEAx8/Lw3HOgYR35DJ5InXah0WOts8rczpTR4ap2YT8b+g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQC4+gZtpW/Ht+McW+EjIUk5UQix/qz/u0OHwxwWi04tcAIhAPeAgFTgw/wzx0seAi3zudyj96RC/PO10NaZN7DiDgM9","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":301730},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"eb4f0b6ac99fb81708caed12589dbde0abad71aa","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/automation_1.10.0_1790324175091_0.6703289138637274","host":"s3://npm-registry-packages-npm-production"}},"1.11.0":{"_id":"@benjosivo/automation@1.11.0","bugs":{"url":"https://github.com/benjosivo/automation/issues"},"dist":{"shasum":"f5e9c37b4d9c18029df0a8ba2db9326b722986ca","tarball":"https://registry.npmjs.org/@benjosivo/automation/-/automation-1.11.0.tgz","fileCount":56,"integrity":"sha512-897nML5pLb529z1NZAfczNiNPbCKAwp74zh4MiZCHjid3qwd4mq1IssQUA4IwnAnrcdDqSTtNeZZyNpuz/UbMw==","signatures":[{"sig":"MEUCIQD+loKBqZ3Nq8+sdTJRVTVXoGhzeBavVJkVAXEzr5pxMgIgK2rX3Xu08ss0MDG5YbvOAT2pvp3/AmM5xDjaG413qfE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC0Msui/kMpWPRj+mt7OjPh+495IBUnO+INnDdUVIEDhAIgSGaAnGsCFByOEKjU/zVONbuh/Z6k9Xzj5Xs8C+veyBU="}],"unpackedSize":305799},"main":"dist/index.js","name":"@benjosivo/automation","type":"module","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./cron":{"types":"./dist/cron.d.ts","default":"./dist/cron.js"},"./proxy":{"types":"./dist/proxy.d.ts","default":"./dist/proxy.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./sql/*":"./sql/*","./package.json":"./package.json"},"gitHead":"69ec4c18740d64322c15cda43a2e1e5e6f5c43ce","license":"MIT","scripts":{"build":"tsc","prepublishOnly":"npm run build"},"version":"1.11.0","_npmUser":{"name":"benjosivo","email":"macklem75007@gmail.com"},"homepage":"https://github.com/benjosivo/automation#readme","repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"_npmVersion":"12.0.2","description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","directories":{},"maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"_nodeVersion":"24.15.0","dependencies":{"cors":"^2.8.6","redis":"^6.0.0","express":"^5.2.1","node-cron":"^4.2.1"},"_hasShrinkwrap":false,"devDependencies":{"react":"^19.2.8","typescript":"^7.0.2","@types/cors":"^2.8.19","@types/node":"^24.12.3","@types/react":"^19.2.18","@types/express":"^5.0.6","@benjosivo/mysql":"^1.2.2"},"peerDependencies":{"react":">=18","@benjosivo/mysql":"^1.2.0"},"peerDependenciesMeta":{"react":{"optional":true},"@benjosivo/mysql":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/automation_1.11.0_1790861202212_0.10667979315649423"}}},"time":{"created":"2026-09-06T11:49:26.336Z","modified":"2026-10-01T13:26:42.521Z","1.0.0":"2026-09-06T11:49:26.577Z","1.1.0":"2026-09-07T05:48:19.520Z","1.1.1":"2026-09-08T18:22:30.634Z","1.3.0":"2026-09-17T21:56:37.892Z","1.4.0":"2026-09-18T06:03:33.501Z","1.5.0":"2026-09-18T06:32:47.956Z","1.6.0":"2026-09-18T06:50:43.031Z","1.7.0":"2026-09-24T11:53:38.267Z","1.8.0":"2026-09-24T13:45:15.423Z","1.9.0":"2026-09-24T15:39:11.249Z","1.10.0":"2026-09-25T08:16:15.171Z","1.11.0":"2026-10-01T13:26:42.373Z"},"bugs":{"url":"https://github.com/benjosivo/automation/issues"},"license":"MIT","homepage":"https://github.com/benjosivo/automation#readme","repository":{"url":"git+https://github.com/benjosivo/automation.git","type":"git"},"description":"Runner de tâches planifiées : job, scheduler, executor, sur MySQL et Redis","maintainers":[{"name":"benjosivo","email":"macklem75007@gmail.com"}],"readme":"# @benjosivo/automation\r\n\r\nA cron/task runner. Task definitions, schedules, run history and concurrency locks live in MySQL (`Autom_*` tables, DDL in `sql/`); Redis holds the fast-path flags, the on-demand trigger queue and a mirror of what a running task reports. Task *code* stays with the host and is loaded dynamically at run time — the `Autom_Task` row is what binds a schedule to a module.\r\n\r\n```ts\r\nimport { startAutomationServer } from '@benjosivo/automation';\r\n\r\nconst port = Number(process.env.AUTOMATION_PORT);\r\n\r\nstartAutomationServer({\r\n    runner: 'avosplats',\r\n    port,\r\n    mysql: {\r\n        host: process.env.MyDB_HOST,\r\n        user: process.env.MyDB_USER,\r\n        password: process.env.MyDB_PASSWORD,\r\n        database: process.env.MyDB_NAME,\r\n    },\r\n    redisUrl: process.env.REDIS_URL,\r\n    corsOrigins: [`http://localhost:${port}`],\r\n    onError: handleError,\r\n});\r\n```\r\n\r\nThe package reads no environment variable of its own. Variable names, the alerting channel and the runner identity all belong to whoever embeds it, so they arrive through the config object.\r\n\r\n## Several runners, one database\r\n\r\n`runner` is the part worth understanding. Multiple runner processes may share a single database, each shipping a different set of task modules. `Autom_Task.Runner` says which process owns a row, and a runner only ever schedules, recovers and executes its own.\r\n\r\nWithout it, every runner loads every active schedule: each cron fires once per process, each runner tries to `import()` a module it does not ship — one failed run and one retry storm per night, for tasks that are not its business — and each boot marks the *other* runner's live runs as timed out. The scoping covers `getActiveSchedules`, `getAllTasks`, `timeoutStaleRuns`, `clearAllLocks`, the trigger queue key (`autom:trigger:queue:<runner>`), and a guard in `executeTask` that refuses a foreign task before creating a run row.\r\n\r\nTwo runners must not share a name.\r\n\r\n## Writing a task\r\n\r\nExport `run` matching `TaskModule`:\r\n\r\n```ts\r\nimport type { TaskRunContext, TaskRunResult } from '@benjosivo/automation';\r\n\r\nexport async function run(ctx: TaskRunContext): Promise<TaskRunResult> {\r\n    try {\r\n        return { output: (await doTheWork()) ?? 'OK' };\r\n    } catch (err: any) {\r\n        throw new Error(`${ctx.taskName} failed: ${err.message}`); // let the executor retry\r\n    }\r\n}\r\n```\r\n\r\nThe module is loaded in a `worker_threads` Worker whose bootstrap calls `init()` from `@benjosivo/mysql` with the options from your config, then imports the module and calls `run(context)`. Throw and the executor retries while `attempt <= RetryLimit`, spaced by `RetryDelaySeconds`, each attempt its own run row. Return and `output` is stored on the row.\r\n\r\n### Reporting progress\r\n\r\nA long task can say where it has got to, and the dashboard shows it while the task runs:\r\n\r\n```ts\r\nexport async function run(ctx: TaskRunContext): Promise<TaskRunResult> {\r\n    const batches = await listBatches();\r\n    for (const [i, batch] of batches.entries()) {\r\n        ctx.progress?.({ percent: Math.round((i / batches.length) * 100), step: `batch ${i + 1}/${batches.length}` });\r\n        await handle(batch);\r\n    }\r\n    return { output: `${batches.length} batches` };\r\n}\r\n```\r\n\r\n`progress` is **optional and must be called with `?.`**: the executor builds it inside the worker, so a module imported and called directly — a test, a script, a host running the task outside the runner — gets a context without it. Every field of the update is optional too; a task that knows its step but not its share sends only `step`, and the bar reads as indeterminate rather than claiming a figure.\r\n\r\n`console.log`, `console.error` and `console.warn` are forwarded as well, with no change to any task: they still reach the runner's stdout, and each line also becomes a dashboard line. A run keeps its last 200 lines; the percentage is coerced, clamped to `[0, 100]` and dropped if it is not a finite number.\r\n\r\nNone of this is persisted. It lives in the runner's memory, mirrored to `autom:run:<id>:progress` in Redis for ten minutes so that a dashboard opened just after a task finished still shows where it got to. The lasting record is the run row's `Output`.\r\n\r\n### Notifying WebSocket clients\r\n\r\nA task can push a message to the host's browsers, through the Redis channel its WebSocket server subscribes to:\r\n\r\n```ts\r\nctx.notify?.({ data: { recipeId }, path: '/recettes', targetUserId: userId });\r\n```\r\n\r\nThe worker does not publish itself: it hands the payload to the runner, which publishes it on `notifyChannel` (default `ws:broadcast`) over its one Redis connection, so a run opens no connection of its own. The payload is sent as `JSON.stringify` of `{ data, path, url, targetUserId }` — the same message the pre-package runner's `notifyClients` published — and routing it is the WebSocket server's business. `notify` is optional like `progress`, and fire-and-forget: a failed publish goes to `onError`, never to the task. Code running in the runner's own thread (`onCompleteFail`, a host route) imports `notifyClients` from the package instead.\r\n\r\n`Autom_Task.ModulePath` is a path to **compiled JS**, resolved against the process working directory. Moving a task file means updating that column — nothing checks it until the task runs.\r\n\r\n`@benjosivo/mysql` is a peer dependency: the host, the package and the tasks must share one pool. Its `executeMySQLQuery2` **returns** `{ error }` rather than throwing — check it or wrap it.\r\n\r\n## Schema\r\n\r\n| File | |\r\n|---|---|\r\n| `sql/001_autom_tables.sql` | The four tables |\r\n| `sql/002_autom_runner.sql` | `Autom_Task.Runner`, required by this version |\r\n| `sql/003_autom_lock_unique.sql` | **Required by any task with a `ConcurrencyGroup`**, and a no-op for the rest. See below |\r\n| `sql/004_schedule_run_setnull.sql` | **Required if anything deletes a schedule.** See below |\r\n\r\n## API\r\n\r\nMounted under `/api` by `startAutomationServer`, plus an unauthenticated `/healthcheck`. `GET|PATCH /tasks/:id`, `POST /tasks/:id/trigger`, `POST /tasks/trigger-by-name/:name`, CRUD on `/schedules` (mutations hot-reload the cron registry — no restart), `POST /schedules/reload`, `GET /runs`, `GET /runs/:id`, `GET /runs/active`, `GET /runs/events`, `GET /runs/:id/progress`. Listing routes are runner-scoped; a task belonging to another runner answers `409`.\r\n\r\n`POST /tasks/:id/trigger` and `POST /tasks/trigger-by-name/:name` answer once the run row exists, with its `runId`, and **409 when no run was created**: the task is already running, inactive, or its concurrency lock is held, with the reason in `error`. Until 1.9.0 both answered 200 immediately and the executor skipped those cases silently, leaving the caller nothing to follow. \"Already running\" is refused for these two routes only, as a guard against a double click: a cron firing on top of a slow previous run behaves as it always has.\r\n\r\n`GET /api/health` lives inside the router, so a host that mounts or proxies it has a liveness probe; `/healthcheck` remains outside for the standalone server.\r\n\r\n`GET /runs/events` is a Server-Sent Events stream: a `snapshot` of what is running on connect, then `start`, `progress`, `log` and `end` as they happen. `GET /runs/:id/progress` answers the same state for one run, from memory while it runs and from Redis for ten minutes after, then `null`. A host that wraps the router in `compression()` **must exclude `/runs/events`** — compression buffers the stream, and no header from here turns that off.\r\n\r\n`GET /runs` truncates `Output` to its first 200 characters. It is a `mediumtext`, and this listing is loaded five hundred rows at a time; what a list renders is the first line. **`GET /runs/:id` is where the column comes back whole** — that is what the dashboard's run modal opens. `GET /tasks/:id/runs` is not truncated: it defaults to twenty rows.\r\n\r\n`POST /schedules` and `PATCH /schedules/:id` answer **400** on an expression that either node-cron or the five-field parser rejects. Before 1.1.0 an unusable expression was stored, listed as active, and never fired — `registerJob()` warned to the console and returned.\r\n\r\n**There is no auth layer**: every `/api` route is open. Restrict what can reach it with `host` and put an authenticated host in front of it — see [Config](#config) for the two deployments. The CORS allowlist only constrains browsers, never a server-to-server call.\r\n\r\nImport `automationRouter` instead of calling `startAutomationServer` to mount it on a server of your own.\r\n\r\n## Driving a runner from another server\r\n\r\n```ts\r\nimport { createAutomationProxyRouter } from '@benjosivo/automation/proxy';\r\n\r\napp.use('/admin/api/automations', requireAdmin,\r\n        createAutomationProxyRouter({ baseUrl: 'http://127.0.0.1:8500/api' }));\r\n```\r\n\r\nOne catch-all handler forwards method, path and query string as received, so it covers every route the API has and every route it grows. It authenticates nothing — the guard you mount in front of it is the whole access control — and it needs `express.json()` upstream, since it re-serialises `req.body`. An unreachable runner answers `502` in the same `{ success, error }` envelope as the API's own failures, naming the base URL it dialled and the underlying cause:\r\n\r\n```\r\nAutomation runner unreachable at http://127.0.0.1:8500/api: connect ECONNREFUSED 127.0.0.1:8500\r\n```\r\n\r\n`/runs/events` is forwarded chunk by chunk rather than buffered, and the `timeoutMs` deadline is dropped once a response turns out to be a stream — otherwise the dashboard's live updates would die after ten seconds.\r\n\r\n**`127.0.0.1` in that example holds only while the runner shares a machine with the server mounting this.** `baseUrl` is dialled from inside the calling process: in separate containers it would name the *caller's own* loopback, where nothing listens, and every request would answer 502. Use the runner's internal service name there — see [Config](#config).\r\n\r\nMounting the router directly in an application process instead does not work: `executeTask` opens its worker in the calling process, and schedule mutations hot-reload the calling process's cron registry. The work would run in the web server and the runner's crons would not reload.\r\n\r\n## Cron helpers\r\n\r\n```ts\r\nimport { isValidCron, nextRuns, expandCron } from '@benjosivo/automation/cron';\r\n```\r\n\r\nA five-field parser with no imports at all, so a browser bundle can pull this subpath without dragging Express, Redis and MySQL in. It is what guards writes server-side and what previews occurrences client-side, which is why the preview cannot promise a firing the runner would refuse.\r\n\r\nDeliberately absent: a function turning an expression into a sentence. Occurrences are computed and cannot be wrong; a paraphrase can, and would be believed.\r\n\r\n## Dashboard\r\n\r\n```tsx\r\nimport { AutomationDashboard } from '@benjosivo/automation/react';\r\n\r\n<AutomationDashboard apiBase=\"/admin/api/automations\" lang=\"fr\" />\r\n```\r\n\r\nThe whole management surface as one component: statistics, running tasks, recent failures, next occurrences, a month/week/by-task calendar overlaying past runs with projected ones, task cards with manual triggering and activation, schedule editing with live validation, and a filterable run history. `react >= 18` is an optional peer dependency; hosts that never import this subpath do not need it.\r\n\r\n`apiBase` points at a `createAutomationProxyRouter` mount. Pass `fetcher` to reuse a host's own fetch wrapper — sessions, redirects, deploy detection.\r\n\r\nA running task shows a progress bar, its current step and its last log line, fed by `GET /runs/events`. **`EventSource` cannot carry a custom header, so `fetcher` does not apply to that connection**: a host authenticating with an `Authorization` header will only ever see `401` there. That is why the stream is not the only path — when it fails twice in a row without opening, the dashboard falls back to polling `/runs/active`, every 1.5 s while something is running and every 8 s otherwise. Meanwhile the stream is reopened every 2 s, then 4, 8… up to every 30 s, and polling stops as soon as it opens again — after a runner restart, for instance, which the proxy answers with `502` while it is down. Nothing has to be configured either way; a cookie-based session works over the stream unchanged.\r\n\r\nWhile the stream is up, `progress` and `log` are applied with no request at all, and the full reload drops to one every five minutes. It is not there to keep up — it reconciles. An `EventSource` reconnects silently and the events emitted while it was away are gone, so the dashboard reloads on every reconnection, which is the moment state can have drifted. The five-minute timer then only catches what no event can describe: a direct `UPDATE` in MySQL. It matches the TTL of the Redis active flag, which is how long the runner itself takes to notice one.\r\n\r\n### One task, on a host's own page\r\n\r\n```tsx\r\nimport { TaskTrigger } from '@benjosivo/automation/react';\r\n\r\n<TaskTrigger apiBase=\"/admin/api/automations\" name=\"importRecettes\" label=\"Importer\" lang=\"fr\" />\r\n```\r\n\r\nA button that starts the task named `name` (recorded as triggered by `api`), then shows its progress bar and current step while it runs, and its final status with the `Output` or the error once it ends, until a ✕ closes them and leaves the bare button. Several can share a page.\r\n\r\nIt cannot be started twice. The button is disabled from the click until the run ends; it follows a run of that task started elsewhere, whoever started it — on mount, and live while the page is open; and the runner answers 409 to a trigger for a running task, which is what holds when two tabs click in the same instant — the component then follows that run rather than showing an error.\r\n\r\nProgress comes from `GET /runs/events`, over **one `EventSource` per page and `apiBase`**, shared by every instance: one each would use up the six connections a browser allows per origin over HTTP/1.1. Past the trigger, a run costs one request — `GET /runs/:id`, for its `Output` once it ends. When the stream cannot open, which is what a host authenticating with an `Authorization` header gets (see below), the instance following a run polls `/runs/active` every 1.5 s through `fetcher` instead, and no longer sees runs started elsewhere until the stream reopens, which it attempts on the same backoff as the dashboard.\r\n\r\nAgainst a runner older than 1.10.0, whose trigger answers without a `runId`, the component finds its run through the stream's `start` event, and says the runner must be updated when it cannot.\r\n\r\nIt injects one stylesheet and reads every colour, font and radius from `--autom-*` custom properties whose defaults are declared on `:root`. Declare the same names on `.autom-root` to restyle it; a property set on the element beats one inherited from an ancestor, so the override wins whatever the stylesheet order. Under a CSP that forbids inline styles, import `AUTOM_CSS` and serve it yourself.\r\n\r\n## Known defects\r\n\r\nCarried over verbatim from the runner this package was extracted from. They are documented rather than silently fixed, because each fix changes what a machine does on an unattended nightly schedule.\r\n\r\n- **The concurrency lock needs `sql/003_autom_lock_unique.sql` to lock at all.** Without the unique index on `ConcurrencyGroup`, every acquisition inserts its own row and finds it free. With the index applied, the lock works as of 1.1.2 — see below for what 1.1.1 and earlier did instead.\r\n- **The heartbeat is written and read by nothing.** Two bugs kept it from even being stored until 1.2.0: `setCache` treats `expirationMs` as an absolute `PXAT` and the heartbeat passed a duration, so every key was written already expired; and the renewal interval was multiplied by 1000 a second time, giving 8 h 20 between beats for a 90 s TTL. Both are fixed, and `autom:run:<id>:heartbeat` now holds what it says. **No consumer has been written yet** — `timeoutStaleRuns()` sweeps every `running`/`pending` row of this runner at boot, on status alone, without consulting liveness. So a run whose worker dies mid-flight still shows as running until the next restart.\r\n- **Deleting a schedule fails once it has run**, until `sql/004_schedule_run_setnull.sql` is applied. The foreign key from `Autom_Task_Run` carries no `ON DELETE` clause, so MySQL restricts: every run the schedule produced holds it. The migration switches it to `SET NULL`, which keeps the runs — a run is a historical fact, and a manually triggered one already has no schedule.\r\n- **The trigger queue is not concurrency-safe.** It is a JSON array read-modify-written through `setCache` and drained whole on each 2-second poll. Two processes polling the same runner's queue can lose entries.\r\n\r\n## Fixed in 1.1.2 — `ConcurrencyGroup` was unusable\r\n\r\nBoth defects were in the lock, and either one alone was enough to make the feature worse than not having it. Neither had been noticed in the wild: no task in the shared database had a `ConcurrencyGroup` set, so the code path had never run.\r\n\r\n**Releasing a lock threw.** `releaseLock()` set `LockedAt = NULL` on a column `sql/001_autom_tables.sql` declares `NOT NULL`, which fails under `STRICT_TRANS_TABLES` — the default. `executeTask` swallows that rejection (`.catch(console.error)`), so the run looked healthy while the lock kept its holder forever and every later acquisition was refused. `clearAllLocks()` did the same thing at boot, awaited with no catch, so restarting — the one thing that should have cleared it — threw during startup instead. Neither call touches `LockedAt` now; the holder column alone says whether the group is free.\r\n\r\n**Acquiring a lock always succeeded.** The old `INSERT IGNORE … ON DUPLICATE KEY UPDATE` read `affectedRows` as 1-took-it / 0-someone-else-has-it. That mapping holds only without `CLIENT_FOUND_ROWS`, and mysql2 enables that flag by default, which makes an unchanged row report 1 as well. Measured against MySQL 8 on 10 September 2026: insert 1, contended 1, re-acquire 2 — every branch above zero, so `acquireLock()` returned `true` to every caller. It is now an `INSERT IGNORE` that only guarantees the row exists, followed by an `UPDATE … WHERE Autom_Task_Run_id IS NULL` whose `WHERE` cannot match a held lock, whatever the client flags count.\r\n\r\n## Platform\r\n\r\n`scheduler.ts` returns before `cron.schedule()` on win32 — cron never fires on a Windows dev machine. Test with `POST /api/tasks/:id/trigger`, or the dashboard's Run button.\r\n\r\n## Config\r\n\r\n`host` chooses the interface the API binds to. It defaults to `0.0.0.0`, which is what 1.0.x did unconditionally.\r\n\r\nBecause the API has no authentication of its own, this option and `baseUrl` on the proxy are one decision, taken twice — they must agree, and how they agree depends on where the two processes run:\r\n\r\n| | runner's `host` | proxy's `baseUrl` | what keeps the API private |\r\n|---|---|---|---|\r\n| One machine | `127.0.0.1` | `http://127.0.0.1:PORT/api` | the loopback: nothing off-host can address it |\r\n| Separate containers | `0.0.0.0` | `http://<internal-service-name>:PORT/api` | the network only |\r\n\r\n`127.0.0.1` is not an address that travels — it means \"whoever is asking\". A proxy in its own container that dials it reaches its own loopback, not the runner, so the pairing on the first row fails as a connection refusal and the proxy answers 502. That is the most common misconfiguration of this package.\r\n\r\nThe second row's cost is that the bind no longer guards anything. Keep the runner's port unpublished on the host and off any public domain: exposed, it lets anyone trigger tasks and edit schedules without ever meeting the guard in front of the proxy.\r\n","readmeFilename":"README.md"}