{"_id":"assignment-user-matcher","_rev":"28-82a45156d9587547fc05cee425541cc4","name":"assignment-user-matcher","dist-tags":{"latest":"1.16.1"},"versions":{"1.0.0":{"name":"assignment-user-matcher","version":"1.0.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.0.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"dist":{"shasum":"f4c64cd4b8fc6295c3895d0484505f87cdb79274","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.0.0.tgz","fileCount":13,"integrity":"sha512-gGy8yyvbtGQPZX/7pVGlEKagbl72rbP2HtV8nh7V79PpvNnZYFkCwqoqFGzaVvMcUssknHTXa9bxTEMznTpo2w==","signatures":[{"sig":"MEUCIQDBR7LbNPtf/ECE0HldBhKRM89M1cqohbLoDMBLH28LzgIgKcR3yOmEmgJqDiBCL2ev2NyNvtGClqgPB3Io3hAxQyI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":60195},"main":"index.js","_from":"file:assignment-user-matcher-1.0.0.tgz","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"viljvo","email":"viljar@testreel.com"},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/f7cb0e52b7233608beee94dd288cead9/assignment-user-matcher-1.0.0.tgz","_integrity":"sha512-gGy8yyvbtGQPZX/7pVGlEKagbl72rbP2HtV8nh7V79PpvNnZYFkCwqoqFGzaVvMcUssknHTXa9bxTEMznTpo2w==","_npmVersion":"10.9.2","description":"[![npm version](https://badge.fury.io/js/match-assignment.svg)](https://badge.fury.io/js/match-assignment) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/docker-build.yml/badge.svg)](https://github.com/ViljarVoi","directories":{},"_nodeVersion":"22.15.0","dependencies":{"redis":"^4.7.1"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"0.23.28","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.0.0_1747818800533_0.17532724781681286","host":"s3://npm-registry-packages-npm-production"}},"1.0.9":{"name":"assignment-user-matcher","version":"1.0.9","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.0.9","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"dist":{"shasum":"0dfb9e3acb6f2740678fc735b013f1e474c932d6","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.0.9.tgz","fileCount":13,"integrity":"sha512-Nhauu+yoRIKIs+lG8U4xx1tfXSdtUd42zRhj0T8bENTBEsKKdyukH8TH2MU7q3L7N0UQj5C2KIWtkwPEjpcsYA==","signatures":[{"sig":"MEYCIQC7dMncLqzlrwxVLsOsiQWl7zicAyndKew71kHO1joutwIhAIf6myNr86JwJgohG2Ng95zktqtVtk8gwZkCSkq7NIbH","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":60314},"main":"index.js","_from":"file:assignment-user-matcher-1.0.9.tgz","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"viljvo","email":"viljar@testreel.com"},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/c2a48b89f854fac13298a5baf32f4460/assignment-user-matcher-1.0.9.tgz","_integrity":"sha512-Nhauu+yoRIKIs+lG8U4xx1tfXSdtUd42zRhj0T8bENTBEsKKdyukH8TH2MU7q3L7N0UQj5C2KIWtkwPEjpcsYA==","_npmVersion":"10.9.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/docker-build.yml/badge.svg)](https://github","directories":{},"_nodeVersion":"22.15.0","dependencies":{"redis":"^4.7.1"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"0.23.28","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.0.9_1747819023845_0.41750043540667936","host":"s3://npm-registry-packages-npm-production"}},"1.0.10":{"name":"assignment-user-matcher","version":"1.0.10","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.0.10","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"dist":{"shasum":"977f7c877b6cf0a1bc94894f872d09107ba755f3","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.0.10.tgz","fileCount":13,"integrity":"sha512-JL3FxZ0SydaGihwwSJ/jidocM8ZLJv6t4gZQHaH2eowasAidf2P/CqcYXSc8d1vCrKL6RZAIbCo7S/dTx7OxrQ==","signatures":[{"sig":"MEQCIEE93yQsJ3DrgzweK7Dnx0dmaHuTKIuG1saqTwnwCeLwAiBoUtIBB1Cd9P8kSGggLEbeHbEcb3dnIXJvxdwNZWCvNA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":60313},"main":"index.js","_from":"file:assignment-user-matcher-1.0.10.tgz","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"viljvo","email":"viljar@testreel.com"},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/cbefa341a42fb17245963e5bb9896c95/assignment-user-matcher-1.0.10.tgz","_integrity":"sha512-JL3FxZ0SydaGihwwSJ/jidocM8ZLJv6t4gZQHaH2eowasAidf2P/CqcYXSc8d1vCrKL6RZAIbCo7S/dTx7OxrQ==","_npmVersion":"10.9.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.15.0","dependencies":{"redis":"^4.7.1"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"0.23.28","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.0.10_1747819843934_0.8199265613378912","host":"s3://npm-registry-packages-npm-production"}},"1.0.11":{"name":"assignment-user-matcher","version":"1.0.11","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.0.11","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"dist":{"shasum":"dd55b71f1f4b0624e671d39f84977aef901f0d5e","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.0.11.tgz","fileCount":14,"integrity":"sha512-zc91LyrgJoGwMesW4zeM8cPzq1mRmMkEW+kFeEMW51LQL+k4597QoXBuN3L9LtQrQQMFNMDFWUbZCIYVtJNJmw==","signatures":[{"sig":"MEUCIQC5h7ni6kL2vzQiYe91cfozacWEqaJVbvDe6HdvRKaj+wIgNOhqVArOVKyPVy1CATYSVBfPA/EhH/A4SvRLa/pkX9Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":134663},"main":"index.js","_from":"file:assignment-user-matcher-1.0.11.tgz","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"viljvo","email":"viljar@testreel.com"},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/77227a8628578e21efe5e300e1677e42/assignment-user-matcher-1.0.11.tgz","_integrity":"sha512-zc91LyrgJoGwMesW4zeM8cPzq1mRmMkEW+kFeEMW51LQL+k4597QoXBuN3L9LtQrQQMFNMDFWUbZCIYVtJNJmw==","_npmVersion":"10.9.3","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.19.0","dependencies":{"redis":"^4.7.1"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.0.11_1756921861635_0.029061755087330665","host":"s3://npm-registry-packages-npm-production"}},"1.0.14":{"name":"assignment-user-matcher","version":"1.0.14","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.0.14","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"dist":{"shasum":"da39a1335701cca9bf6012a1798a4b688ab87aea","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.0.14.tgz","fileCount":9,"integrity":"sha512-7o81LXigxBs/0AvXnzBJpuNAPV4vu1OhP9zbV6mZIA0Kt4wTvZtWjoCGftgKUoTOJa459KKLz37vISFfJryCPQ==","signatures":[{"sig":"MEUCIEzh4J1xpQJ7NmtPt6n5V1u0CD86KkBAO/48NQJlGBuUAiEAhqYx7xhI8CngntcEhmp/sAtuYIBZ1CrodRfp0p4+a/c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":50506},"main":"dist/index.js","_from":"file:assignment-user-matcher-1.0.14.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"viljvo","email":"viljar@testreel.com"},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/e68876f890a20aec5b20a6d1d6b025c1/assignment-user-matcher-1.0.14.tgz","_integrity":"sha512-7o81LXigxBs/0AvXnzBJpuNAPV4vu1OhP9zbV6mZIA0Kt4wTvZtWjoCGftgKUoTOJa459KKLz37vISFfJryCPQ==","_npmVersion":"10.9.4","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.21.1","dependencies":{"redis":"^4.7.1"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.0.14_1763413731499_0.7127917082377158","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"assignment-user-matcher","version":"1.1.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.1.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"dist":{"shasum":"1b29145b0d4568fc991277fd74b93cbdac5075bc","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.1.0.tgz","fileCount":9,"integrity":"sha512-3KM8uoAfyUH/iQSqCBg1cEP0FY3yvwW+wjmrRlUTRdhVYibzp/0bIVeqitcDXEJmntVmV0tyWsWu1+ts1YwDbg==","signatures":[{"sig":"MEUCIFn/0F2ZtNProS8tebB/6I+Ja9T8MFaFmqGdDc+ZBTrdAiEArcn4pKOuNn2vRNmGCx1/kIkpUO++7uuTUZ6UgeIcfn8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":58177},"main":"dist/index.js","_from":"file:assignment-user-matcher-1.1.0.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"viljvo","email":"viljar@testreel.com"},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/6a5cd961a41d198eba35fcd59f643eca/assignment-user-matcher-1.1.0.tgz","_integrity":"sha512-3KM8uoAfyUH/iQSqCBg1cEP0FY3yvwW+wjmrRlUTRdhVYibzp/0bIVeqitcDXEJmntVmV0tyWsWu1+ts1YwDbg==","_npmVersion":"10.9.4","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.21.1","dependencies":{"redis":"^4.7.1"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.1.0_1763810893642_0.5913625656049757","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"assignment-user-matcher","version":"1.1.1","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.1.1","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"dist":{"shasum":"c85c87bf547806b2785014ca5bef729645b326e7","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.1.1.tgz","fileCount":9,"integrity":"sha512-nSWbpvPPmYgbNIlo1Odr30Ird4o6zxUIrZBelqTk+Dfav03rmvJxtbPciHuI1oZaFFSzf76SwrdQNBsRskDqOg==","signatures":[{"sig":"MEYCIQDIcRHPduoBIk6w266sH3bWqe3T8rsbxlhujrqCeQmFzgIhAM0kkxqttuiq/j3BacylfOtaHNGX5idtMU/xBA4HaMfY","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":58384},"main":"dist/index.js","_from":"file:assignment-user-matcher-1.1.1.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"viljvo","email":"viljar@testreel.com"},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/b316de83d834b46f699d6732ddbc7c16/assignment-user-matcher-1.1.1.tgz","_integrity":"sha512-nSWbpvPPmYgbNIlo1Odr30Ird4o6zxUIrZBelqTk+Dfav03rmvJxtbPciHuI1oZaFFSzf76SwrdQNBsRskDqOg==","_npmVersion":"10.9.4","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.21.1","dependencies":{"redis":"^4.7.1"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.1.1_1764013252712_0.2949061989701607","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"assignment-user-matcher","version":"1.1.2","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.1.2","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"dist":{"shasum":"886c9fb8cd22534773d233a63aba129d15e75640","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.1.2.tgz","fileCount":9,"integrity":"sha512-5w+7gbtXX1H3fI3fNFMBvLAElEGPC79oKhyR4f8sfdQORKbAEkIef28R5Qw/j2PbzkP7ZxA+QizngbRT9T8Eyw==","signatures":[{"sig":"MEUCIB86hDU/AjResEsAHHVeMXNMYJ57FC8c/B6M09/ExHjGAiEAu1yfqzjpHb1eek4K/PryiqHsTby5mmI31k2nDq1gpOs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":65113},"main":"dist/index.js","_from":"file:assignment-user-matcher-1.1.2.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"viljvo","email":"viljar@testreel.com"},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/d7900b0b8fe34d89d8e6449301b9ca65/assignment-user-matcher-1.1.2.tgz","_integrity":"sha512-5w+7gbtXX1H3fI3fNFMBvLAElEGPC79oKhyR4f8sfdQORKbAEkIef28R5Qw/j2PbzkP7ZxA+QizngbRT9T8Eyw==","_npmVersion":"10.9.4","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.21.1","dependencies":{"redis":"^4.7.1"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.1.2_1765097359837_0.5095945916299764","host":"s3://npm-registry-packages-npm-production"}},"1.1.6":{"name":"assignment-user-matcher","version":"1.1.6","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.1.6","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"1e01739a67a26fe64dffd4dcf16042f8efc80dab","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.1.6.tgz","fileCount":9,"integrity":"sha512-iYrzG/zqcrr6qyK3OH6SB5whdNWCn43d6VJ2uVsttn9kdtDk9tBzOYkQ4SITj0NxY6rLIs41l9imqdc463Jsnw==","signatures":[{"sig":"MEQCIHc8AdJ0t27gfnPCyjARtjgj3+tOdeiAv66j9ab4tfMDAiBJiSQlw2n2AT60FDpsdQ03sAN5qMGb5KTQmoDnbHGrRQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.1.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":76761},"main":"dist/index.js","_from":"file:assignment-user-matcher-1.1.6.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/2e1078f25fbe55daa3c8e7f5ce0d2a4c/assignment-user-matcher-1.1.6.tgz","_integrity":"sha512-iYrzG/zqcrr6qyK3OH6SB5whdNWCn43d6VJ2uVsttn9kdtDk9tBzOYkQ4SITj0NxY6rLIs41l9imqdc463Jsnw==","repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"11.7.0","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.21.1","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.1.6_1765871082149_0.11695692030771787","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"assignment-user-matcher","version":"1.2.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.2.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"090031b37d7551566e332c67a5c817c5e505c56e","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.2.0.tgz","fileCount":45,"integrity":"sha512-k+4j3a4EJmCL5sa31qhAk9wisIQAg1NGZEh7cRaKP+axGk8e7vSzxQcPtDE5khtT0pcq+oUJ7mipto+ucufthw==","signatures":[{"sig":"MEUCIQD5Pv7z0LHyR6AYbf2nuO63PQV5OqCK4C+ZyWCoa3EJ+gIgLSucSHjMlk7ECuJ/3YamZ4WP286OgvjwBedeeJR6rck=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":193335},"main":"dist/index.js","_from":"file:assignment-user-matcher-1.2.0.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/f8b34e64220a95d3fdb67caace6bb312/assignment-user-matcher-1.2.0.tgz","_integrity":"sha512-k+4j3a4EJmCL5sa31qhAk9wisIQAg1NGZEh7cRaKP+axGk8e7vSzxQcPtDE5khtT0pcq+oUJ7mipto+ucufthw==","repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"11.7.0","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.21.1","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.2.0_1767627049129_0.2738776487474537","host":"s3://npm-registry-packages-npm-production"}},"1.2.1":{"name":"assignment-user-matcher","version":"1.2.1","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.2.1","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"9022f6a7d4eeb7afb18ba30afda3df3409c5da23","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.2.1.tgz","fileCount":45,"integrity":"sha512-WoKSS/NnVQz+AykEF6GV0VjOz++xDakxSzsrVGwO+dgK4Dyd1waLl37v/pfellqMzjJM1Pk6u/Bf/+1KKET06w==","signatures":[{"sig":"MEQCIDUjc3uTJn7v8paZnFDtEl9OJI4NKanf43P2i97vwKUaAiArGuwFBFbRcbxUcx174f+3NfRsC0atjVr9sL/FJfsVLw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":205523},"main":"dist/index.js","_from":"file:assignment-user-matcher-1.2.1.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/9a93c4967f96ca22463c174bd308033c/assignment-user-matcher-1.2.1.tgz","_integrity":"sha512-WoKSS/NnVQz+AykEF6GV0VjOz++xDakxSzsrVGwO+dgK4Dyd1waLl37v/pfellqMzjJM1Pk6u/Bf/+1KKET06w==","repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"11.10.0","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.22.0","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.2.1_1771251654609_0.06098324175346237","host":"s3://npm-registry-packages-npm-production"}},"1.2.2":{"name":"assignment-user-matcher","version":"1.2.2","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.2.2","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"f266659bee9ea1b2b77620fa0fc736b4a02537da","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.2.2.tgz","fileCount":54,"integrity":"sha512-8fOESPyRe+aAanl9ecHe+gw0b9RXIZNHgKjglAG0ALF7E85bSQh+JqwOSPdkAanXbR2os2LzTcZnK4PaCEFU1w==","signatures":[{"sig":"MEUCIAgW0W9RgeHy92uZCbzqtjH4fNORox7mU249tul8H+2KAiEAxkG+nuLCgZ2petg3mOG7/+SLBiq9BvLBJcflFlKvv3U=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.2.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":274723},"main":"dist/index.js","_from":"file:assignment-user-matcher-1.2.2.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","benchmark:learning":"ts-node benchmark-learning.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/a73af8d96b67865575b4f0bf5da342fc/assignment-user-matcher-1.2.2.tgz","_integrity":"sha512-8fOESPyRe+aAanl9ecHe+gw0b9RXIZNHgKjglAG0ALF7E85bSQh+JqwOSPdkAanXbR2os2LzTcZnK4PaCEFU1w==","repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"11.16.0","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.2.2_1781187772203_0.754692178030884","host":"s3://npm-registry-packages-npm-production"}},"1.2.3":{"name":"assignment-user-matcher","version":"1.2.3","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.2.3","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"81053b21396685e93374713da072816892225a0a","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.2.3.tgz","fileCount":61,"integrity":"sha512-3iF2a3pGJOkx6n26o71RKQPFv9CRZsCVWxLYGDIg+ROr7LLiaGtnzfZB1123CoW5Ql9RULF+GVIoILLDNWS7nw==","signatures":[{"sig":"MEYCIQDgTm47RiYkTCOzTe1sHYqP2wdNJ0T8Y9bj4PdsNHuOaQIhAKEqOTXsji4KJb5LKp4194z9w2Z9hool/afP4r7sH7y9","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.2.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":358016},"main":"dist/index.js","_from":"file:assignment-user-matcher-1.2.3.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","benchmark:learning":"ts-node benchmark-learning.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"_resolved":"/tmp/20c52e79f5f2b93d23d20f49b0c0ecc4/assignment-user-matcher-1.2.3.tgz","_integrity":"sha512-3iF2a3pGJOkx6n26o71RKQPFv9CRZsCVWxLYGDIg+ROr7LLiaGtnzfZB1123CoW5Ql9RULF+GVIoILLDNWS7nw==","repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"11.18.0","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.23.1","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.2.3_1783446455577_0.21697958139651097","host":"s3://npm-registry-packages-npm-production"}},"1.2.5":{"name":"assignment-user-matcher","version":"1.2.5","author":{"name":"Viljar Võidula"},"license":"SEE LICENSE IN LICENCE","_id":"assignment-user-matcher@1.2.5","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"9cabc32dc102c53539f309fc3e51adb3775884a7","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.2.5.tgz","fileCount":64,"integrity":"sha512-FAoxvD4/4DwTk80XV39MHbhS6+RJ4QZn5W/SvJfjD/Z4VY7lIYGkR/TDsA4eiPLSkZoxE8pnWpFwSY3/Eyr6mQ==","signatures":[{"sig":"MEYCIQCitqrZ5DH8/wnCkEGAUniNEWBdrJBmGwoQpmWUdpMPZAIhANmxXz3f57DMwARY0yXpnlt7sTuxPnIfikJtoHkFUolh","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.2.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":403400},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"a509b01d4cde774650bf913f0453283620e5b4ee","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:learning":"ts-node benchmark-learning.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.1","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.23.1","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.2.5_1783765270696_0.6321376584210465","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"assignment-user-matcher","version":"1.3.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.3.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"f8ba16eabc09f4bb3b73040fe2c154231ca7ceb9","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.3.0.tgz","fileCount":70,"integrity":"sha512-32pvgEQaq8C9FeP2Zumm26XQjsFRd53zNMlgekK2pdbxqoaA880qsvc3Gh+EXduig4s7OXZ9tPU9ePltLzEAeQ==","signatures":[{"sig":"MEQCIE0pAGvZGn5tlhThM8avVuqCbb/V/ibtI09+5OaoQL6MAiApvZtQbXIETS5y+Jb2kCijtPrzcM2jHsSNNxs1vUTsHQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":477665},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"a243014d9767b8083172045261cc58852a94fd80","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:learning":"ts-node benchmark-learning.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.1","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.23.1","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.3.0_1784379987099_0.4840285040161887","host":"s3://npm-registry-packages-npm-production"}},"1.6.0":{"name":"assignment-user-matcher","version":"1.6.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.6.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"70e6abe9607fa82552ce2dda6948c18374b3e4a9","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.6.0.tgz","fileCount":61,"integrity":"sha512-QZTpzUhYcp5pPICWT7JIi6LzyMINECezkVH9VbmvHVJhIV3N/6LLYoncB8G0lUQIaae2fcO7ZdyV6ejqjFDm7Q==","signatures":[{"sig":"MEQCIHAqOsvt7U6bj/BRl9+qQZEfQXcZMYFElszL5bu4i53MAiBzGvUW4U1eTEiZALCSGic9lXj2K8v7lnnIOdUGFQqjag==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.6.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":516517},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"87b702e39e5ba14739a8518acf22b768e64cc88d","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:learning":"ts-node benchmark-learning.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.1","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"22.23.1","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.6.0_1784824250705_0.7875582520647753","host":"s3://npm-registry-packages-npm-production"}},"1.7.0":{"name":"assignment-user-matcher","version":"1.7.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.7.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"b0cd623bf64094bf07151f088fef89ef3ccc422f","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.7.0.tgz","fileCount":64,"integrity":"sha512-Ul08aO6Ze5TBESULOs20p6dxcS80ehPgzErSxeq+7xCBCq7IXU9kRp7aI1ibhc4RJKFHoRfy8lh9GWVO5/lvYA==","signatures":[{"sig":"MEUCICj6/iWLLXdB8W7SecqIr/3v9gp1nV0iwCvSxtbNh3kkAiEA9zESNrAv45gmR+9uJKjkMS5q4S1SO0w+zvnwlR73WZk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.7.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":561384},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"c1b43bd0a97019910e8bb5efa775a6de4d1c116c","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:learning":"ts-node benchmark-learning.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.1","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.7.0_1784966175160_0.022521633174665823","host":"s3://npm-registry-packages-npm-production"}},"1.8.0":{"name":"assignment-user-matcher","version":"1.8.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.8.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"3264054e8b3cbc0d9aaf3c23472773064b0e86de","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.8.0.tgz","fileCount":67,"integrity":"sha512-rmGC3wVj0N0AFV2KvmkXhOvBKc1oxNn83OThMTIUWWLDXs0R4zVgYDAU1Zu976TV0F/V5EJmFdhNdNM0BX7vfA==","signatures":[{"sig":"MEUCIBG11b0ysqTYoWuZy33bs3jmBQYyqKUPEI0qHoH1wyJXAiEA38oPjn7r1ZLGMxwY6xzmvZitK4tbPvF2gtHbGrd339Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.8.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":632454},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"253057cf30fd32a4d3baa7f665ad218420744d9b","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:learning":"ts-node benchmark-learning.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.8.0_1785490286309_0.6049155510333664","host":"s3://npm-registry-packages-npm-production"}},"1.9.0":{"name":"assignment-user-matcher","version":"1.9.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.9.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"30c0b53b939261295966b49d3dca088c2b1cc903","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.9.0.tgz","fileCount":73,"integrity":"sha512-KO+zi2NVCTYGkqYFg9aIqNx9it8DGgwj+ZR6lIBeWQeam8p3/sBmD1rS3Y13+e3cRzjfydJzY850mmriljIK5Q==","signatures":[{"sig":"MEUCIQDWDTIXYKr+56wA71UJTXr+6pOzOhoyWnDilPBNsAkDigIgLf00iisszruxTi+V0CrhEJXzKR+eJFoNp8OrYo0txt0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.9.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":689113},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"f888dcdcee24c6b4a149cd4afeab41042139da8f","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:learning":"ts-node benchmark-learning.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.9.0_1786088440690_0.046382204276618655","host":"s3://npm-registry-packages-npm-production"}},"1.10.0":{"name":"assignment-user-matcher","version":"1.10.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.10.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"7848aa899ae6abc7c0569567e1a44192c488468a","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.10.0.tgz","fileCount":214,"integrity":"sha512-6EcbqMKWdyB4LV2Q5pXI7MUI9XNVc/Ib8HWBi0OWIU5czg2TVocT4XWi86S5DTHd8i7IrF6daftJwPjkmGD/Ag==","signatures":[{"sig":"MEQCIFp+T6kDE9pfT8VJ2scXzJ6noVILVU1bwvSLJqVDpsxKAiAodeEShlZMPvB2WT+gZdLjFct0Y3IRgoR4Cb7C1oClQA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.10.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1118508},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"ceb11b872140bb7fe37242ad113a7b76c1dbf0ee","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:learning":"ts-node benchmark-learning.ts","benchmark:scheduling":"ts-node benchmark-scheduling.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748","devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.10.0_1786974476136_0.5771306697587935","host":"s3://npm-registry-packages-npm-production"}},"1.11.0":{"name":"assignment-user-matcher","version":"1.11.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.11.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"348f4f98719e42273a893be95afd733333b78791","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.11.0.tgz","fileCount":217,"integrity":"sha512-wd59WJHqHq9ijR+kdztuN0OnPzyiEE74MclkvPtNWflHimKWjYRKd3rriPlVZPfxa+ZhZyBbDfLJT332NwWCvw==","signatures":[{"sig":"MEYCIQCSgpF1eMFxAwv9aCQjTZseZHJySSkUI6JjOl1NkkdeygIhAMcONMauMeDf4wEtO9paBVIXsohVsoK9zC2Ql1A0xCUS","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.11.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1170661},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"94d782233ffb049a8824c88d786d1973788ab4a0","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:learning":"ts-node benchmark-learning.ts","benchmark:scheduling":"ts-node benchmark-scheduling.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748","devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.11.0_1787216293678_0.9810962245426094","host":"s3://npm-registry-packages-npm-production"}},"1.12.0":{"name":"assignment-user-matcher","version":"1.12.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.12.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"d0e8c5b0c1ccbcbd39d5cf5f63dc42b852dbe8fc","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.12.0.tgz","fileCount":220,"integrity":"sha512-E3jgQH0DXyZinsaPZLy4x/paqoWqSXtXvtIvN2Sa+zNc1lUECXF9JN3YcrWjXPb95Do862EjOUYQvRuTlqSnYQ==","signatures":[{"sig":"MEQCIGBzBa8JmFUSpp7npeEZwTRmhBu6au5wIGqqpKsYMwVPAiBw/FJkoP54kb04D6k+YyW01zASzQ7WKNscO/E6Gyr6KQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.12.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1207178},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"326255ef7b75a86169d8f4e5d601b412717d2cc8","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:learning":"ts-node benchmark-learning.ts","benchmark:scheduling":"ts-node benchmark-scheduling.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748","devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.12.0_1787751581304_0.5712315566622115","host":"s3://npm-registry-packages-npm-production"}},"1.13.0":{"name":"assignment-user-matcher","version":"1.13.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.13.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"0e45568d5d299f106b4454414eb6004443b17881","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.13.0.tgz","fileCount":220,"integrity":"sha512-8K8LaDyCaBa+qwLb+2msQ4eAQoo354/LS5wbJf2hx8JyfA8iWKTJ2e+T5S2kX8YQgS5b3ch4Q1eeZ2MoZIQuRA==","signatures":[{"sig":"MEUCIF5pu/yH5ykYHfxySPR9yKeVOo/Z5jSWh03vJU9SNYZEAiEAip4f4x4KZTzV+3xmxyulux3rIC2ecngZ3SwDc7ux6qg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.13.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1230953},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"8d6c547a5fd717a43643a274963649567ee17a1f","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:learning":"ts-node benchmark-learning.ts","benchmark:scheduling":"ts-node benchmark-scheduling.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"redis":"5.10.0"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748","devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.13.0_1787809821255_0.05658244032870874","host":"s3://npm-registry-packages-npm-production"}},"1.14.0":{"name":"assignment-user-matcher","version":"1.14.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.14.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"16153f3d064db6dde4e6cbe0d029e8685adfc289","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.14.0.tgz","fileCount":235,"integrity":"sha512-JzUNPGqk7p42kL7Er8GlLYgnnrIVbv4L79yVGZT54tHgaQGQK0rG/2KRcCYO1z5tNmPY+NmLeZyLThl2QPXeaA==","signatures":[{"sig":"MEQCIH0qtv483B5PnLb2wrwDjGz2D3wbYUXWQWUdUlwAdS8JAiBkALs+mZuKrogo2TsiWWk8m31vnPS8Uv0KT21gQCDe8w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.14.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1289664},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./scheduling":{"types":"./dist/scheduling/index.d.ts","import":"./dist/scheduling/index.js","require":"./dist/scheduling/index.js"},"./package.json":"./package.json"},"gitHead":"2ce8c5327fb736a30cf49a9dd994115e6c072992","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:learning":"ts-node benchmark-learning.ts","benchmark:scheduling":"ts-node benchmark-scheduling.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"redis":"5.10.0"},"typesVersions":{"*":{"scheduling":["dist/scheduling/index.d.ts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748","devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.14.0_1788359170302_0.985986028918987","host":"s3://npm-registry-packages-npm-production"}},"1.15.0":{"name":"assignment-user-matcher","version":"1.15.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.15.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"648e6680994ef1b20478c203433b1eaf12f6fa16","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.15.0.tgz","fileCount":235,"integrity":"sha512-UcdqM6S3ymXib3o5iVS/0tOLFclPIq87Xb2SADxJqws4FxYeWmbnNkNT8k784Yezcj77t4PNrFFJoBx0wOFr1g==","signatures":[{"sig":"MEYCIQCdqRsuuw/vo1nY3BEbvViSNEsk4tULdhpzeSHXZvLwlgIhAOJVttJge+/Q5NSq0Isuaq3gsNAsVjrZ3DXA77DjoRp9","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.15.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1300487},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./scheduling":{"types":"./dist/scheduling/index.d.ts","import":"./dist/scheduling/index.js","require":"./dist/scheduling/index.js"},"./package.json":"./package.json"},"gitHead":"e8bf769f46bc6d46c06fec95a3332dd0853564e7","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:learning":"ts-node benchmark-learning.ts","benchmark:scheduling":"ts-node benchmark-scheduling.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.20.0","dependencies":{"redis":"5.10.0"},"typesVersions":{"*":{"scheduling":["dist/scheduling/index.d.ts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748","devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.15.0_1788507699306_0.3853389614377982","host":"s3://npm-registry-packages-npm-production"}},"1.15.1":{"name":"assignment-user-matcher","version":"1.15.1","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.15.1","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"06171f15b0288bf8edd12f0fec2777223cceba83","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.15.1.tgz","fileCount":235,"integrity":"sha512-b4AEnKD6neYLAVxqnahOtx2H0iF3mRJyWHCzZLnBJWbOEes5EiDf386wRdeez98a9q46lZUUpoZIvMl4dXITNw==","signatures":[{"sig":"MEUCIQD3asFeO0mCUA6/08025RTEfMOMVqOT4m5uy8HSX/W2GwIgR8o4WgGL42tmpWQwO7uhZsRx0hA03FfVIvNb93iq8iA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.15.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1314806},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./scheduling":{"types":"./dist/scheduling/index.d.ts","import":"./dist/scheduling/index.js","require":"./dist/scheduling/index.js"},"./package.json":"./package.json"},"gitHead":"5c10f943144f74081b307cfec3d33835ccc204df","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:learning":"ts-node benchmark-learning.ts","benchmark:scheduling":"ts-node benchmark-scheduling.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"redis":"5.10.0"},"typesVersions":{"*":{"scheduling":["dist/scheduling/index.d.ts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748","devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.15.1_1788515478914_0.30256861951556413","host":"s3://npm-registry-packages-npm-production"}},"1.16.0":{"name":"assignment-user-matcher","version":"1.16.0","author":{"name":"Viljar Võidula"},"license":"MIT","_id":"assignment-user-matcher@1.16.0","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"dist":{"shasum":"570ea29917061ad82c11f92579d3d33e4374e163","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.16.0.tgz","fileCount":244,"integrity":"sha512-RdwMHDxShQD8cfg6bbTVjYUtDgABqPz3Dc74gQb66VwbbXX/QG0qPx0x9SEWqZGY47+VjT3LN/XRwoH9gAIJaA==","signatures":[{"sig":"MEQCIHsT8ZWuypvda825YUYl3XrxxWYkRSUse9wxwkPkHsARAiAUJeeg1WOxPSPLKptFen/PMq9KkTs+yrC+A31+GeOA5w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.16.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1474360},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./scheduling":{"types":"./dist/scheduling/index.d.ts","import":"./dist/scheduling/index.js","require":"./dist/scheduling/index.js"},"./package.json":"./package.json"},"gitHead":"e4e029f209b26dd47bad0d93dd01631228d11648","private":false,"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prettier":"prettier --write .","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","release:patch":"bash ./scripts/release-patch-local.sh.example","prepublishOnly":"pnpm run build","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:learning":"ts-node benchmark-learning.ts","benchmark:scenarios":"ts-node benchmark-learning-scenarios.ts","benchmark:scheduling":"ts-node benchmark-scheduling.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"prettier":{"semi":true,"tabWidth":4,"printWidth":124,"singleQuote":true,"trailingComma":"all"},"repository":{"url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git","type":"git"},"_npmVersion":"12.0.2","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","directories":{},"_nodeVersion":"24.20.0","dependencies":{"redis":"5.10.0"},"typesVersions":{"*":{"scheduling":["dist/scheduling/index.d.ts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748","devDependencies":{"nyc":"15.1.0","chai":"4.5.0","mocha":"10.8.2","sinon":"20.0.0","rimraf":"4.4.1","ts-node":"10.9.2","typedoc":"^0.25.0","prettier":"3.5.3","typescript":"4.9.5","@types/chai":"4.3.20","@types/node":"18.19.103","@types/mocha":"10.0.10","@types/sinon":"10.0.20","typedoc-plugin-markdown":"3.17.1"},"_npmOperationalInternal":{"tmp":"tmp/assignment-user-matcher_1.16.0_1788597272629_0.5406462104366034","host":"s3://npm-registry-packages-npm-production"}},"1.16.1":{"name":"assignment-user-matcher","version":"1.16.1","repository":{"type":"git","url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git"},"main":"dist/index.js","module":"dist/index.js","types":"dist/index.d.ts","exports":{"./package.json":"./package.json",".":{"types":"./dist/index.d.ts","require":"./dist/index.js","import":"./dist/index.js"},"./scheduling":{"types":"./dist/scheduling/index.d.ts","require":"./dist/scheduling/index.js","import":"./dist/scheduling/index.js"}},"typesVersions":{"*":{"scheduling":["dist/scheduling/index.d.ts"]}},"author":{"name":"Viljar Võidula"},"license":"MIT","private":false,"dependencies":{"redis":"5.10.0"},"scripts":{"test":"nyc mocha tests/ --recursive --extension .ts --exit","benchmark:learning":"ts-node benchmark-learning.ts","benchmark:weights":"ts-node benchmark-weights.ts","benchmark:scenarios":"ts-node benchmark-learning-scenarios.ts","benchmark:scheduling":"ts-node benchmark-scheduling.ts","build:docs":"npx typedoc --plugin typedoc-plugin-markdown","lint":"eslint . --cache --cache-location node_modules/.cache/eslint/.eslintcache","lint:ci":"eslint . --quiet --cache --cache-location node_modules/.cache/eslint/.eslintcache","lint:fix":"eslint . --fix","check":"pnpm run lint:ci","prettier":"prettier --write .","build":"rimraf dist && tsc && mkdir -p dist/lua && cp src/lua/*.lua dist/lua/","prepublishOnly":"pnpm run build","release:patch":"bash ./scripts/release-patch-local.sh.example"},"prettier":{"semi":true,"singleQuote":true,"trailingComma":"all","printWidth":124,"tabWidth":4},"devDependencies":{"@eslint/js":"^9.39.5","@types/chai":"4.3.20","@types/mocha":"10.0.10","@types/node":"18.19.103","@types/sinon":"10.0.20","chai":"4.5.0","eslint":"^9.39.5","eslint-plugin-import-x":"^4.17.1","globals":"^17.12.0","mocha":"10.8.2","nyc":"15.1.0","prettier":"3.5.3","rimraf":"4.4.1","sinon":"20.0.0","ts-node":"10.9.2","typedoc":"^0.25.0","typedoc-plugin-markdown":"3.17.1","typescript":"4.9.5","typescript-eslint":"^8.69.0"},"packageManager":"pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748","gitHead":"905107b754280f9811e61ebd15c73ae04e438fd5","_id":"assignment-user-matcher@1.16.1","description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","_nodeVersion":"24.20.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-5lsBdy2V0G9TCAHSXiUvIeiUIoPhGvKl6bV0iLzwr3JOBbiLY9agkLriOOYTagGzv9CQo/WN4xnKwLoXBvgOvA==","shasum":"b9407d7d1f848f58bdceadc9743982cad86ac28b","tarball":"https://registry.npmjs.org/assignment-user-matcher/-/assignment-user-matcher-1.16.1.tgz","fileCount":244,"unpackedSize":1511830,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/assignment-user-matcher@1.16.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDBhTJubvuHqiiDiCN5WMoK+Qy/+aRbhHla6gAo0Yt3YAIhAI3J5L6c8SvHGzxiQRSsbFhlmXv60jNcRpgQXvAyLhGe"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3c49579f-e445-4ca9-8df4-31e143ff4e33"}},"directories":{},"maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/assignment-user-matcher_1.16.1_1789292098564_0.10802692444559137"},"_hasShrinkwrap":false}},"time":{"created":"2025-05-21T09:13:20.532Z","modified":"2026-09-13T09:34:58.983Z","1.0.0":"2025-05-21T09:13:20.755Z","1.0.9":"2025-05-21T09:17:04.076Z","1.0.10":"2025-05-21T09:30:44.132Z","1.0.11":"2025-09-03T17:51:01.824Z","1.0.14":"2025-11-17T21:08:51.676Z","1.1.0":"2025-11-22T11:28:13.847Z","1.1.1":"2025-11-24T19:40:52.895Z","1.1.2":"2025-12-07T08:49:19.988Z","1.1.6":"2025-12-16T07:44:42.277Z","1.2.0":"2026-01-05T15:30:49.268Z","1.2.1":"2026-02-16T14:20:54.758Z","1.2.2":"2026-06-11T14:22:52.339Z","1.2.3":"2026-07-07T17:47:35.848Z","1.2.5":"2026-07-11T10:21:10.831Z","1.3.0":"2026-07-18T13:06:27.312Z","1.6.0":"2026-07-23T16:30:50.859Z","1.7.0":"2026-07-25T07:56:15.308Z","1.8.0":"2026-07-31T09:31:26.476Z","1.9.0":"2026-08-07T07:40:40.848Z","1.10.0":"2026-08-17T13:47:56.289Z","1.11.0":"2026-08-20T08:58:13.838Z","1.12.0":"2026-08-26T13:39:41.436Z","1.13.0":"2026-08-27T05:50:21.395Z","1.14.0":"2026-09-02T14:26:10.452Z","1.15.0":"2026-09-04T07:41:39.463Z","1.15.1":"2026-09-04T09:51:19.044Z","1.16.0":"2026-09-05T08:34:32.789Z","1.16.1":"2026-09-13T09:34:58.710Z"},"bugs":{"url":"https://github.com/ViljarVoidula/assignment-user-matcher/issues"},"author":{"name":"Viljar Võidula"},"license":"MIT","homepage":"https://github.com/ViljarVoidula/assignment-user-matcher#readme","repository":{"type":"git","url":"git+https://github.com/ViljarVoidula/assignment-user-matcher.git"},"description":"[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher) [![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.","maintainers":[{"name":"viljvo","email":"viljar@testreel.com"}],"readme":"# `assignment-user-matcher`: Lightning-Fast, Tag-Based User & Assignment Matching\n\n[![npm version](https://badge.fury.io/js/assignment-user-matcher.svg)](https://badge.fury.io/js/assignment-user-matcher)\n[![CI Pipeline](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/ViljarVoidula/assignment-user-matcher/actions/workflows/npm-publish.yml)\n[![5xer Cloud](https://img.shields.io/badge/%E2%98%81%EF%B8%8F_5xer_Cloud-Sign_up-7C3AED)](https://5xer.com)\n\n<!-- Add other badges if you have them, e.g., build status, test coverage -->\n\n**Tired of inefficiently assigning tasks or struggling to connect the right users with the right work? `assignment-user-matcher` is a specialized Node.js library designed for high-performance, near real-time matching of a smaller pool of users to a large volume of assignments, primarily based on shared tags and priority.**\n\nIt leverages the speed and efficiency of Redis to deliver a robust solution perfect for scenarios like call centers, customer support queues, back-office operations, and more.\n\n> ### ☁️ Don't want to run your own Redis?\n>\n> **[5xer Cloud](https://5xer.com)** offers this matching engine as a fully managed service — no infrastructure, no maintenance sweeps, no Redis ops. On top of that, you get a polished no-code UI: a mobile-friendly PWA where workers see their assignments, accept, and complete work from anywhere — no client to install, no code to write.\n>\n> **[Sign up at 5xer.com →](https://5xer.com)** — getting started is free.\n\n## The Challenge: Efficiently Connecting Users to Work\n\nIn many applications, from customer service platforms to internal task management systems, the core challenge remains the same: how do you quickly and accurately assign incoming work (assignments) to the best available person (user)? This becomes particularly complex when dealing with:\n\n- **Large volumes of incoming assignments:** Tasks can pile up quickly, demanding immediate attention.\n- **Real-time requirements:** Users expect instant responses, and assignments need prompt handling.\n- **Varying skill sets and priorities:** Not all users are equipped for all tasks, and some assignments are more critical than others.\n\nTraditional matching systems can become bottlenecks, leading to delays, suboptimal pairings, and frustrated users or customers.\n\n## Introducing `assignment-user-matcher`: Your Solution for Smart Task Distribution\n\n`assignment-user-matcher` tackles these challenges head-on by providing a specialized engine optimized for a common pattern: a relatively small, active set of users (like support agents or back-office staff) processing a continuous, large stream of assignments.\n\n**Key Features:**\n\n- **Tag-Based Matching:** Intelligently connect users and assignments based on an intersection of descriptive tags (e.g., skills, language, department).\n- **Priority-Aware:** Ensure that high-priority assignments are surfaced and processed first.\n- **Redis-Powered:** Utilizes Redis for its in-memory data structures, enabling blazing-fast lookups and operations.\n- **Optimized for Specific Scenarios:** Excels where a smaller group of users handles a large number of assignments.\n- **User Backlog Management:** Prevents users from being overwhelmed by limiting the number of assignments they can be tentatively matched with.\n\n## Why `assignment-user-matcher`?\n\n- 🚀 **Speed:** Designed for near real-time performance, ensuring assignments are matched and delivered swiftly.\n- 🎯 **Accuracy:** Improves the quality of matches by considering relevant tags and priorities.\n- ⚙️ **Efficiency:** Optimizes resource allocation, ensuring users are working on the most appropriate and urgent tasks.\n- 😌 **Improved Experience:** Leads to faster response times for customers and a more streamlined workflow for users.\n- 💡 **Scalable (for its niche):** Handles a large throughput of assignments efficiently.\n\n## Core Concepts: How It Works\n\n`assignment-user-matcher` operates on a few key principles:\n\n1.  **Users and Assignments Have Tags:**\n\n    - **Users:** Each user is defined by an ID and a set of `tags` representing their skills, capabilities, or any other relevant attributes (e.g., `['english', 'billing_support', 'tier_1']`).\n    - **Assignments:** Each assignment has an ID, `tags` describing its requirements (e.g., `['english', 'billing_inquiry']`), and a `createdAt` timestamp (or a custom priority score) to determine its urgency.\n\n2.  **Matching via Tag Intersection:**\n    The core matching logic finds users whose tags have a common intersection with an assignment's tags.\n\nWhen using `routingWeights` with the built-in matcher:\n\n- Only **positive** weights (`> 0`) make assignments eligible.\n- Weight `0` is a **hard veto** (exact tag or suffix wildcard like `lang:*`).\n- Wildcards are **suffix-only** (`prefix*`). Patterns like `skill:*:node` are not treated as wildcards.\n- If a user has `routingWeights` but no positive entries, assignments stay queued.\n\nHard veto configuration example:\n\n```ts\nawait assignmentMatcher.addUser({\n    id: 'agent_1',\n    tags: [],\n    routingWeights: {\n        'support:*': 100, // eligible\n        'lang:*': 0, // hard veto for all language-tagged assignments\n        default: 0, // hard veto for default matching fallback\n    },\n});\n```\n\nNote: `usingDefaultMatchScore` is an internal implementation detail and is **not** a public option.\nConfigure hard veto behavior only through `routingWeights` values.\n\nAssignments can also veto specific users from their side: setting `vetoedUsers: ['user_1']` on an\nassignment guarantees those users are never matched to it, no matter how well their tags, weights,\nor other criteria fit (this also overrides workflow targeting).\n\n3.  **Prioritization Engine:**\n    Assignments are typically processed in the order they are received or by a custom prioritization function you can provide. This ensures that older or more critical assignments get attention first. The library aims to match the highest priority assignments to available, suitable users.\n\n4.  **Redis as the Backbone:**\n    Redis is used to store user availability, assignment details, and manage the matching process. Its speed is crucial for the near real-time performance of the library. Users are maintained in sorted sets based on their backlog size and last activity, ensuring fair and efficient distribution.\n\n5.  **User Backlog (`maxUserBacklogSize`):**\n    To prevent any single user from being assigned too many tasks at once (even if they are a match), `assignment-user-matcher` maintains a backlog for each user. New assignments are only matched if a user's backlog is below this threshold.\n\n## Real-World Use Cases\n\n`assignment-user-matcher` is particularly well-suited for:\n\n- **📞 Call Centers:** Routing incoming calls (assignments) to the agent (user) with the right language skills, product knowledge, and availability.\n- **🎫 Customer Support Ticketing:** Assigning new support tickets to support agents based on their expertise (e.g., \"technical_issue,\" \"api_support\") and the ticket's urgency.\n- **🗄️ Back-Office Operations:** Distributing tasks like data entry, document verification, or claims processing to available staff members based on task type and employee skills.\n- **🚗 Gig Economy Platforms (Niche):** Matching available service providers to incoming customer requests where the provider pool is managed and assignments are plentiful (e.g., a dispatch system for a fleet of delivery drivers).\n- ** Leads Distribution:** Assigning sales leads to representatives based on territory, product specialization, or lead score.\n\n## Getting Started\n\n### 1. Installation\n\n```bash\nnpm install assignment-user-matcher redis\n# or\nyarn add assignment-user-matcher redis\n# or\npnpm add assignment-user-matcher redis\n```\n\n### 2. Basic Usage\n\nHere's a simple example to get you up and running:\n\n```javascript\nimport AssignmentMatcher from 'assignment-user-matcher'; // or `import { AssignmentMatcher } from 'assignment-user-matcher';`\nimport { createClient } from 'redis';\n\nasync function runExample() {\n    // Connect to your Redis instance\n    const redisClient = createClient({\n        // url: 'redis://your-redis-host:6379' // Optional: specify Redis URL if not default\n    });\n    await redisClient.connect();\n    console.log('Connected to Redis.');\n\n    const assignmentMatcher = new AssignmentMatcher(redisClient, {\n        redisPrefix: 'exampleApp:', // Good practice to namespace your Redis keys\n        maxUserBacklogSize: 5, // Max 5 assignments per user backlog\n        enableDefaultMatching: true,\n    });\n\n    // Add a user\n    await assignmentMatcher.addUser({\n        id: 'agent_007',\n        tags: ['english', 'technical_support', 'vip_clients'],\n    });\n    console.log(\"User 'agent_007' added.\");\n\n    // Add an assignment\n    await assignmentMatcher.addAssignment({\n        id: 'ticket_12345',\n        tags: ['english', 'technical_support'],\n        createdAt: new Date().getTime(), // Use current time for priority\n    });\n    console.log(\"Assignment 'ticket_12345' added.\");\n\n    // Run the matching process\n    // This will attempt to match pending assignments to available users\n    console.log('Running matching process...');\n    await assignmentMatcher.matchUsersAssignments();\n\n    // Check current assignments for the user\n    const userAssignments = await assignmentMatcher.getCurrentAssignmentsForUser('agent_007');\n    console.log(`Assignments for 'agent_007':`, userAssignments);\n\n    if (userAssignments && userAssignments.length > 0) {\n        console.log(`User 'agent_007' is matched with assignment '${userAssignments[0].id}'.`);\n    } else {\n        console.log(`User 'agent_007' has no assignments currently.`);\n    }\n\n    // Clean up (optional, depending on your Redis setup)\n    // await redisClient.flushDb(); // Be careful with this in production!\n    await redisClient.quit();\n    console.log('Disconnected from Redis.');\n}\n\nrunExample().catch(console.error);\n```\n\n### 3. Workflow Quick Start\n\nWorkflows are easiest to use with the builder helpers and the `executeWorkflow()` convenience method.\n\n```typescript\nimport AssignmentMatcher, { workflow } from 'assignment-user-matcher';\n\nconst matcher = new AssignmentMatcher(redisClient, {\n    enableWorkflows: true,\n    redisPrefix: 'exampleApp:',\n});\n\n// Optional if your Redis client is already connected.\n// Useful when the matcher is created with a closed client.\nawait matcher.waitUntilReady();\n\nconst onboardingWorkflow = workflow('onboarding', 'Onboarding')\n    .step('profile')\n    .name('Complete profile')\n    .assignment({ tags: ['profile'], title: 'Complete your profile' })\n    .targetUser('initiator')\n    .defaultNext('review')\n    .done()\n    .step('review')\n    .name('Manager review')\n    .assignment({ tags: ['review'], title: 'Review onboarding' })\n    .targetUser({ tag: 'managers' })\n    .defaultNext(null)\n    .done()\n    .build();\n\n// Registers the definition if needed, then starts the workflow instance.\nconst instance = await matcher.executeWorkflow(onboardingWorkflow, 'agent_007', {\n    source: 'signup',\n});\n\nconsole.log(instance.id, instance.currentStepId);\n```\n\nYou can still call `registerWorkflow()` and `startWorkflow()` separately if you want explicit lifecycle control.\n\n#### Escalation ladders (`escalateTo`)\n\nA step whose timeout expires normally fails the run (after any `maxRetries`). `escalateTo(stepId)` turns that timeout into a forward hop instead — the classic page-the-primary-then-the-secondary ladder:\n\n```typescript\nconst escalation = workflow('oncall', 'On-call escalation')\n    .step('page-primary')\n    .assignment({ tags: ['sev:1', 'oncall-primary'] })\n    .targetUser({ tag: 'oncall-primary' })\n    .timeout(60_000)\n    .escalateTo('page-secondary')\n    .route('result.acked === true', 'mitigate')\n    .done()\n    .step('page-secondary')\n    .assignment({ tags: ['sev:1', 'oncall-secondary'] })\n    .targetUser({ tag: 'oncall-secondary' })\n    .timeout(300_000)\n    .escalateTo('page-manager')\n    .done();\n// … 'page-manager' has no escalateTo: the ladder ends and the run fails,\n// which is the honest \"escalated to the top and nobody answered\" state.\n```\n\nWhen a step escalates, its outstanding assignment is removed so a late responder cannot act on a superseded tier, `context._escalatedFrom` and `context._escalationDepth` are set, and a `step.escalated` transition is emitted (in addition to `step.expired`). Escalating does **not** consume `maxRetries` — a different person getting the work is not another attempt at the same one. `maxEscalationDepth(n)` (default 10) stops a mis-wired cycle from climbing forever.\n\nRegistration rejects an escalation target that doesn't exist, a step escalating to itself, escalation without a resolvable timeout, and escalation from inside a parallel group.\n\nTargeting matters: `targetUser({ tag: '…' })` routes the step's assignment through ordinary tag matching (so fairness, backlog caps, and the rolling-window grant cap all apply), while `targetUser('<userId>')` makes it workflow-targeted and therefore exempt from the fairness window cap. For tiered escalation you almost always want the tag form.\n\nA workflow-targeted assignment is claimed queued→pending for its target through the same atomic claim gate as organic matching, whether or not the worker's tags/weights would ever have matched it — so `acceptAssignment`/`completeAssignment` work identically and the workflow advances. A worker who rejects a targeted assignment is not re-granted it (the standard rejection contract); the step then runs its response-deadline/escalation machinery.\n\nPlain object workflow definitions are also accepted. The library now fills in sensible defaults:\n\n- `version` defaults to `1`\n- `initialStepId` defaults to the first step ID\n- invalid definitions fail early during registration with clear validation errors\n\n### 4. Scaling & Reliability for Workflows\n\nThe workflow engine is designed to run with many orchestrator replicas over large instance counts. The key building blocks:\n\n- **Indexed step timeouts** — step expirations are tracked in a sorted set and claimed atomically, so `processExpiredWorkflowSteps()` is O(due steps) instead of scanning every instance, and an expiry fires on exactly one replica.\n- **In-place conflict retries** — optimistic-lock (`VERSION_MISMATCH`) conflicts are retried immediately with a fresh read instead of waiting for the orphan-reclaim window.\n- **Delayed retry queue** — failed events are scheduled for retry with exponential backoff (`workflowRetryBackoffMs`, default 1000ms initial delay) and drained automatically by the orchestrator; retries are claimed atomically across replicas.\n- **Flow rate control** — tune orchestrator throughput per replica: `workflowEventBatchSize` (XREADGROUP COUNT, default 10), `workflowPollBlockMs` (blocking poll wait, default 5000ms), and `workflowMaxEventsPerSecond` to cap event processing (applies to both stream consumption and retry draining; unlimited when unset).\n- **Per-event idempotency markers** — processed-event markers carry their own TTL (`workflowIdempotencyTtlMs`), and replayed step executions generate deterministic assignment IDs so crash-replays never create duplicate assignments.\n- **Shared circuit breaker** — set `circuitBreakerShared: true` to converge breaker state across replicas through a shared Redis failure counter (recommended for multi-replica deployments).\n- **Instance retention** — set `workflowInstanceRetentionMs` to expire terminal (completed/failed/cancelled) instances and clean their registry/index entries. Unset (the default) keeps them forever, matching previous behavior; a value in the range of days to weeks is recommended for high-volume deployments.\n- **Event-stream trimming** — set `workflowEventStreamAutoTrim: true` to keep the `events:stream` Redis stream bounded. Trimming runs after event processing and on the orchestrator's reclaim interval, and is anchored to the consumption frontier — every entry delivered _and_ acknowledged by every consumer group — so it can never drop an unconsumed event, no matter how far consumers lag (the flip side: the stream still grows while consumers are down). Unset (the default), the stream is never trimmed; `trimWorkflowEventStream()` exposes the same trim for hosts running their own tick.\n\nOperational helpers:\n\n```typescript\nconst matcher = new AssignmentMatcher(redisClient, {\n    enableWorkflows: true,\n    circuitBreakerShared: true,\n    workflowInstanceRetentionMs: 30 * 24 * 60 * 60 * 1000, // 30 days\n});\n\n// Machine steps can run through named handlers with timeout enforcement\n// (step.timeoutMs / defaultTimeoutMs); falls back to executeMachineTask.\nmatcher.registerMachineHandler('score-lead', async ({ instance, step }) => {\n    return { score: 42 };\n});\n\n// One-time migration for deployments created before the indexes existed\nawait matcher.backfillWorkflowIndexes();\n\n// Periodic maintenance for deployments without retention configured\nawait matcher.pruneWorkflowInstances(30 * 24 * 60 * 60 * 1000);\n\n// Monitoring: active instances, retry queue depth, DLQ size, stream stats\nconst metrics = await matcher.getWorkflowMetrics();\n```\n\n## API Reference\n\nThe main class provided is `AssignmentMatcher`.\n\n### `new AssignmentMatcher(redisClient, options?)`\n\nCreates a new `AssignmentMatcher` instance.\n\n- `redisClient`: An initialized and connected `redis` client instance (version 4+).\n- `options` (Optional): Configuration options for the matcher. See [Options](#options) below.\n\n### `addUser(user: User): Promise<void>`\n\nAdds or updates a user in the system.\n\n- `user`: An object representing the user.\n    - `id: string`: Unique identifier for the user.\n    - `tags: string[]`: Array of tags associated with the user.\n    - `maxBacklogSize?: number`: Optional per-user backlog cap overriding the matcher-wide `maxUserBacklogSize` in every matching path (`0` = receive nothing; negative or non-numeric values are ignored). The cap bounds the user's **total** pending backlog: a user already holding work is only topped up to the cap, never past it. The fairness rolling-window auto-cap derivation stays team-level and keeps using the global value.\n\n### `addAssignment(assignment: Assignment): Promise<void>`\n\nAdds a new assignment to be matched.\n\n- `assignment`: An object representing the assignment.\n    - `id: string`: Unique identifier for the assignment.\n    - `tags: string[]`: Array of tags required for the assignment.\n    - `createdAt: number`: Timestamp (e.g., `new Date().getTime()`) indicating when the assignment was created. Used for default prioritization (older assignments get higher priority). Can be influenced by `prioritizationFunction`.\n    - `priority?: number | string`: Optional explicit priority. If `prioritizationFunction` is used, this might be an input to it.\n    - `vetoedUsers?: string[]`: Optional list of user IDs that must never receive this assignment, regardless of any other matching criteria (tags, `routingWeights`, priority, or workflow targeting). Vetoed assignments are excluded from a user's candidate pool before scoring, so the veto adds no per-match overhead. To change an assignment's vetoes, remove and re-add the assignment.\n\n### `matchUsersAssignments(): Promise<void>`\n\nTriggers the matching process. This method iterates through pending assignments and tries to match them with suitable, available users based on tags and user backlog capacity. With no `userId`, every eligible user is evaluated in parallel by default and contested assignments go to whoever's claim resolves first - see the `fairness` option in [Options](#options) to make the best-scoring candidate win deterministically (`'best-match'`), or to additionally balance load across users (`'balanced'`, `'spread-work'`).\n\n### `getCurrentAssignmentsForUser(userId: string): Promise<Assignment[]>`\n\nRetrieves the current list of assignments that a user is tentatively matched with (i.e., in their backlog).\n\n- `userId: string`: The ID of the user.\n\n### `getPendingAssignmentsWithAge(options?: { limit?: number }): Promise<PendingAssignmentInfo[]>`\n\nRetrieves current pending assignments, including who owns each assignment and how long it has been pending. Reads are bounded (the pending hash is paged with `HSCAN` and each page's lookups are batched), and corrupt entries are skipped. Pass `{ limit }` (clamped to 1000) to hydrate only the top-N longest-pending assignments instead of every one; without options the behavior is unchanged.\n\n- `assignment`: The assignment payload.\n- `ownerId`: The current owner user ID, or `null` if missing.\n- `pendingForMs`: Elapsed pending time in milliseconds.\n- `pendingSince`: Unix timestamp (ms) when the assignment entered pending state (derived from the assignment's own response deadline — `escalation.respondWithinMs` or `matchExpirationMs`).\n- `expiresAt`: Unix timestamp (ms) when pending expiration is scheduled.\n\n### `getCompletedAssignments(options?: { cursor?, limit? }): Promise<CompletedAssignmentPage>`\n\nLists terminal assignments from the completed store (completions and failures share it), cursor-paged like the other bounded reads. Each entry is stamped `_status: 'completed'` or `'failed'` (the terminal marker also distinguishes them: `_completedBy`/`_completedAt` vs `_failedBy`/`_failedAt`). The store is only pruned when `completedAssignmentsRetentionMs` is configured (see [Options](#options)); pagination is best-effort under a concurrent purge — the cursor is a positional offset, so entries deleted between pages can shift what a later page returns. Loop until `hasMore` is false and re-page if completeness matters.\n\n### User status & workload queries\n\nThree bounded APIs answer \"who is in the pool and how loaded are they?\" without materializing whole Redis structures:\n\n#### `getUsersPaginated(options?: UserQueryOptions): Promise<UserQueryResult>`\n\nPaginated user scan with composable filters (logical AND):\n\n```ts\nconst page = await matcher.getUsersPaginated({\n    status: 'active', // 'all' | 'active' | 'paused' | 'idle'\n    idleForMs: 10 * 60_000, // last touchUser() older than this\n    hasBacklog: true, // pending backlog non-empty\n    atCapacity: true, // backlog reached the effective cap\n    limit: 100, // clamped to [1, 1000]\n    includeAssignments: true, // also return pending/accepted ids\n    includeTotal: true, // also return the unfiltered pool size\n    cursor: null, // pass nextCursor verbatim to continue\n});\n```\n\nEach `UserSummary` carries `userId`, the stored `user` record, `paused`, `lastActiveAt`, `backlog`, `acceptedCount`, the effective `maxBacklogSize` (per-user override honored), and `atCapacity`. `status: 'idle'` without an explicit `idleForMs` falls back to the matcher's `idleUserTimeoutMs` (and matches nobody when neither is set). Two pagination caveats, both consequences of paging the users hash with `HSCAN`: a scan page is never split, so a returned page may exceed `limit` by less than one scan page; and filters are applied during the scan, so a page may return fewer than `limit` users while `hasMore` is still true. Loop on `hasMore` until it is false. With `includeAssignments`, the returned `acceptedAssignmentIds` come straight from the per-user index and are not verified against the accepted store — a rare stale id can appear; `getActiveAssignmentsForUser()` is the verified (and self-healing) read.\n\n#### `getUserSummaries(userIds: string[]): Promise<UserSummary[]>`\n\nOrder-preserving batch version for a known set of ids (chunked pipelined reads). Missing or corrupt user records are omitted.\n\n#### `getActiveAssignmentsForUser(userId: string): Promise<ActiveAssignmentInfo[]>`\n\nA user's accepted (in-progress) assignments, newest first, each with its `acceptedAt` timestamp — the complement to `getCurrentAssignmentsForUser()`'s pending backlog. Backed by a per-user accepted index (`user:{id}:accepted`) maintained on accept and on every terminal transition (complete/fail/remove/SLA sweeps). Two freshness notes:\n\n- The index is maintained from the version that introduced it: accepted work that predates upgrading (or that is in flight during a rolling upgrade) appears only after it cycles.\n- A stale member can survive `removeAssignment` on a non-SLA accepted assignment (only SLA records carry their owner); reads verify ids against the accepted store and self-heal, so results stay correct.\n\n**Crash-safety note.** `getUsers()` (and `getQueueStats()`'s internal user scan) now skip a corrupt user record instead of throwing on it — the same precedent `getAllAssignmentsFromStores` already set. One malformed entry can no longer take down a dashboard endpoint.\n\n### `pauseUser(userId: string): Promise<boolean>` / `resumeUser(userId: string): Promise<boolean>`\n\nOperator availability controls. A paused user stops receiving new assignments in every matching path but keeps their pending backlog and can still accept, complete, or reject work they already hold. Paused users are exempt from idle auto-removal (`idleUserTimeoutMs`) — pausing is deliberate absence, not idleness. `pauseUser` returns `false` when the user is not in the pool; `resumeUser` returns `false` when the user was not paused, and records activity (resets the idle clock) when it succeeds. Inspect state with `isUserPaused(userId)` and `getPausedUsers()`.\n\nWhen decision traces are enabled, paused users appear struck out in traces with a `paused` reason, and `explainMatch()` reports them as ineligible.\n\n### `releaseUserAssignments(userId: string): Promise<string[]>`\n\nThe redistribution counterpart to `pauseUser`: requeue every pending (matched but not yet accepted) assignment the user holds so other users can pick them up — for when a worker is gone rather than briefly away. Accepted assignments (work in progress) are never touched. Requeued assignments keep their original wait clock (`getQueueStats().oldestWaitingMs` does not reset), workflow subscribers receive an `EXPIRED` event per released assignment, and a still-paused user cannot win their released work back until resumed. Returns the released assignment ids (`[]` when the user holds nothing or is unknown). The idle auto-removal path (`processIdleUsers`) uses the same requeue mechanics internally.\n\n### `assignToUser(assignmentId: string, userId: string, options?: { force?: boolean }): Promise<{ previousOwnerId: string | null }>`\n\nOperator override: hand an assignment directly to a user, bypassing tag/weight selection. Works on queued assignments and on pending assignments held by another user (the previous owner's backlog slot is released and the expiry clock restarts). Idempotent when the user already owns the assignment.\n\nHard rules still apply unless `force: true`: the user must not be paused, must have backlog headroom, must not be vetoed on the assignment, and must not have previously rejected it. The learning layer is never fed by manual assignments. When tracing is active the override is recorded as a decision trace with mode `'manual'`, so supervisor actions land in the same audit trail as organic matches.\n\n### Response deadlines & escalation (`Assignment.escalation`)\n\nBy default an assignment nobody responds to is requeued after the matcher-wide `matchExpirationMs` — and the same user may win it straight back. Attach an `EscalationPolicy` to make that behaviour explicit: a per-assignment deadline, an optional block on the non-responder, a priority climb, and an optional tier ladder that moves the work to a different pool on each hop. **No workflow is required.**\n\n```ts\nawait matcher.addAssignment({\n    id: 'incident-1',\n    tags: ['sev:1', 'oncall-primary'],\n    priority: 1000,\n    escalation: {\n        respondWithinMs: 60_000,\n        onNoResponse: 'block', // don't offer it back to whoever ignored it\n        priorityBoost: 500, // ignored work climbs the queue\n        tiers: [['oncall-primary'], ['oncall-secondary'], ['oncall-manager']],\n        onExhausted: 'park', // nobody answered, all the way up\n    },\n});\n\nmatcher.startMaintenance(); // deadlines don't fire unless something sweeps them\n```\n\n| Field             | Meaning                                                                                                                                        | Default                            |\n| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |\n| `respondWithinMs` | Per-assignment response deadline, overriding `matchExpirationMs`                                                                               | required                           |\n| `onNoResponse`    | `'block'` treats a non-response as a soft rejection (same rejected set an explicit reject writes to); `'allow'` keeps the historical behaviour | `'allow'`                          |\n| `priorityBoost`   | Priority delta applied on each hop                                                                                                             | `0`                                |\n| `tiers`           | Tag ladder. Tier tags are swapped per hop; tags not named by any tier survive untouched                                                        | none                               |\n| `maxEscalations`  | Hop ceiling                                                                                                                                    | `tiers.length - 1`, else unlimited |\n| `onExhausted`     | `'queue'` keeps recirculating; `'park'` holds it out of matching                                                                               | `'queue'`                          |\n\nThe wait clock always survives an escalation: requeues go through `addAssignment`, whose `NX` keeps the original first-enqueue time, so `getQueueStats().oldestWaitingMs` measures the work item rather than the current tier.\n\nSubscribe via `onAssignmentLifecycle` for `escalated` (`{ fromWorkerId, level, blockedPreviousOwner }`) and `escalationExhausted` (`{ level, parked }`) events; `expired` is still emitted first, so existing consumers are unaffected. Parked assignments are reachable with `getParkedAssignments()`, returned to the queue with `unparkAssignment(id, { resetEscalation? })`, and report `_status: 'parked'` from `getAssignment()` — they never read as \"not found\". `getEscalationLevel(id)` reports how far an assignment has climbed.\n\n#### `failed` — the worker-reported outcome\n\n`failAssignment(userId, assignmentId, reason?)` now emits `{ kind: 'failed', taskId, workerId, reason?, failedAt }`. It previously emitted nothing at all, so an explicit failure was the one terminal transition invisible to `onAssignmentLifecycle` (the workflow event stream did hear it). It is deliberately distinct from `completionBreached` with `action: 'fail'`: that is a deadline a policy acted on, this is a person reporting that the work could not be done. No `assignment` snapshot rides along — the record stays in the completed store carrying `_failedBy` / `_failureReason`, so `getAssignment()` can still answer for it.\n\nA host that `switch`es exhaustively over `AssignmentLifecycleEvent` will see a new `kind` here; the union is additive and every existing branch keeps its shape.\n\n### SLA policies (`Assignment.sla`)\n\nEscalation owns the _response_ side (how long a matched user has to accept). `SlaPolicy` owns everything after that: how long the accepting user has to finish, how many times the work may be refused before it is pulled from rotation, and an absolute freshness cutoff after which the work is moot.\n\n```ts\nawait matcher.addAssignment({\n    id: 'order-42',\n    tags: ['fulfillment'],\n    sla: {\n        completeWithinMs: 30 * 60_000, // finish within 30 min of accepting\n        onCompletionBreach: 'requeue', // take it back if they don't\n        maxRejections: 3, // after 3 refusals, stop offering it\n        expireAfterMs: 24 * 3600_000, // moot after 24h no matter what\n    },\n});\n\nmatcher.startMaintenance(); // SLA clocks are swept by the maintenance tick\n```\n\n| Field                | Meaning                                                                                                                                                            | Default    |\n| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |\n| `completeWithinMs`   | Completion deadline, measured from `acceptAssignment()`. Swept by `processCompletionDeadlines()`                                                                   | none       |\n| `expireAfterMs`      | Absolute freshness cutoff from **first enqueue** — survives requeues and applies in every state (queued, pending, accepted). Swept by `processSlaExpiries()`       | none       |\n| `maxRejections`      | Refusal budget. Explicit `rejectAssignment()` calls **and** blocking no-response expiries (`escalation.onNoResponse: 'block'`) count; idle/operator releases don't | none       |\n| `onCompletionBreach` | `'notify'` (event only, worker keeps it) / `'requeue'` (take back, block breacher) / `'fail'` / `'park'`                                                           | `'notify'` |\n| `onMaxRejections`    | `'park'` / `'fail'` / `'keep'` (keep offering; budget becomes measurement-only)                                                                                    | `'park'`   |\n| `onExpire`           | `'drop'` (remove entirely) / `'park'` (retain for inspection)                                                                                                      | `'drop'`   |\n\nSemantics worth knowing:\n\n- **Fire-once.** A breached completion deadline is removed from the deadline index before the action runs, so a `'notify'` breach doesn't re-fire every tick; a requeued-after-breach assignment gets a fresh completion clock only on its next accept.\n- **The TTL never extends.** Rejection ping-pong re-adds the same JSON, whose `_enqueuedAt` keeps the original first-enqueue time — the freshness cutoff is a hard wall, not a sliding window.\n- **Rejection budget outranks escalation.** Once `maxRejections` is exhausted the assignment is parked/failed instead of climbing further tiers.\n- **Sweep granularity.** Like response deadlines, SLA clocks fire on the maintenance tick, not at the exact deadline.\n- **Unparking.** `unparkAssignment(id, { resetSla: true })` clears the first-enqueue clock and rejection counter; without it an unparked TTL-expired item simply expires again on the next sweep.\n\nLifecycle events: `completionBreached` (`{ workerId, action }`), `slaExpired` (`{ ownerId, action }`), `rejectionBudgetExhausted` (`{ rejections, action }`).\n\n**SLO measurement.** `getSlaStats(tag?)` returns aggregate attainment counters — global, or for one tag: `offers`, `acceptedInTime`, `acceptanceBreaches`, `completionBreaches`, `ttlExpiries`, `rejectionParked`, plus `meanAcceptLatencyMs` (matched→accepted) and `meanCompleteLatencyMs` (accepted→completed). Counters are best-effort `HINCRBY`s piggybacking paths that already touch Redis; only SLA-bearing assignments are measured.\n\n**Learning integration.** Breach outcomes flow into the contextual bandit as ordinary rewards (TTL expiry and completion breach → `expire`; completion breach with `'fail'` → `fail`), so per-user/per-tag stats and the automatic routing-weight vetoes pick up chronic breachers with no extra configuration. The default feature extractor also emits `sla:hasDeadline` and `sla:tightness` (normalized against `learningSlaTightnessReferenceMs`, default 1h) for assignments with a completion deadline, letting the model learn that tight-deadline work needs fast finishers. Custom feature extractors are unaffected.\n\n### Scheduled assignments (`Assignment.schedule`)\n\nEscalation owns the response clock and `SlaPolicy` owns the post-accept contract; `SchedulePolicy` owns the **offer window**: when the work becomes visible to matching at all (`notBefore`), and how long an offer may go un-accepted before it is pulled (`notAfter`). Acceptance ends the schedule's authority — completion pressure is `sla.completeWithinMs`'s job.\n\n```ts\nawait matcher.addAssignment({\n    id: 'callback-42',\n    tags: ['callbacks'],\n    schedule: {\n        notBefore: Date.parse('2026-08-07T09:00:00Z'), // invisible to matching until 9:00\n        notAfter: Date.parse('2026-08-07T11:00:00Z'), // parked if nobody accepted by 11:00\n        onMiss: 'park', // or 'drop'\n    },\n});\n\nmatcher.startMaintenance(); // schedule clocks are swept by the maintenance tick\n```\n\n| Field       | Meaning                                                                                                                                                               | Default  |\n| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |\n| `notBefore` | Epoch ms. Held in a dedicated scheduled store — no matching, no workflow targeting, no wait clock — until the scheduled sweep enqueues it                             | none     |\n| `notAfter`  | Epoch ms. Absolute offer deadline; applies only while un-accepted (scheduled, queued, or pending). Valid without `notBefore`. Must be `> notBefore` when both are set | none     |\n| `onMiss`    | `'park'` (retain for inspection) / `'drop'` (remove entirely) when `notAfter` elapses un-accepted                                                                     | `'park'` |\n\nSemantics worth knowing:\n\n- **Clocks anchor at activation.** A held assignment has no wait clock and no SLA freshness stamp; both start when the sweep (or a forced early assign) enqueues it. `getQueueStats().oldestWaitingMs` never counts held work.\n- **Acceptance kills the offer clock.** Once accepted, a passing `notAfter` is a non-event — late completion is governed by `sla.completeWithinMs` alone.\n- **A miss beats activation.** When one sweep finds both timestamps in the past, the assignment parks/drops directly; it never transits the queue or emits `scheduleActivated`.\n- **The offer deadline never extends.** `notAfter` is absolute, so rejection requeues carry the same deadline — a re-offered assignment can still miss.\n- **Fire-once, replica-safe.** Sweep entries are claimed with a `ZREM` before acting, so concurrent replicas can run the sweep in parallel.\n- **Sweep granularity.** Schedule clocks fire on the maintenance tick (`processScheduledAssignments()`), not at the exact millisecond.\n- **One policy, one window.** Repeating work is a [recurring assignment](#recurring-assignments-addrecurringassignment): a standing template whose sweep materializes each occurrence as an ordinary scheduled assignment carrying a derived policy. (Hand-materializing occurrences from your own scheduler still works — re-adding an existing id never resets its clocks, so materialization is idempotent — and the [`scheduling` module](#shift-scheduling-scheduling-module) produces dated occurrences you can feed straight in.)\n- **Operator override.** `assignToUser(id, userId)` refuses held assignments; `{ force: true }` early-activates (clocks anchor there). If the user then rejects and `notBefore` is still in the future, the assignment returns to the scheduled store.\n- **Unparking.** `unparkAssignment(id, { resetSchedule: true })` strips the schedule policy; without it a miss-parked assignment misses again on the next sweep.\n\nLifecycle events: `scheduleActivated` (`{ taskId, at }`) and `scheduleMissed` (`{ ownerId, state, action, assignment }` — the snapshot is the last surviving copy on the `'drop'` path).\n\n**Introspection.** Held assignments are listed by `getScheduledAssignments()`, read as `_status: 'scheduled'` from `getAssignment()`, and counted separately in `getAssignmentCounts().scheduled` and `getQueueStats().scheduled` (excluded from `total` and pagination, like parked items). Misses bump the `scheduleMisses` counter in `getSlaStats(tag?)`.\n\n**Learning integration.** Misses feed the contextual bandit as `expire` outcomes — a pending-state miss penalizes the user who sat on the offer, so automatic routing-weight vetoes pick up chronic missers with no extra configuration.\n\n### Recurring assignments (`addRecurringAssignment`)\n\nA recurring assignment is a **standing template**, never itself matchable: the recurrence sweep cuts each occurrence from it as an ordinary assignment whose `schedule` is derived from the policy (`notBefore` = the slot's open time, `notAfter` = open + `windowMs`). Everything else the template carries — tags, priority, SLA, escalation, vetoes, geo — is inherited by every occurrence.\n\n```ts\nawait matcher.addRecurringAssignment({\n    id: 'blog-draft',\n    tags: ['blog'],\n    recurrence: {\n        everyMs: 14 * 24 * 3600_000, // biweekly\n        startAt: Date.parse('2026-09-07T09:00:00Z'), // first window opens here (default: now)\n        windowMs: 3 * 24 * 3600_000, // each occurrence offered for 3 days\n        onMiss: 'park', // an unserved window parks the occurrence for inspection\n    },\n});\n\nmatcher.startMaintenance(); // the recurrence sweep rides the maintenance tick\n```\n\n| Field            | Meaning                                                                                                                             | Default  |\n| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------- |\n| `everyMs`        | Interval between window opens. Minimum 1000                                                                                         | required |\n| `startAt`        | Epoch ms the first window opens                                                                                                     | now      |\n| `windowMs`       | Offer window per occurrence (`schedule.notAfter = open + windowMs`). Omitted: occurrences never expire off the offer clock          | none     |\n| `onMiss`         | Per-occurrence miss policy, as in `SchedulePolicy`                                                                                  | `'park'` |\n| `until`          | No occurrence opens after this epoch ms; the template retires                                                                       | none     |\n| `maxOccurrences` | Retire after this many occurrences materialized                                                                                     | none     |\n| `catchUp`        | Downtime policy: `'skip'` materializes only slots whose window is still open; `'all'` materializes every elapsed slot (audit trail) | `'skip'` |\n\nSemantics worth knowing:\n\n- **Occurrence ids are deterministic** — `<templateId>@<openEpochMs>` — so a crashed sweep re-materializing a slot is an idempotent re-add, and any occurrence traces back to its template with no extra state.\n- **Slots never drift.** They align to `startAt + k × everyMs` whatever the sweep cadence; the tick decides when a slot is _noticed_, never where it sits.\n- **One occurrence ahead.** The sweep materializes each occurrence one interval before its window opens, so upcoming work is already visible in the scheduled store (`getScheduledAssignments()`, `getQueueStats().scheduled`).\n- **Skipped slots are free.** Under `catchUp: 'skip'`, slots that fully elapsed during downtime never existed: they don't count against `maxOccurrences` and don't flood the queue on revival. Under `'all'` they materialize and are immediately missed by the schedule sweep (parked/dropped per `onMiss`) — the audit-trail reading of a dead interval.\n- **Re-adding updates the template, not the clock.** Occurrences already cut and the next slot are facts about the past; remove and re-add to restart.\n- **Removal is forward-looking.** `removeRecurringAssignment(id)` stops future occurrences; already-materialized ones live on as ordinary assignments. `{ dropScheduled: true }` additionally clears occurrences still held un-activated in the scheduled store — never queued, pending, or accepted work.\n- **Fire-once, replica-safe, self-healing.** Sweep claims are one-shot `ZREM`s like the scheduled sweep; the due index is re-derived from the template store at the top of every pass, so a crashed sweep can strand nothing.\n\nAPI: `addRecurringAssignment(template)` · `removeRecurringAssignment(id, { dropScheduled? })` · `getRecurringAssignment(id)` / `listRecurringAssignments()` (each returns `{ template, nextAt, occurrences }`) · `processRecurringAssignments()` (manual sweep; also runs on the maintenance tick as `recurrence`, default on).\n\nLifecycle events: `recurrenceMaterialized` (`{ taskId, recurringId, occurrence, opensAt, at }` — hosts that keep per-task context copy it template → occurrence on this event) and `recurrenceRetired` (`{ recurringId, reason, occurrences, at }`).\n\n### `startMaintenance(options?)` / `stopMaintenance()` / `runMaintenanceOnce(options?)`\n\nDeadlines are not self-firing — something has to sweep them. `startMaintenance()` runs every enabled sweep on one tick:\n\n- `recurrence` (default on) — recurring-assignment materialization. Runs first so a due-now occurrence flows recurrence → schedule → queue inside one tick.\n- `scheduled` (default on) — schedule activations and offer-window misses (`schedule.notBefore` / `schedule.notAfter`). Runs before the deadline sweeps so a just-activated assignment's other clocks start from the same tick.\n- `responseDeadlines` (default on) — expiry, escalation, parking.\n- `completionDeadlines` (default on) — SLA completion deadlines (`sla.completeWithinMs`).\n- `slaExpiries` (default on) — SLA freshness TTLs (`sla.expireAfterMs`).\n- `workflowStepTimeouts` (default on when `enableWorkflows`) — the step-timeout index. **Note:** `startOrchestrator()` consumes the event stream but does not sweep step timeouts; without maintenance, `timeoutMs` on a workflow step never fires.\n- `idleUsers` (default on when `idleUserTimeoutMs` is set).\n- `completedRetention` (default on when `completedAssignmentsRetentionMs` is set) — purges terminal assignments from the completed store once their `_completedAt`/`_failedAt` timestamp is older than the window. The window applies retroactively to records created before the option was configured; purges are capped per pass so a large historical store drains across ticks. Entries without a terminal timestamp (externally injected) are never deleted. Also available standalone as `processCompletedAssignmentsRetention()`.\n\n`runMaintenanceOnce()` does one pass and returns a `MaintenanceReport` (`expiredMatches`, `escalations`, `parked`, `completionBreaches`, `slaExpiries`, `scheduleActivations`, `scheduleMisses`, `recurrenceMaterializations`, `recurrenceRetirements`, `retentionPurged`, `expiredSteps`, `releasedIdleUsers`, `tookMs`) — use it from a host that already owns a tick (a multi-tenant worker, a serverless schedule) instead of holding a timer per matcher. `startAutoReleaseInterval()` remains as a deprecated alias that sweeps response deadlines only.\n\n### `getQueueStats(): Promise<QueueStats>`\n\nLive operational snapshot for dashboards:\n\n- `queued` / `pending` / `scheduled`: assignment counts by state (`scheduled` = held by `schedule.notBefore`, not yet in the queue).\n- `oldestWaitingMs`: age of the longest-waiting unaccepted assignment, or `null`. The wait clock starts at first enqueue and survives reject/expiry requeues; it stops when a user accepts the assignment or it is removed. Held (scheduled) assignments have no wait clock yet.\n- `perUser`: every user's `backlog` depth, effective `maxBacklogSize` cap, and `paused` state.\n\n### `removeUser(userId: string): Promise<void>`\n\nRemoves a user from the system and clears their assignment backlog.\n\n### `removeAssignment(assignmentId: string, tags: string[]): Promise<void>`\n\nRemoves a specific assignment from the system. Requires tags to efficiently locate the assignment in Redis.\n\n### `completeAssignmentForUser(userId: string, assignmentId: string): Promise<void>`\n\nMarks an assignment as completed by a user, removing it from their backlog and potentially making them available for new assignments.\n\n### `waitUntilReady(): Promise<AssignmentMatcher>`\n\nWaits for the matcher to finish connecting and initializing internal Redis and workflow resources.\n\n### `executeWorkflow(workflowOrId, userId, initialContext?): Promise<WorkflowInstance>`\n\nThe simplest workflow entrypoint.\n\n- Pass a workflow ID to start an already-registered workflow.\n- Pass a workflow definition or builder-produced definition to register it and start it in one call.\n\n## Options\n\nYou can pass an options object to the `AssignmentMatcher` constructor:\n\n```typescript\ntype Options = {\n    // How many of the highest-priority assignments to consider in one matching batch.\n    // A larger batch might find more matches but could be slower.\n    relevantBatchSize?: number; // Default: 50\n\n    // Prefix for all Redis keys used by this library. Useful for namespacing\n    // if you use the same Redis instance for multiple applications or purposes.\n    redisPrefix?: string; // Default: '' (empty string)\n\n    // The maximum number of assignments a user can have in their \"tentative\" backlog.\n    // Once a user reaches this limit, they won't be matched with new assignments\n    // until some are completed or removed.\n    maxUserBacklogSize?: number; // Default: 9\n\n    // Whether to enable default tag injection/matching behavior.\n    // Notes for built-in weighted matching (`routingWeights`):\n    // - only weights > 0 are eligible\n    // - weight 0 is a hard veto (exact and suffix wildcard)\n    // - if no positive weights exist, no assignment is pulled from queue\n    // If set to false, you should provide a custom `matchingFunction`.\n    enableDefaultMatching?: boolean; // Default: true\n\n    // Opt-in idle user auto-rejection. When set, users holding pending\n    // (not yet accepted/rejected) assignments with no activity for this many\n    // milliseconds are removed from the matching pool by `processIdleUsers()`,\n    // and their pending assignments are requeued for other users.\n    // Activity is recorded automatically on addUser/acceptAssignment/rejectAssignment,\n    // or explicitly via `touchUser(userId)` as a heartbeat.\n    // Use `startIdleUserInterval(intervalMs)` / `stopIdleUserInterval()` to run\n    // the check periodically. Disabled when undefined (default).\n    idleUserTimeoutMs?: number; // Default: undefined (disabled)\n\n    // Retention window for the completed-assignments store, in ms. Terminal\n    // assignments (completed or failed — they share the store) whose\n    // _completedAt/_failedAt timestamp is older than the window are purged by\n    // the maintenance tick (sweep flag `completedRetention`, default on when\n    // this is set; also callable directly as processCompletedAssignmentsRetention()).\n    // The window applies RETROACTIVELY: records created before you configured\n    // it are purged too — in capped batches per tick, so a large historical\n    // store drains gradually rather than in one deletion storm. Entries\n    // without a terminal timestamp are never deleted. Disabled when\n    // undefined (default) — the store is kept forever, so set this on busy\n    // deployments unless you need the full audit trail.\n    completedAssignmentsRetentionMs?: number; // Default: undefined (keep forever)\n\n    // Who wins when several users are eligible for the same assignment\n    // during bulk matching (matchUsersAssignments() with no userId)?\n    //   'first-come'  - whoever's claim reaches Redis first (fastest; the\n    //                   winner is arbitrary). This is the default.\n    //   'best-match'  - the highest-scoring eligible user, deterministically.\n    //   'balanced'    - best match wins, but near-ties (within ~5% of the\n    //                   typical candidate score) go to whoever has less work.\n    //   'spread-work' - work is spread as evenly as skills allow: every\n    //                   assignment already on someone's plate discounts their\n    //                   next bid by half the typical candidate score, so\n    //                   finishing work fast never just means drowning in more.\n    // The numbers behind 'balanced' / 'spread-work' are derived automatically\n    // from each matching pass's candidate scores - nothing to calibrate. Both\n    // also include an hourly guardrail by default: nobody receives at more\n    // than double the team's average grant rate (see fairnessMaxPerWindow).\n    // Switchable at runtime via `setFairness(mode)` / `getFairness()`, or\n    // retune every fairness knob below at once with\n    // `setFairnessConfig(partial)` / `getFairnessConfig()` - changes apply on\n    // the next `matchUsersAssignments()` call, no reconstruction needed.\n    fairness?: 'first-come' | 'best-match' | 'balanced' | 'spread-work'; // Default: 'first-come'\n\n    // Hard ceiling on how many assignments one user can be *granted* within a\n    // rolling time window, in any fairness mode other than 'first-come'. The\n    // backlog cap alone can't protect diligent users: fast workers keep\n    // freeing backlog slots and keep winning, so speed is rewarded with ever\n    // more work. This counts grants regardless of how quickly they were\n    // cleared; at the cap, contested work spills to the next-best eligible\n    // user (or stays queued) until the window rolls. Workflow-targeted\n    // assignments are direct handoffs and bypass the cap.\n    // Left undefined, the 'balanced' / 'spread-work' presets default it to a\n    // team-relative guardrail - max(maxUserBacklogSize, 2x the team's average\n    // grants in the window), recomputed each pass, so it adapts to any\n    // deployment's volume. Set a number to pin the ceiling, or Infinity to\n    // disable the window cap entirely.\n    fairnessMaxPerWindow?: number; // Default: auto for 'balanced'/'spread-work', else disabled\n    fairnessWindowMs?: number; // Default: 3600000 (one hour)\n\n    // Expert alternative to `fairness` - the raw switches behind it:\n    // enableFairTiebreaker is exactly `fairness: 'best-match'` when true;\n    // fairnessLoadPenalty is an absolute score discount per assignment\n    // already on a user's backlog, and fairnessTieBand treats scores in the\n    // same band-sized bucket as tied (less-loaded user wins). Explicit values\n    // here override what a `fairness` preset would derive. Runtime toggles:\n    // `setFairTiebreaker(enabled)` / `isFairTiebreakerEnabled()`.\n    enableFairTiebreaker?: boolean; // Default: false\n    fairnessLoadPenalty?: number; // Default: 0 (off)\n    fairnessTieBand?: number; // Default: 0 (off)\n\n    // A custom function to determine the priority of an assignment.\n    // The function receives assignment objects (or undefined if fewer than 3 are available for comparison)\n    // and should return a numerical priority score. Higher scores mean higher priority.\n    // Useful if `createdAt` is not sufficient for your prioritization needs.\n    prioritizationFunction?: (...args: (Assignment | undefined)[]) => Promise<number>;\n\n    // A custom function to determine if a user can be matched with an assignment\n    // and the \"cost\" or \"rank\" of that match.\n    // If `enableDefaultMatching` is true, this function can augment or override the default logic.\n    // If `enableDefaultMatching` is false, this function is solely responsible for matching.\n    // It should return a tuple: `[matchRank, userCost]`.\n    // Important: built-in hard-veto/positive-only weighted semantics apply to the\n    // built-in matcher. With a custom matchingFunction, you own final scoring logic.\n    // - `matchRank`: A score indicating the quality of the match (higher is better).\n    //                A rank of 0 or less means no match.\n    // - `userCost`: A score indicating the \"cost\" of assigning this task to this user\n    //               (lower is better, used for tie-breaking among users).\n    matchingFunction?: (\n        user: User,\n        assignmentTags: string[],\n        assignmentPriority: number | string, // This is the score from prioritizationFunction or createdAt\n        assignmentId?: string,\n    ) => Promise<[number, number]>;\n\n    // ========== Decision Traces & Explainability ==========\n\n    // Persist an auditable decision trace for every routing decision. Each\n    // matched assignment gets a MatchDecisionTrace — winner, arbitration mode,\n    // and every evaluated candidate with score breakdown and exclusion\n    // reasons — appended to a capped Redis stream and queryable via\n    // getDecisionTraces(). Captured while the decision happens, never\n    // reconstructed. Toggleable at runtime via setDecisionTraces(enabled).\n    enableDecisionTraces?: boolean; // Default: false\n\n    // Retention (entry count) for the trace stream, and how many candidates\n    // are stored per trace (the chosen candidate is always kept).\n    decisionTraceMaxEntries?: number; // Default: 1000\n    decisionTraceMaxCandidates?: number; // Default: 25\n\n    // Real-time hook invoked with each MatchDecisionTrace as decisions are\n    // made (e.g. push to a websocket). Setting it activates capture even when\n    // enableDecisionTraces is false — traces then stream to the callback only\n    // and are not persisted. Callback errors never affect matching.\n    onMatchDecision?: (trace: MatchDecisionTrace) => void;\n\n    // ========== Reinforcement Learning Options ==========\n\n    // Enable the adaptive learning layer (contextual bandit re-ranking).\n    enableLearning?: boolean; // Default: false\n\n    // SGD learning rate for online model updates.\n    learningRate?: number; // Default: 0.1\n\n    // Epsilon-greedy exploration rate in [0, 1]. With this probability, a random\n    // jitter is added to candidate scores so the model keeps gathering data on\n    // under-served candidates.\n    learningExplorationRate?: number; // Default: 0.05\n\n    // Shadow mode: record decisions and learn from outcomes, but never alter\n    // ranking. Useful for safely evaluating the model before going live.\n    learningShadowMode?: boolean; // Default: false\n\n    // Multiplier applied to the predicted reward when re-ranking candidates.\n    // Higher values let the learned model dominate over base priority.\n    learningBoostFactor?: number; // Default: 1\n\n    // Override rewards per lifecycle outcome (merged with defaults):\n    // { accept: 0.3, complete: 1, reject: -0.6, expire: -0.3, fail: -0.8 }\n    learningRewards?: Partial<Record<'accept' | 'complete' | 'reject' | 'expire' | 'fail', number>>;\n\n    // Custom feature extractor. Defaults to tag-match, normalized skill-weight,\n    // tag-overlap-ratio and embedding-similarity features.\n    learningFeatureExtractor?: (user: User, assignment: { id: string; tags: string[] }) => Record<string, number>;\n\n    // Reference duration (ms) used to normalize the `sla:tightness` feature\n    // emitted by the default extractor for assignments with sla.completeWithinMs.\n    learningSlaTightnessReferenceMs?: number; // Default: 3600000 (1 hour)\n\n    // TTL for stored decision contexts in ms.\n    learningDecisionTtlMs?: number; // Default: 604800000 (7 days)\n\n    // Weights applied to named external feedback signals when computing rewards.\n    // Signals not listed here default to weight 1. Negative weights turn a\n    // signal into a penalty (e.g. { errorRate: -2 }).\n    learningSignalWeights?: Record<string, number>;\n\n    // TTL for archived episodes awaiting late external feedback in ms.\n    learningFeedbackTtlMs?: number; // Default: 604800000 (7 days)\n\n    // SGD update rule. 'normalized' bounds the step by the active feature\n    // vector's squared norm; 'raw' is the pre-1.16 rule and diverges once\n    // several features fire at once.\n    learningUpdateMode?: 'normalized' | 'raw'; // Default: 'normalized'\n\n    // L2 weight decay applied per update.\n    learningL2?: number; // Default: 0\n\n    // Clip on the L2 norm of a single update.\n    learningMaxUpdateNorm?: number; // Default: 1\n\n    // Absolute cap on any single learned weight.\n    learningMaxWeightMagnitude?: number; // Default: 100\n\n    // Cap on the learning layer's contribution to effective priority.\n    // Unset = unbounded (pre-1.16 behaviour).\n    learningMaxBoost?: number;\n\n    // What counts as one observation for per-tag evidence.\n    learningRewardAccounting?: 'per-event' | 'per-attempt'; // Default: 'per-event'\n\n    // Candidate selection policy. 'epsilon-greedy' logs a recoverable\n    // propensity on every committed decision; 'jitter' is the pre-1.16\n    // unlogged score noise.\n    learningExplorationPolicy?: 'greedy' | 'epsilon-greedy' | 'jitter'; // Default: 'jitter'\n\n    // Random source for exploration; inject for reproducible runs.\n    learningRng?: () => number; // Default: Math.random\n\n    // Opt-in multi-target reward modelling.\n    learningTargets?: {\n        targets?: Array<'acceptance' | 'successGivenAcceptance' | 'quality'>;\n        acceptanceWeight?: number;\n        successWeight?: number;\n        qualityWeight?: number;\n        learningRates?: Partial<Record<'acceptance' | 'successGivenAcceptance' | 'quality', number>>;\n        systemFaultCountsAsFailure?: boolean; // Default: false\n    };\n\n    // Emit observed worker-performance features (batch-loaded once per\n    // worker per matching pass, never per candidate).\n    enableLearningPerformanceFeatures?: boolean; // Default: false\n    learningPerformancePriorStrength?: number; // Default: 5\n\n    // Maximum age of an outcome/feedback event relative to its attempt.\n    // Later events are rejected and counted as staleEvents.\n    learningMaxFeedbackAgeMs?: number; // Default: learningFeedbackTtlMs\n\n    // Validation bounds applied before anything is written.\n    learningMaxSignalMagnitude?: number; // Default: 1e6\n    learningMaxRewardMagnitude?: number; // Default: 100\n};\n```\n\n## Decision Traces & Explainability\n\nEvery routing decision the engine makes can be explained and audited. Two\ncomplementary tools cover the two questions teams actually ask:\n\n- **\"Why did this assignment go to that user?\"** — decision traces, the\n  auditable record captured _while the decision was made_.\n- **\"Who could receive this assignment right now, and why (not)?\"** —\n  `explainMatch()`, an on-demand evaluation against live state.\n\n### Decision traces (`enableDecisionTraces`)\n\n```typescript\nconst matcher = new AssignmentMatcher(redisClient, {\n    enableDecisionTraces: true,\n    // Optional real-time feed (dashboards, sockets). Works even without\n    // persistence: setting only onMatchDecision streams traces to the\n    // callback and skips storage.\n    onMatchDecision: (trace) => io.emit('match:decision', trace),\n});\n\nawait matcher.matchUsersAssignments();\n\nconst [trace] = await matcher.getDecisionTraces({ assignmentId: 'ticket-42' });\n// {\n//   assignmentId: 'ticket-42',\n//   chosenUserId: 'alice',\n//   mode: 'best-match',            // how the winner was arbitrated\n//   matchedAt: 1752404712345,\n//   candidates: [\n//     { userId: 'alice', eligible: true,  chosen: true,  score: 101,\n//       effectivePriority: 201, reasons: [ { kind: 'tagWeight', tag: 'english', weight: 100 }, ... ] },\n//     { userId: 'bob',   eligible: false, chosen: false, score: 0,\n//       effectivePriority: 0,   reasons: [ { kind: 'veto', tag: 'german', pattern: 'german' } ] },\n//   ],\n// }\n```\n\nTraces are appended to a capped Redis stream (`decisionTraceMaxEntries`,\ndefault 1000), so the audit record survives restarts and is shared across\nreplicas. Query with `getDecisionTraces({ assignmentId?, userId?, limit? })`\n(newest first), wipe with `clearDecisionTraces()`, and toggle at runtime with\n`setDecisionTraces(enabled)` / `isDecisionTracesEnabled()`.\n\nWhat a trace contains:\n\n- The winner, the arbitration mode (`'first-come'`, `'best-match'`,\n  `'balanced'`, `'spread-work'`, `'direct'` for per-user matching, or\n  `'workflow'` for deterministic workflow-targeted handoffs), and the moment\n  of the decision.\n- Every candidate evaluated in that matching pass with its full score\n  breakdown: matched routing weights (`tagWeight`, wildcard `pattern`\n  included), the implicit `defaultTag`, `tagOverlap` for unweighted users,\n  `geoDistance`/`geoBoost`, `learningBoost` (with `shadowMode` flag),\n  `skillThreshold` failures, `cidrMismatch`, and `customScore` when a custom\n  `matchingFunction` owns scoring.\n- Users excluded before scoring by hard rules — zero-weight `veto` entries,\n  `assignmentVeto` (the assignment's `vetoedUsers`), and `rejectedPreviously` —\n  are struck out in the trace even though the Redis-side prefilter kept them\n  out of the scoring pass entirely.\n- Where a veto came from. For users carrying `learnedRoutingWeights`, each\n  `veto` reason also has `source: 'learned' | 'manual'`, separating a veto the\n  automatic routing-weight sync synthesized from one an operator configured.\n  The field is absent for users with no learned weights, so traces are\n  unchanged when the learning layer is off.\n\nHonesty notes: in the default `'first-come'` mode users evaluate and claim in\nparallel, so a candidate whose snapshot read happened after the winner's claim\nmay be absent from the trace (they never evaluated it); fair modes evaluate\neveryone read-only first and always capture the full candidate field. Reason\nkinds are add","readmeFilename":"README.md"}