{"_id":"@pandascore/odds-sdk","_rev":"23-e5ee79e19bf77e3eb9b5237aba520dbe","name":"@pandascore/odds-sdk","dist-tags":{"latest":"2.2.0"},"versions":{"0.0.3":{"name":"@pandascore/odds-sdk","version":"0.0.3","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"MIT","_id":"@pandascore/odds-sdk@0.0.3","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"8ceaafb8e1700089f1d62603aa7219814cfd5ca0","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-0.0.3.tgz","fileCount":36,"integrity":"sha512-+TaLnkan5E6CbeynYYzX9QvzzUEZA3e431fhJD2Zxh93wgC070Nszsxg7pqGn+/jiklQnU/A+mmWzNt79x7kVQ==","signatures":[{"sig":"MEUCIGRsTuNqR+xiDzpaLIoyrN6WTEMWu32FRdQFt2GhNmmbAiEA5K5HJeo6hSi18BiU0yEJx1R0JgpaU3c6I82yGheWbiM=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":54038},"main":"dist/src/index.js","types":"dist/src/index.d.ts","gitHead":"72d398d9daff16780cc4b8a6aa37249164b1f08e","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"tsc && tsc-alias","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"10.8.2","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"22.4.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.4","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.5","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_0.0.3_1721134434485_0.20500624451328187","host":"s3://npm-registry-packages"}},"0.0.4":{"name":"@pandascore/odds-sdk","version":"0.0.4","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"MIT","_id":"@pandascore/odds-sdk@0.0.4","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"c3b9ecfb1dc123acae181eeca033a504a07381b6","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-0.0.4.tgz","fileCount":3,"integrity":"sha512-lyHk1p+nrJrwvqDMu3ORrPZIDLlmzfM+R1nzNecpVHryjmWVutAbQxdIqcvN1NB1U8CogEvCoMkElW7gYBHSWQ==","signatures":[{"sig":"MEQCIEY9QUc1XYHjVvH6LJqgTzz9X28q0Da0+OJZ78xcR07GAiAnGnlKdUgIegOeLBk9ty16TIYB3F7g006o+fVvPITn+g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":8395},"main":"dist/src/index.js","types":"dist/src/index.d.ts","gitHead":"8c54d027303cb7680cca6c5a78da58e01b3dcd62","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"tsc && tsc-alias","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.4.2","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"22.17.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.4","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.5","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_0.0.4_1759741654684_0.7545788440079759","host":"s3://npm-registry-packages-npm-production"}},"0.0.5":{"name":"@pandascore/odds-sdk","version":"0.0.5","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@0.0.5","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"5e9e3d67abe999f2fb59e8c2e768e84b0f8c29bf","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-0.0.5.tgz","fileCount":60,"integrity":"sha512-5AKVGNvhnfi5sDq9jgua/mZ/z1MF5/qsAMU0jEew2QoqTvSVhWNg3RF01EI5ZoCXFi9QYYunVTFdrq70lmK3+Q==","signatures":[{"sig":"MEYCIQDk1FI9za+24Ak0UCud4ku8SMSY++5X7xLOZT39x2oh6QIhAPrPriJV2dkeUCoeWOW+en/Lkf+5F/ybVECkeg6M+Zty","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":112888},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"691ce9d41882cdeac53827a132395293b21129c9","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.6.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"24.9.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.4","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.5","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_0.0.5_1759764797615_0.9431449131410585","host":"s3://npm-registry-packages-npm-production"}},"0.0.7":{"name":"@pandascore/odds-sdk","version":"0.0.7","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@0.0.7","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"ef16b63dffefd57f6645470d84130eb8bde6c215","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-0.0.7.tgz","fileCount":60,"integrity":"sha512-yWHy16oAJTOCgJCDmOezyS4W/5EZCKIe2CQxgbA7I66RJ4oXdrXz/a72mBm4AdT8vQc9O31XzirSnnsJ2dwpgA==","signatures":[{"sig":"MEQCIHiK3AK/4hU4BZDUVwwfzO02u5M7/T6Umxdql+bLminKAiBa9x0u5PJEvauH7xETt4KbMupGnkU5bp8IrePjOkUtBg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":112344},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"84a18cc7ab3698b359b53bd8c15f58744e8803a6","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.6.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"24.9.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.4","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.5","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_0.0.7_1759844273082_0.978401598773375","host":"s3://npm-registry-packages-npm-production"}},"0.0.9":{"name":"@pandascore/odds-sdk","version":"0.0.9","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@0.0.9","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"303e6c74f78a79555a08d570ed3d22c9f99f1c55","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-0.0.9.tgz","fileCount":60,"integrity":"sha512-QAPevZ4H2vaaO+0myG9Aq+9mGmJFkwjVSlEVHqIeCOivcyPK21+VYM0Fd1OXiONcxqnhLdZScceWH24ZCvKTCQ==","signatures":[{"sig":"MEUCIQCyUnvigKf3S4yG5x8jpSxVR8W7naYHt07sBMkEDlYUkQIgJC/ZECEX4hlfXdtPk3Mp6O34FYUnwYuyWq1XDd65Hng=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":112352},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"7b3ecdebecccd6ed903bcea3b96deff14b19a507","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.6.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"24.9.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.4","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.5","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_0.0.9_1759846980408_0.9683829863984517","host":"s3://npm-registry-packages-npm-production"}},"0.0.11":{"name":"@pandascore/odds-sdk","version":"0.0.11","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@0.0.11","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"b084bd7611a60925ffa236045a6b75e31f3a5806","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-0.0.11.tgz","fileCount":60,"integrity":"sha512-M0hBzM8+YZJ1sg2+qqzn8n0rV1P4tWEMUV9i9ktiTckT9kMY+xAMNKKfHCYCtyPUTQ3B6MehASzjT9c7FRT4/Q==","signatures":[{"sig":"MEUCIEAb5Lh8uYpMVQf1VDYelD8ct061FVwsujvG7TuIjTfUAiEAofDhT1pRDJ2Yo3WhTGbKGsz/DO8PYOczA56+ID2vZv0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":115189},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"736ea749792291b758006471a1f08e3ac1cc9d5d","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.6.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"24.9.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.4","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.5","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_0.0.11_1759999249697_0.9562248634089288","host":"s3://npm-registry-packages-npm-production"}},"0.0.12":{"name":"@pandascore/odds-sdk","version":"0.0.12","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@0.0.12","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"297237017d4711371ac175765b4db1bd568793ec","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-0.0.12.tgz","fileCount":60,"integrity":"sha512-6pdjbnTZqA9xHyldU7StutpbPwd4kIwObwKTXi8jP9DzeAXQwcsPcyNIMPuRj714VoZVt0BT2HEiuCMtM8dHeA==","signatures":[{"sig":"MEUCIQCfYVyvjeal9SHC240GQgOd4C1spf0z/hoG4vcKPecuTwIgZw+/txdjMOyLzvEPDOfZzE7T+gvuucSjrgIdNqp+DWs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":123676},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"0c9a72fd6f7a054c213b811fa183df83d9bd368c","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.6.2","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"25.3.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.4","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.5","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_0.0.12_1768832247830_0.11219611017838593","host":"s3://npm-registry-packages-npm-production"}},"0.0.13":{"name":"@pandascore/odds-sdk","version":"0.0.13","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@0.0.13","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"e34acfc78d07f6a2327db1ed93ff80d7978e3474","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-0.0.13.tgz","fileCount":60,"integrity":"sha512-2JVssXFBLVoThFcKhWgOXyLxCPPklS6nMNcvqe9R6NHAwC1Rs2QAOOY0Q9j5ZJxCP0pjPoXEbwF0UPvyFmi+Ig==","signatures":[{"sig":"MEUCIQDuBk1Z4dhJBP6668QDG1yunz4y44UlU2PxAnSXQv0UVwIgUg8zo4YDMxDxxNk6pXBaQ79GNsFvPzdi1RnshXKP/K8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":124846},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"a5a7d219dbc9363c4af4f918cd7da8fe5ba46734","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.8.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"25.6.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.4","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.5","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_0.0.13_1770815916251_0.17295892407985836","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@pandascore/odds-sdk","version":"1.0.0","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@1.0.0","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"ed3714afdd5b2a2ece3c0e50f405f659293f47d4","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-1.0.0.tgz","fileCount":60,"integrity":"sha512-Mng/XnDwXLUeLUR2Mi7C6hJKUIMRVMYJoMhiXQxqf8+R5Nkh4yLqa+IMeK4U2UjKPc5cpeWh8sJ8VRPA8mwgOA==","signatures":[{"sig":"MEYCIQD0GLkoqaQDJN4WQVmeLWaSZIp5igebLgMTnV99Ld68PwIhAOzsliMga3opJ9cAWJXiYPjPGnKfLpOeH4WPG/37DOy3","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":128736},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"53f4d00d0b5d929e6cabb3031a5fbf695defba6e","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.11.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"25.8.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_1.0.0_1772811249057_0.43498346219182316","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@pandascore/odds-sdk","version":"1.0.1","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@1.0.1","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"4b2b105eb3989d4ecf285bcd4c5a907700ac6ba0","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-1.0.1.tgz","fileCount":60,"integrity":"sha512-rj91pzU+IFFz0VTVky8ULCvAmETADJLPPef39B5DRbvieOuN6w6KaIBtfU2lUE04BGtB+j0YbbeC31Kn+QlHQw==","signatures":[{"sig":"MEUCIQDlgGOYSRvBYXmcbg9KrclMDVTnb+3H9rmkagy/PQ8+AgIgfL9ETAg8r7iozrxEhRCRZwQRUqDQteU5GX3XCoEVjag=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":131481},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"43b7c2e48e451b238cf8bf8e20be25d15c5d1e92","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.11.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"25.8.1","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_1.0.1_1774278798071_0.38448375601443985","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@pandascore/odds-sdk","version":"1.0.2","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@1.0.2","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"a43caf3c46649f0d3e98c945f8ef8e7f45795b9f","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-1.0.2.tgz","fileCount":60,"integrity":"sha512-crpD9fLv8TXgWSiIrGPeRJ3L8b0EHQRBCmnHUFqTssBX43TdqZIdJLVTXs1WQQvh9U/qkixde5iX9CIybrgK5w==","signatures":[{"sig":"MEQCICU7aoogJCeA0GZkgkrb+kRKTUcPiFJwCSUd1Bc1BzYoAiAsJdIwLRYJUlhPV9CYKSbvo7yNoBpClRHzOM+CKxp5WQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":134779},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"036755dc27d6df13b041cae4056b53b35f8e24d0","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.11.1","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"25.8.2","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_1.0.2_1774544518442_0.000433078531369091","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@pandascore/odds-sdk","version":"1.1.0","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@1.1.0","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"7cfe997afde48cac600c3f2d7bf84a0afac86556","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-1.1.0.tgz","fileCount":60,"integrity":"sha512-44qvpM/PgCLAWQb1KmGRwMjvwjIEf1g9ME6kfqeDSMupk4aBbASG1JzC1sAu79EcrdXYRvMf1I1IOZOEuPepBw==","signatures":[{"sig":"MEYCIQC7Cxjz6Yq+/2iRdF2b8JqTSwh5ePBxfk4n1p3etqsnYwIhAI4Yxy5cwA88TnoqZ9qJGoZbRpFJmu8+wMpMCjwF+sua","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":156602},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"b6c11a4d9c2f2430b1c7aec1abc43a8726f53044","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.16.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"26.3.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_1.1.0_1780580618271_0.4944962252271323","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@pandascore/odds-sdk","version":"1.2.0","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@1.2.0","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"14ca84520f65888ad3844b658b834d5c56ae6377","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-1.2.0.tgz","fileCount":73,"integrity":"sha512-CacQN2CKRlqRjdd+C1cFhFvo6XQxF/uN8PnHs5zVBMbus3apwccnNl2k/SLDV+yzUvx8+tDfuFQEABdDwhtASg==","signatures":[{"sig":"MEUCIQDUthKZ1LJBGf01cWVLRL243M3UCKcZkvnqHWxfnJE+EwIgRQbJ71tldEp6CTRWDstFw41Nw8EUrHFF1ECcvC/UFi0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":235377},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"acb980a4fdd5bf1612c9cb55f8863f5d4150fb65","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","test":"vitest run","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","test:watch":"vitest","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.16.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"26.3.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","vitest":"^2.1.9","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","vite-tsconfig-paths":"^5.1.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_1.2.0_1781536309667_0.9765872908699484","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@pandascore/odds-sdk","version":"1.3.0","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@1.3.0","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"4b82c4775c3e5b2ce9053c701479b37e726515f9","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-1.3.0.tgz","fileCount":78,"integrity":"sha512-GiA9Y3ujX7/O8HO1RSdFPX025CX+5G9WU3agdBbfP+tAaBNnEk5bHEvTiTZL7M0pvAsLomOM42KSLXBd+It/CQ==","signatures":[{"sig":"MEYCIQC4HvauqKJ+zo7N4qKtaKmjujKTGdQwJaC7Slf7ixwI0gIhAN+LTsxF12pDwTNiCdc7k6Tnh0kiVHBw9BNBOrKZUhaF","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":266845},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"c5c8ec04f6da172e72348d6f7e2c26f5d182df7e","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","test":"vitest run","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","test:watch":"vitest","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.17.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"26.5.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","vitest":"^2.1.9","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","vite-tsconfig-paths":"^5.1.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_1.3.0_1784902408715_0.5599848762557316","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"@pandascore/odds-sdk","version":"1.4.0","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@1.4.0","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"homepage":"https://github.com/PandaScore/pandascore-sdk-js#readme","bugs":{"url":"https://github.com/PandaScore/pandascore-sdk-js/issues"},"dist":{"shasum":"491ac944f672fdac3af1ab8d3f3bb81a5a2e5407","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-1.4.0.tgz","fileCount":88,"integrity":"sha512-paz5IYz8TE1LnxfywhHgjbm4whqA5yvOQFo2iHDTCZEzMwi6s+oKN0OTYl7m1BSphrBHlJfc8UgmYF+0NAJq7g==","signatures":[{"sig":"MEQCIGJDkWBCiFRQqHl9YavMvQiUtyqR1tHEaiMXh1yr3BPeAiBX+tDiBoa4d5mJJD/sDAFV3LGSDHxZAzqAjSHLeLJ5LA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":322439},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"2a980f03a74e17b60b6432e119e5816d13df55f0","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","test":"vitest run","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","test:watch":"vitest","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"repository":{"url":"git+ssh://git@github.com/PandaScore/pandascore-sdk-js.git","type":"git"},"_npmVersion":"11.17.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"26.5.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","vitest":"^2.1.9","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","vite-tsconfig-paths":"^5.1.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_1.4.0_1785334683979_0.6653187566811241","host":"s3://npm-registry-packages-npm-production"}},"1.5.0":{"name":"@pandascore/odds-sdk","version":"1.5.0","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@1.5.0","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"dist":{"shasum":"85f1f97cc2161c4e851bb5186d9fe8487043b6ce","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-1.5.0.tgz","fileCount":88,"integrity":"sha512-b8hYQjyRKHMEkzAt/QUgt5YzfzM0WI5ElphRuoshJ1aQUeXRR/jJPqquE7nqGHuN/Q82MpXCVWCSblIHExZHig==","signatures":[{"sig":"MEQCIEzseXTto6OKZPDYTFMMJ4bCmhR9b/WKorDmhxk4vytxAiBbvXKFenDi4SknqUZwzNxvudHG6CS822b6NV4FED8yfA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":322856},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"0e92dcfaacf61048f1d49a62496b6c4a0e9f4f24","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","test":"vitest run","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","test:watch":"vitest","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"_npmVersion":"11.17.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"26.5.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","vitest":"^2.1.9","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","vite-tsconfig-paths":"^5.1.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_1.5.0_1785338351633_0.5638965818053967","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@pandascore/odds-sdk","version":"2.0.0","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@2.0.0","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"dist":{"shasum":"7ee6db947a297ec0776e35a5d64c65eaeeb7c51b","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-2.0.0.tgz","fileCount":92,"integrity":"sha512-oizQ6HEnDw7/eBIMzWanIR34CTki/J1ZyObvagRrV6LR0ZQ82K8Mn3V4N7kbmC09WBVmru4GHaYm+KypGR2kaQ==","signatures":[{"sig":"MEUCIQDTUP1tmiGRj19XVzYdnrokG7mB2VueRYFqJ1lRhYmbvQIgPa1s0ihyTZaFG7lQgk5q0Kz2RmARd+4mwfNLCmKR/7o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":424111},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"07e2dff33d45f4a3bba7bd9626373b48c5e694bc","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","soak":"node soak-reconnect.js","test":"vitest run","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","test:watch":"vitest","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"_npmVersion":"11.19.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"26.7.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","vitest":"^2.1.9","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","vite-tsconfig-paths":"^5.1.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_2.0.0_1786021365611_0.9141244321932314","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@pandascore/odds-sdk","version":"2.1.0","author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","_id":"@pandascore/odds-sdk@2.1.0","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"dist":{"shasum":"895fe030639f39d179fbc0bdd3a26f8e174aa390","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-2.1.0.tgz","fileCount":92,"integrity":"sha512-Jv1xD3nv/QqIgiCOeg1AdmMz1XA04yRiOmzPcGs7BNsdQ7rd/rfTHepHaATOygXuZejoSpRoH7Jhu5lMpIULbQ==","signatures":[{"sig":"MEYCIQCW6k3t4S3ZaTbsnIME9kolAc8kU5/VU3CUL9Do2DPKQQIhAJhzyV5sUbF/5Yyrba3xJVxVQmGZEitg9i4c9MLYqUF6","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":461600},"main":"./dist/lib/index.js","types":"./dist/lib/index.d.ts","gitHead":"58149b99409a2efc5b5fd9ec20c1c2eae6e33ed1","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","soak":"node soak-reconnect.js","test":"vitest run","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","test:watch":"vitest","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"_npmVersion":"11.19.0","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"_nodeVersion":"26.7.0","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","vitest":"^2.1.9","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","vite-tsconfig-paths":"^5.1.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"tmp":"tmp/odds-sdk_2.1.0_1787734887712_0.7154421283174563","host":"s3://npm-registry-packages-npm-production"}},"2.2.0":{"_id":"@pandascore/odds-sdk@2.2.0","dist":{"shasum":"b04499135fb7f11e6bf8420562415c0b50dcfa33","tarball":"https://registry.npmjs.org/@pandascore/odds-sdk/-/odds-sdk-2.2.0.tgz","fileCount":96,"integrity":"sha512-jZZUEtBbsfx9p6jMucaIRjvLWj7OqLtstwKglXbzNFQOXuEpZpWnNiZ5noWOuu0L5GRvXkFaoKSLARJ9AUhz1w==","signatures":[{"sig":"MEYCIQDxXBL2+oUsq1kLuGh6DD06wTbwAN3z019LkcBHrQgoxAIhAL1RG1BGz7EwwBJL3W/Njd7hRLYk4XsB2Or2ZnCTr1oh","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEC5DM/jLoE2A+U4CTV/eJT6TOZOt/wxEBcpLopUPJVqAiA6cQ3NRxP6OMoByVwImDjFdfOKsJBXFz5WHj8d/mLw7A=="}],"unpackedSize":526220},"main":"./dist/lib/index.js","name":"@pandascore/odds-sdk","types":"./dist/lib/index.d.ts","author":{"name":"Pandascore","email":"cto@pandascore.co"},"gitHead":"e2b0452cd8444231a8b927c8866e4c6ae8e96a14","license":"UNLICENSED","scripts":{"lint":"eslint \"src/**/*.{js,ts}\"","soak":"node soak-reconnect.js","test":"vitest run","build":"yarn compile && yarn copy-examples","start":"yarn ts-prepare && ts-node ","format":"prettier --write \"src/**/*.{js,ts}\"","compile":"tsc && tsc-alias","lint:fix":"eslint --fix \"src/**/*.{js,ts}\"","test:watch":"vitest","ts-prepare":"ts-patch install -s","copy-examples":"cp -r examples dist/"},"version":"2.2.0","_npmUser":{"name":"pandascore","email":"cto@pandascore.co"},"_npmVersion":"11.19.1","description":"SDK to use Pandascore Betting Feed","directories":{"example":"examples"},"maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"_nodeVersion":"26.8.2","dependencies":{"path":"^0.12.7","axios":"^1.7.2","amqplib":"^0.10.8","winston":"^3.13.1","module-alias":"^2.2.3"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.7.0","vitest":"^2.1.9","ts-node":"^10.9.2","prettier":"^3.3.3","ts-patch":"^3.2.1","tsc-alias":"^1.8.10","typescript":"^5.5.3","@types/node":"^20.14.10","@types/amqplib":"^0.10.8","tsconfig-paths":"^4.2.0","@types/module-alias":"^2.0.4","vite-tsconfig-paths":"^5.1.4","eslint-config-prettier":"^9.1.0","eslint-plugin-prettier":"^5.1.3","@typescript-eslint/parser":"^7.16.0","typescript-transform-paths":"^3.4.7","@typescript-eslint/eslint-plugin":"^7.16.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/odds-sdk_2.2.0_1789478095804_0.6240726502526648"}}},"time":{"created":"2024-07-16T12:06:00.490Z","modified":"2026-09-15T13:14:56.107Z","0.0.1":"2024-07-16T12:06:00.762Z","0.0.2":"2024-07-16T12:52:41.944Z","0.0.3":"2024-07-16T12:53:54.659Z","0.0.4":"2025-10-06T09:07:34.875Z","0.0.5":"2025-10-06T15:33:17.793Z","0.0.7":"2025-10-07T13:37:53.275Z","0.0.9":"2025-10-07T14:23:00.610Z","0.0.11":"2025-10-09T08:40:49.869Z","0.0.12":"2026-01-19T14:17:27.970Z","0.0.13":"2026-02-11T13:18:36.380Z","1.0.0":"2026-03-06T15:34:09.208Z","1.0.1":"2026-03-23T15:13:18.204Z","1.0.2":"2026-03-26T17:01:58.609Z","1.1.0":"2026-06-04T13:43:38.422Z","1.2.0":"2026-06-15T15:11:49.822Z","1.3.0":"2026-07-24T14:13:28.863Z","1.4.0":"2026-07-29T14:18:04.153Z","1.5.0":"2026-07-29T15:19:11.781Z","2.0.0":"2026-08-06T13:02:45.768Z","2.1.0":"2026-08-26T09:01:27.860Z","2.2.0":"2026-09-15T13:14:55.946Z"},"author":{"name":"Pandascore","email":"cto@pandascore.co"},"license":"UNLICENSED","description":"SDK to use Pandascore Betting Feed","maintainers":[{"name":"pandascore","email":"cto@pandascore.co"}],"readme":"# PandaSDK (TypeScript)\n\nPandaSDK is the TypeScript SDK for the PandaScore Sportsbook. It connects to the real-time\ntrading feed over AMQPS, delivers structured odds, fixture, scoreboard and settlement\nmessages, and provides REST helpers for matches, markets and settlements.\n\n## Features\n\n- RabbitMQ feed integration - connect to the PandaScore AMQPS feed and receive structured JSON events.\n- Resilient reconnection - exponential backoff with jitter, and an automatic snapshot recovery when the outage outlasted your queue's message TTL.\n- Startup and restart handling - cold-start snapshot, warm-restart replay, and a persistable checkpoint.\n- Feed status signal - a single `FeedStatus` you can gate bet acceptance on.\n- Typed message callbacks - an optional `FeedListener` with `onMarkets` / `onFixture` / `onScoreboard` / `onSettlements`.\n- Void reasons - a structured `void_reason` on voided markets and selections, plus a REST helper listing all possible reasons.\n- BetBuilder - price combinations of selections from the same match, including across its games, with derived odds and typed selection errors.\n- Betting catalog - leagues, series, tournaments, videogames and market templates, plus outright markets on a serie.\n- HTTP clients - fetch matches, markets, booked matches, and settlements.\n- Videogame on markets responses (2.1.0+) - recovered and fetched markets carry the videogame their match is played on, and `recoverMatchMarkets` returns a recovery window grouped by match.\n- Team and player matches (2.1.0+) - `competitorId(result)` reads a score on both, including the eBattles videogames where the competitor is a player rather than a team.\n- Snapshot recovery (2.2.0+) - `requestRecovery` asks us to republish your current book on your own vhost, arriving as ordinary feed messages.\n- Extensive logging - file + console logging with contextual metadata.\n\n## Table of Contents\n\n- [Installation](#installation)\n  - [Upgrading to 2.2.0](#upgrading-to-220)\n  - [Upgrading to 2.1.0](#upgrading-to-210)\n- [Configuration](#configuration)\n  - [Computed odds fields](#computed-odds-fields)\n- [Quick Start](#quick-start)\n- [Service Startup Guide](#service-startup-guide)\n- [Feed Status](#feed-status)\n- [Connection Behavior](#connection-behavior)\n  - [Heartbeat monitoring](#heartbeat-monitoring)\n  - [Reconnection and recovery](#reconnection-and-recovery)\n  - [Recovery window](#recovery-window)\n  - [Configuration matrix](#configuration-matrix)\n- [Typed Listener](#typed-listener)\n- [Checkpointing](#checkpointing)\n- [Graceful Shutdown](#graceful-shutdown)\n- [HTTP API](#http-api)\n- [Betting catalog endpoints](#betting-catalog-endpoints)\n  - [Market templates](#market-templates)\n  - [Serie shapes](#serie-shapes)\n  - [Outright markets](#outright-markets)\n- [BetBuilder](#betbuilder)\n- [Settlement Feed](#settlement-feed)\n- [Void reasons](#void-reasons)\n- [Data Models](#data-models)\n  - [Fixture actions](#fixture-actions)\n  - [Market actions](#market-actions)\n  - [Match and game status are different sets](#match-and-game-status-are-different-sets)\n  - [Videogame-specific match fields](#videogame-specific-match-fields)\n  - [Reading match scores](#reading-match-scores)\n- [Testing](#testing)\n\n## Installation\n\n```bash\nnpm install @pandascore/odds-sdk\n```\n\nOr via yarn:\n\n```bash\nyarn add @pandascore/odds-sdk\n```\n\n### Upgrading to 2.2.0\n\n**There is one thing to check, and it is only for code that reads the reconnection\nnotification.** `n.recoveryData` no longer carries recovered markets and matches on a\nreconnection: that data now arrives on the feed instead, as ordinary `markets` and `fixture`\nmessages through the handler you already have. Every method signature is unchanged, and\n`startWithSnapshot` / `startWithRecovery` still populate `recoveryData` as before.\n\n```typescript\n// Before\nMySDK.events.on('notification', (n) => {\n  if (n.type === 'reconnection' && n.complete) {\n    applyRecovered(n.recoveryData.markets, n.recoveryData.modifiedMatches);\n  }\n});\n\n// Now: the same data arrives through your message handler\nMySDK.startLive({\n  onMarkets: (m) => applyIfNewer(m),\n  onFixture: (f) => applyIfNewer(f),\n  onRecoveryCompleted: (m) => resumeBetAcceptance(m.recovery_request_id),\n});\n```\n\n**Apply messages idempotently, keeping the newest `at` per market.** If you already do, there is\nnothing to add. A snapshot message carries the market's real last-modification time, so one older\nthan what you hold is correctly ignored and a duplicate is a no-op.\n\n**If your queues are created outside the SDK,** bind them to `v1.recovery.#` or you will not\nreceive the control messages. The SDK adds the binding to queues it declares itself.\n\nTwo behaviour changes worth knowing about, neither requiring an edit:\n\n- A reconnection inside your queue's message TTL (15 minutes by default, `queueMessageTtlMs`)\n  requests nothing at all, because the queue already held what you missed.\n- Live messages are no longer buffered during a reconnection. Delivery resumes immediately.\n\n`recoverMarkets` and `recoverMatchMarkets` are deprecated but unchanged, and still work.\n\n### Upgrading to 2.1.0\n\n**Your integration keeps running exactly as it does today.** Nothing changes at runtime: every\nmethod returns the same values, the feed delivers the same messages, and no data you already read\nchanges shape or meaning. `recoverMarkets` returns the same flat list it returned in 2.0.0.\n\nWhat can change is your **build**. The fixture types now describe what the feed actually sends. `?: T`\nmeans \"may be absent\", which does not admit an explicit `null` - and this API sends the null: a game\nhas `map: null` until a map is picked, and a team can arrive with `acronym: null` while its opponent\nin the same message sends a string. If you compile with `strict` and read one of these, TypeScript\nwill now ask you to handle that case:\n\n| Field | Was | Is now | What to do |\n|---|---|---|---|\n| `MatchResult.team_id` | `number` | `number \\| undefined` | Use [`competitorId(result)`](#reading-match-scores), which returns `team_id` or `player_id`, whichever the match carries |\n| `Game.map` | `GameMap` | `GameMap \\| null \\| undefined` | `game.map?.name` |\n| `FixtureMessage.match`, `.serie` | non-nullable | `\\| null` added | `message.match?.id` |\n| `FixtureTeam` / `FixtureWinner` / `GameMap` optional strings | `string \\| undefined` | `string \\| null \\| undefined` | `team.acronym ?? ''` |\n| `Game.number_of_rounds` | `number` | `number \\| undefined` | `game.number_of_rounds ?? 0` |\n| `Game.rounds_score` | `RoundScore[] \\| null` | `RoundScore[] \\| null \\| undefined` | `game.rounds_score ?? []` |\n| `Tournament.type` | `string` | `string \\| null` | `tournament.type ?? ''` |\n| `Streams[language].embed_url`, `.raw_url` | `string` | `string \\| null` | Check before rendering the link |\n\nIf you do not compile with `strict`, or you do not read these fields, **there is nothing to do** -\ninstall the new version and carry on.\n\nRun `tsc --noEmit` after upgrading to see the full list for your codebase. Every error will be one of\nthe rows above, and each is a missing guard on a value that could already have arrived `null` at\nruntime before this release - the type simply did not say so.\n\nNew in this release and optional: [`recoverMatchMarkets`](#reconnection-and-recovery) for a recovery\nwindow grouped by match, and [`competitorId`](#reading-match-scores) for reading scores on both team\nand player matches.\n\n> **Upgrading from 1.5.0: delete your existing queue first.**\n>\n> Feed queues are now declared as quorum queues, so a queue survives a broker node failure along\n> with the messages waiting in it. A queue's type is fixed when it is created, so a queue declared\n> under an earlier version has to be removed before the new declaration can take effect. Until it\n> is, connecting reports:\n>\n> ```\n> PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue '<your-queue>':\n> received 'quorum' but current is 'classic'\n> ```\n>\n> The credentials you already connect with declare the queue, so they can also remove it:\n> `channel.deleteQueue('<your-queue>')` over AMQP, or the delete action in the RabbitMQ management\n> view if you have access to it. The SDK recreates it as a quorum queue on your next connect.\n>\n> Nothing changes in your own code: queue names, routing keys and every callback stay as they are.\n> Drain or accept the loss of anything still queued at the moment you delete it, and prefer\n> [`startWithRecovery`](#warm-restart) on the first connect after the switch so you replay from your\n> last checkpoint. Deleting the queue ends any consumer still attached to it, so do this as part of\n> the deploy rather than ahead of it.\n>\n> A first-time integration is unaffected: there is no existing queue to replace.\n\n## Configuration\n\n```typescript\nimport { PandaSDK } from '@pandascore/odds-sdk';\n\nconst MySDK = PandaSDK.initialize({\n  apiToken: '<your-api-token>',         // your API token\n  apiBaseURL: '<your-api-base-url>',    // ask your integration manager for the base URL\n  feedHost: '<your-feed-host>',         // ask your integration manager for the feed host\n  company_id: 0,                        // your PandaScore company ID\n  email: '<your-email>',                // your registered email\n  password: '<your-password>',          // your connection password\n  queues: [\n    { queueName: 'my-queue', routingKey: '#' }, // '#' receives all message types\n  ],\n  oddsFormat: ['american', 'fractional'], // optional; decimal odds are always included\n  logging: {\n    directory: './PandaScore_logs',       // optional; omit to disable file logging\n  },\n  recoverOnReconnect: true,               // optional; default true - see Connection Behavior\n  heartbeatMonitoring: true,              // optional; default true - see Connection Behavior\n  prefetchCount: 1,                       // optional; default 1 - unacked messages per consumer\n  recoveryWindowMs: 2 * 60 * 60 * 1000,   // optional; default 2h - see Recovery window\n});\n```\n\n### Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `apiToken` | string | - | REST API authentication token (required) |\n| `company_id` | number | - | Your PandaScore account ID (required) |\n| `email` | string | - | Account email (required to connect to the feed) |\n| `password` | string | - | Account password (required to connect to the feed) |\n| `queues` | array | - | At least one `{ queueName, routingKey }` binding (required to connect to the feed, max 10) |\n| `apiBaseURL` | string | `https://api.pandascore.co/betting/matches` | REST API base URL |\n| `bettingBaseURL` | string | - | Root for the betting catalog and BetBuilder endpoints (leagues/series/tournaments/esports/betbuilder). Optional; derived from `apiBaseURL` by stripping a trailing `/matches` when unset |\n| `feedHost` | string | - | AMQPS feed hostname (required to connect to the feed) |\n| `oddsFormat` | `('american'\\|'fractional')[]` | `[]` | Extra odds formats to compute; decimal is always present |\n| `recoverOnReconnect` | boolean | `true` | Auto-recover markets and matches on reconnect |\n| `heartbeatMonitoring` | boolean | `true` | Detect silent drops via heartbeats; set `false` for a specific routing key |\n| `prefetchCount` | number | `1` | RabbitMQ QoS prefetch per consumer |\n| `recoveryWindowMs` | number | `7200000` | Max startup-recovery lookback for `startWithRecovery` |\n| `logging.directory` | string | `./logs` | File-log directory; omit to disable |\n| `customLogger` | object | - | Inject your own logger (`error/warn/info/debug/logApiResponse`) |\n\n### Computed odds fields\n\nDecimal odds always come straight from the feed. Listing a format in `oddsFormat` makes the SDK\nadd that format to every selection, computed from the decimal values - two fields per format, one\nfrom `odds_decimal` and one from `odds_decimal_with_overround`:\n\n| `oddsFormat` entry | Fields added to each selection |\n|--------------------|-------------------------------|\n| `'american'` | `odds_american`, `odds_american_with_overround` |\n| `'fractional'` | `odds_fractional`, `odds_fractional_with_overround` |\n\n```typescript\n// oddsFormat: ['american', 'fractional']\nselection.odds_decimal;                   // 2.5   from the feed\nselection.odds_decimal_with_overround;    // 2.35  from the feed\nselection.odds_american;                  // 150\nselection.odds_american_with_overround;   // 135\nselection.odds_fractional;                // \"3/2\"\nselection.odds_fractional_with_overround; // \"27/20\"\n```\n\nUse the `_with_overround` pair for what you offer players; the plain pair is the true price before\nyour overround. A field is `null` when its source decimal is `null`, which is what an unpriceable\nselection sends - an eliminated outright participant, or the `\"any-other\"` catch-all on an outright\nmarket. Leave `oddsFormat` unset to skip the computation entirely.\n\nThe same fields are computed on BetBuilder responses, on both the combination and each selection -\nsee [BetBuilder](#betbuilder).\n\n## Quick Start\n\n```typescript\nconst MySDK = PandaSDK.initialize({ /* ...config... */ });\n\n// React to connection lifecycle (disconnection / reconnection / snapshot) and recovery data.\nMySDK.events.on('notification', (n) => {\n  if (n.type === 'disconnection') {\n    // Feed is down - suspend your markets.\n  } else if (n.type === 'reconnection') {\n    if (!n.recoveryRequest) {\n      // Inside the queue TTL. The queue redelivered everything you missed; resume.\n    } else if (n.complete) {\n      // A recovery is in flight. Its messages arrive on your handler, framed by\n      // onRecoveryStarted / onRecoveryCompleted. Resume on the completed message.\n    } else {\n      // The request could not be placed - the window from n.since is still outstanding.\n    }\n  }\n});\n\n// Gate business actions on feed health. This form takes one change object; the\n// FeedListener callback of the same name takes three arguments instead.\nMySDK.onFeedStatusChanged(({ previous, current, reason }) => {\n  console.log(`feed status: ${previous} -> ${current} (${reason})`);\n});\n\n// Start consuming. On a fresh process, prefer startWithSnapshot (see Service Startup Guide).\nawait MySDK.startWithSnapshot((msg) => {\n  // msg.type is \"markets\" | \"fixture\" | \"scoreboard\" | \"settlements\" | ...\n});\n```\n\n## Service Startup Guide\n\nThe right entry point depends on whether your **process restarted** or only the **feed\nconnection dropped**.\n\n| Situation | In-memory state | What to do |\n|---|---|---|\n| Feed dropped, process still running | Intact | Nothing - the SDK reconnects and recovers automatically |\n| First launch | Empty | `startWithSnapshot()` to load all booked matches, then go live |\n| Restart after downtime, with a saved checkpoint | Empty | `startWithRecovery(since)` to replay the gap, then go live |\n| You already hold match state from elsewhere | Provided | `startLive()` - deltas only, no snapshot or replay |\n\n### The three start methods\n\n| Method | Use when |\n|--------|----------|\n| `startWithSnapshot(handler)` | Cold start, no checkpoint. Fetches all booked matches before going live. |\n| `startWithRecovery(since, handler)` | Warm restart with a persisted checkpoint. Replays the gap, then goes live. |\n| `startLive(handler)` | Advanced. You already have match state. No snapshot, no recovery. (`getRMQFeed` is an alias.) |\n\nAll three accept either a raw `(msg) => void` callback or a typed [`FeedListener`](#typed-listener).\nLive messages that arrive while the snapshot or replay is being fetched are buffered and\ndelivered in order afterwards, so the bootstrap data is never overwritten by newer updates.\nThis applies to these three start methods only. A mid-run reconnection buffers nothing: see\n[Reconnection and recovery](#reconnection-and-recovery).\n\n### Cold start\n\n```typescript\n// startWithSnapshot delivers a one-off `snapshot` notification, then streams live.\nMySDK.events.on('notification', (n) => {\n  if (n.type === 'snapshot') {\n    if (n.complete) {\n      const booked = n.recoveryData.bookedMatches; // rebuild your match state from these\n    }\n  }\n});\n\nawait MySDK.startWithSnapshot((msg) => {\n  // live updates, applied on top of the snapshot\n});\n```\n\nYou can also call `fetchBookedMatches()` directly if you want to build state before connecting:\n\n```typescript\nconst booked = await MySDK.fetchBookedMatches();\n```\n\n### Warm restart\n\n```typescript\nconst since = loadCheckpoint(); // the ISO timestamp you persisted via onCheckpoint\ntry {\n  await MySDK.startWithRecovery(since, (msg) => {\n    // live updates\n  });\n} catch (err) {\n  // The checkpoint is older than recoveryWindowMs (STRICT mode, the default).\n  // Replay what we can and accept a partial picture:\n  const { RecoveryWindowExceededError } = await import('@pandascore/odds-sdk');\n  if (err instanceof RecoveryWindowExceededError) {\n    await MySDK.startWithRecovery(since, (msg) => { /* ... */ }, undefined, 'ALLOW_PARTIAL');\n  } else {\n    throw err;\n  }\n}\n```\n\n## Feed Status\n\n`FeedStatus` is a single signal you can gate downstream actions (such as accepting bets)\non. Subscribe with `onFeedStatusChanged`, or read the current value with `getFeedStatus()`.\n\n| Status | Accept bets? | Meaning |\n|--------|--------------|---------|\n| `HEALTHY` | yes | Live, ordered, up to date. |\n| `DELAYED` | yes (usually) | A beat is more than 25s overdue. Usually network jitter or a skipped publish; data is still valid. Treat as a warning, not a stop. |\n| `RECOVERING` | no | A startup snapshot or replay is in progress and live messages are buffered, or a feed recovery is being published. A feed recovery does not buffer: see [Reconnection and recovery](#reconnection-and-recovery). |\n| `PARTIAL_RECOVERY` | no | Known-incomplete state. **Sticky** until you reconcile and call `clearPartialRecovery(reason)`. |\n| `STALE` | no | Heartbeats gone; the connection may still be up. Treat data as untrusted. |\n| `DISCONNECTED` | no | AMQP is down; the SDK is reconnecting. |\n\n```typescript\nlet bettingEnabled = false;\nMySDK.onFeedStatusChanged(({ current }) => {\n  bettingEnabled = current === 'HEALTHY' || current === 'DELAYED';\n});\n```\n\n> **Two shapes, one name.** `MySDK.onFeedStatusChanged` receives a **single**\n> `{ previous, current, reason }` object. `FeedListener.onFeedStatusChanged` receives **three**\n> arguments, `(previous, current, reason)`. Taking the wrong one leaves the status undefined,\n> which silently pins a bet-acceptance flag to `false` rather than throwing.\n\n`PARTIAL_RECOVERY` is reached when a startup replay was clamped to the recovery window, or\nwhen automatic recovery failed after all retries. It does not clear on its own:\n\n```typescript\n// After you have reconciled the missing window out of band:\nMySDK.clearPartialRecovery('reconciled via fetchBookedMatches');\n```\n\n## Connection Behavior\n\n### Heartbeat monitoring\n\nPandaScore sends a heartbeat roughly every 10 seconds on the feed exchange. The watch checks every\n10 seconds and counts a miss when the last beat is more than **25 seconds** old (the 10s interval\nplus a 15s grace). One miss flags `DELAYED`; three consecutive misses mark the feed `STALE` and\nemit a `disconnection` notification, roughly 50 seconds after the last beat.\n\nThe grace is deliberately wider than one interval so a **single skipped beat does not flag a\nhealthy feed**. Publishers skip the occasional beat, and a feed that misses one while still\ndelivering messages is not degraded. Two consecutive skips still register.\n\n> **Heartbeats are a liveness signal, not just a keepalive.** If beats stop while messages are\n> still arriving, do not read the continuing traffic as evidence the feed is fine. `STALE` means\n> the feed's own health signal has gone, whatever else is on the wire, and it is the state to gate\n> bet acceptance on.\n\n\n**This only works if heartbeats reach your queue.** Heartbeats only match the catch-all\nbinding `#`. If you bind to a specific routing key (for example, settlements only),\nheartbeats never arrive and the SDK would emit false disconnection warnings. Set\n`heartbeatMonitoring: false` in that case:\n\n```typescript\nPandaSDK.initialize({\n  // ...\n  heartbeatMonitoring: false,\n  queues: [{ queueName: 'settlements', routingKey: 'v1.*.match.*.settlements.updated' }],\n});\n```\n\nAMQP-level drops (socket error, channel close) are detected immediately and emit a\n`disconnection` regardless of `heartbeatMonitoring` - the flag only controls the\nheartbeat-based timeout.\n\n### Reconnection and recovery\n\nWhen a disconnection is detected the SDK reconnects automatically using **exponential\nbackoff with jitter** (`min(attempt * 5s, 60s)` plus random jitter), so a fleet of clients\ndoes not reconnect in lockstep after an outage.\n\nOnce the feed is flowing again, and if `recoverOnReconnect` is `true`, the SDK compares the\noutage against `queueMessageTtlMs` (default 15 minutes) and does one of three things:\n\n| Outage | What happens |\n|---|---|\n| Within the TTL | **Nothing.** Your queue held the messages and delivers them on reconnect |\n| Past the TTL, within 24 hours | A `windowed` recovery from the last message timestamp the SDK saw |\n| Past 24 hours | A `bootstrap` recovery with `include_recently_settled` |\n\nThe window starts at the **last message timestamp the SDK saw**, not at the moment the outage was\nnoticed. Heartbeats carry a timestamp and arrive every 10 seconds, so that value tracks the feed\nclosely and no safety margin is needed.\n\nWhatever you asked for arrives **on the feed**, as ordinary `fixture` and `markets` messages on\ntheir usual routing keys, framed by two control messages. Live delivery is not held back while it\nruns: every snapshot message carries its market's real last-modification time, so keeping the\nnewest `at` per market applies exactly what you missed.\n\n```typescript\nMySDK.events.on('notification', async (n) => {\n  if (n.type !== 'reconnection') return;\n  if (!n.recoveryRequest) {\n    // Inside the queue TTL. Nothing was lost.\n    resumeMarketOperations();\n  } else if (n.complete) {\n    // A recovery is on its way; onRecoveryCompleted tells you when it has all arrived.\n    console.log(`recovery ${n.recoveryRequestId} requested`);\n  } else {\n    // The request itself could not be placed. The window from n.since is still outstanding.\n    await MySDK.requestRecovery({ mode: 'windowed', since: n.since });\n  }\n});\n```\n\nApply the snapshot with your normal handler, keeping the newest `at` per market:\n\n```typescript\nMySDK.startLive({\n  onMarkets: (m) => applyIfNewer(m),       // live and snapshot both land here\n  onFixture: (f) => applyIfNewer(f),\n  onRecoveryStarted: (m) => suspendBetAcceptance(m.recovery_request_id),\n  onRecoveryCompleted: (m) => resumeBetAcceptance(m.recovery_request_id),\n});\n```\n\n> **Your queue must receive `v1.recovery.#`.** The SDK adds that binding to queues it declares.\n> If your queues are created outside the SDK and bound to specific routing keys, add it yourself,\n> with the trailing `#`: a topic exchange matches word by word, so a bare `v1.recovery` matches\n> nothing.\n\nSet `recoverOnReconnect: false` to skip all of this (the reconnect itself still happens).\n\n#### Running more than one consumer\n\nA recovery covers your **company**, not a queue, and we publish the snapshot to your whole vhost.\nEvery feed bound to receive it gets the `started` messages, the snapshot and the `completed`,\nwhichever of your consumers asked.\n\nSo enable the request on exactly one of them. `recoverOnReconnect` is resolved per feed, falling\nback to the global value when you leave it unset:\n\n```typescript\n// The consumer that asks\nconst primary = PandaSDK.initialize({ ...config, recoverOnReconnect: true });\n\n// The others: they receive the same snapshot without asking\nconst secondary = PandaSDK.initialize({ ...config, recoverOnReconnect: false, queues: otherQueues });\n```\n\nA second request while one is running is refused with `429`. That is handled rather than fatal,\nsince the winner's snapshot republishes the same book, but it is noise worth avoiding.\n\n#### What a rate-limited request means\n\n`RecoveryRequestError.isRateLimited` covers two cases, and the SDK tells them apart:\n\n- **Another of your consumers is already recovering.** Its snapshot republishes this feed's book\n  too, so nothing is owed here. The feed stays on course for `HEALTHY` and waits for the\n  `completed` control message.\n- **You asked less than five minutes ago** (fifteen for a bootstrap) **and nothing is running.**\n  The window really is outstanding, so the feed goes `PARTIAL_RECOVERY` and the reconnection\n  notification reports `complete: false`.\n\nIf you call `requestRecovery` yourself and catch a rate-limited error, wait for the running\nrecovery's completed message rather than retrying.\n\n#### Settlement Feed data\n\nA recovery republishes `fixture` and `markets` messages, so a market's settled state comes back\nwith its results. The Settlement Feed is a separate feed and is **not** republished.\n\nIf you consume it and need to catch up after a recovery, call `sdk.fetchSettlements(matchId)` for\nthe matches you care about. A recovery names every match it touches with a `fixture` message before\nits markets, so by the time the `completed` control message arrives you already have the list. Each\nresponse is the full current state for that match, so one call per match is enough.\n\n`recoverSettlements` still applies to `startWithSnapshot` and `startWithRecovery`, which have the\nmatch list in hand. It is not used on a reconnection.\n\n#### Requesting a recovery yourself\n\n```typescript\n// Everything that changed since a timestamp, up to 24 hours back\nconst { request_id } = await MySDK.requestRecovery({ mode: 'windowed', since: lastSeenAt });\n\n// Your whole current book, for a first start or after a long outage\nawait MySDK.requestRecovery({ mode: 'bootstrap', include_recently_settled: true });\n```\n\nOne recovery runs at a time per company, with five minutes between requests and fifteen between\nbootstraps. A request refused for that reason throws `RecoveryRequestError` with\n`isRateLimited === true`; wait for the running recovery's completed message rather than retrying.\n\n#### `recoverMarkets` and `recoverMatchMarkets` (deprecated)\n\nBoth still work and return what they always returned. They cover markets only, reach back at most\nfive hours, and hand you a shape that is not a feed message, so `requestRecovery` is the better\nchoice for new integrations. The automatic reconnect path no longer calls them.\n\n#### Example log output\n\n```\n11:20:07 [WARN]  Disconnection detected at 2026-01-20T11:20:07Z.\n         (notification: disconnection - suspend markets)\n11:20:12 [WARN]  Reconnecting in 7s (attempt #1)\n11:20:58 [INFO]  Reconnection successful at 2026-01-20T11:20:58Z.\n11:20:58 [INFO]  Requested a windowed recovery (request d82018a1-...).\n11:20:58 [INFO]  Feed status: STALE -> RECOVERING (recovery d82018a1-... requested)\n11:21:02 [INFO]  Recovery d82018a1-... started\n11:21:09 [INFO]  Feed status: RECOVERING -> HEALTHY (recovery d82018a1-... completed)\n```\n\n### Recovery window\n\n`recoveryWindowMs` (default 2 hours) bounds how far back `startWithRecovery` will replay.\nThe `recover_markets` endpoint returns the whole window in a single response, so the\npayload grows with your booked-match count.\n\n| Account profile | Recommended `recoveryWindowMs` |\n|-----------------|--------------------------------|\n| Does not book eBattles | `7200000` (2 hours, default) |\n| Books eBattles | a few minutes, e.g. `5 * 60 * 1000` |\n\nA `since` older than the window either throws `RecoveryWindowExceededError` (default,\n`'STRICT'`) or clamps to the window edge and marks the feed `PARTIAL_RECOVERY`\n(`'ALLOW_PARTIAL'`). See [Warm restart](#warm-restart).\n\n### Configuration matrix\n\n| Routing key | `heartbeatMonitoring` | `recoverOnReconnect` | Behavior |\n|---|---|---|---|\n| `#` | `true` (default) | `true` (default) | Full disconnection detection + automatic recovery. Recommended for most integrations. |\n| `#` | `true` (default) | `false` | Disconnection detection and automatic reconnect, but no recovery request and no `v1.recovery.#` binding. Use if you handle recovery yourself. |\n| Specific key | `false` | `false` | No heartbeat detection, no recovery request. AMQP-level drops still reconnect. Use when handling everything in your app. |\n| Specific key | `true` (default) | any | Avoid - heartbeats never arrive and the SDK emits false disconnection warnings. |\n\n## Typed Listener\n\nInstead of a raw callback you can pass a `FeedListener` and implement only the methods you\ncare about. Pass it to any start method.\n\n```typescript\nimport { PandaSDK, FeedListener } from '@pandascore/odds-sdk';\n\nconst listener: FeedListener = {\n  onMarkets(msg) { /* msg: MarketsMessage */ },\n  onFixture(msg) { /* msg: FixtureMessage */ },\n  onScoreboard(msg) { /* msg: ScoreboardMessage - see Scoreboards below */ },\n  onSettlements(msg) { /* msg: SettlementMessage */ },\n  onUnknown(raw) { /* unrecognized type */ },\n\n  // Wired automatically when the listener is passed to a start method:\n  onCheckpoint(ts) { saveCheckpoint(ts); },\n  onFeedStatusChanged(prev, next, reason) { /* gate bets */ },\n};\n\nawait MySDK.startWithSnapshot(listener);\n```\n\nMessages arrive already parsed, and markets are already enriched with the configured odds\nformats, so handlers receive ready-to-use objects.\n\n**Identifying what a message is about:** read `event_type` and `event_id` together. That pair is on\nevery message and is what the routing key carries. `event_type` is `match`, `game` or `serie`, and\n`event_id` is the id of that thing.\n\n`match_id` is a convenience for pointing back at the parent match and is only present where it adds\nsomething, which is why it is typed `match_id?: number | null`. On a game-scoped markets message it\ngives you the parent match; on a match-scoped one it is usually absent, because `event_id` is already\nthe match id. Keying purely on `match_id` therefore drops match-scoped updates, which are a\nsubstantial share of the markets traffic. Key on `event_type` plus `event_id`, and read `match_id`\nwhen you specifically want the parent of a game.\n\n> **A recovery arrives on the listener.** Since 2.2.0 the snapshot published after a\n> disconnection comes through `onMarkets` and `onFixture` like any live message, so a\n> `FeedListener` on its own is enough to stay in sync. `onRecoveryStarted` and\n> `onRecoveryCompleted` frame it, and a snapshot message carries `recovery_request_id` if you\n> want to tell it apart. The `reconnection` notification still reports what the SDK *decided*\n> the outage cost - `recoveryRequest` is `null` when your queue held everything and nothing\n> was requested - so wire `events.on('notification')` as well if you act on that decision.\n> See [`examples/feed_listener_with_recovery.ts`](examples/feed_listener_with_recovery.ts)\n> for the full pattern.\n\n## Scoreboards\n\n`onScoreboard` gives you a `ScoreboardMessage`. A scoreboard's shape depends on the videogame, so switch on `videogame_slug` and narrow with the matching helper:\n\n```typescript\nimport { asCs, asDota2, asLol, scoreboardType } from '@pandascore/odds-sdk';\n\nconst listener: FeedListener = {\n  onScoreboard(message) {\n    switch (message.videogame_slug) {\n      case 'cs-go': {\n        const sb = asCs(message);\n        sb?.games.forEach(g => g.teams.forEach(t => render(t.id, t.round_score)));\n        break;\n      }\n      case 'league-of-legends': {\n        const sb = asLol(message);\n        sb?.games.forEach(g => render(g.id, g.timer));\n        break;\n      }\n      default:\n        // Nothing is unreachable: the payload is on message.scoreboard\n        console.debug('No typed shape for', message.videogame_slug);\n    }\n  },\n};\n```\n\n| `videogame_slug` | Helper |\n|---|---|\n| `cs-go` | `asCs` → `ScoreboardCs` |\n| `league-of-legends` | `asLol` → `ScoreboardLol` |\n| `dota-2` | `asDota2` → `ScoreboardDota2` |\n| `valorant` | `asValorant` → `ScoreboardValorant` |\n| `e-soccer` | `asEsoccer` → `ScoreboardEsoccer` |\n| `e-basketball` | `asEbasketball` → `ScoreboardEbasketball` |\n| `e-hockey` | `asEhockey` → `ScoreboardEhockey` |\n| `e-tennis` | `asEtennis` → `ScoreboardEtennis` |\n\nEach helper returns `null` if the message is for a different videogame or carries no scoreboard, so a mismatched call gives you `null` rather than a wrongly-typed object.\n\n**`scoreboard_type` is not the videogame.** It reports how the data is produced, and you read it with `scoreboardType(message)`:\n\n| Value | Meaning |\n|---|---|\n| `realtime` | Continuously updated as the game progresses. Every videogame except Dota 2. |\n| `snapshot` | Values are captured periodically and hold until the next capture. Dota 2 only. |\n\n**Counters are nullable.** A score, kill count, tower count or gold lead the API has not resolved yet is `null`, which is not the same as `0`. This matters most for `Dota2Game.radiant_gold_lead`, where `0` means the sides are level. On a game that has not started the API may send `null` for one counter and `0` for another in the same object.\n\nTwo per-videogame notes: eTennis groups by `sets`, not `games`, and carries `current_game` per set. Dota 2 is snapshot-based, so age its timer against `snapshotted_at` - the Dota 2 timer carries no timestamp of its own:\n\n```typescript\nconst game = asDota2(message)?.games[0];\nif (game?.snapshotted_at != null && game.timer != null) {\n  const staleSeconds = (Date.now() - Date.parse(game.snapshotted_at)) / 1000;\n  render(game.timer + staleSeconds);\n}\n```\n\n## Checkpointing\n\nTo support warm restarts, persist the latest server timestamp and pass it to\n`startWithRecovery` next time.\n\n```typescript\n// Option A: callback on every message\nMySDK.onCheckpoint((ts) => saveCheckpoint(ts)); // ts is an ISO-8601 string\n\n// Option B: read on demand\nconst latest = MySDK.getLastMessageTimestamp(); // string | null\n```\n\n## Graceful Shutdown\n\n```typescript\nawait MySDK.close(); // stops heartbeat monitoring, cancels reconnects, closes the connection\n```\n\nAfter `close()` the feed will not reconnect or emit further disconnection events.\n\n## HTTP API\n\n```typescript\nconst match    = await MySDK.fetchMatch('979621');\nconst markets  = await MySDK.fetchMarkets('979621');          // flat list of markets\nconst matchMarkets = await MySDK.fetchMatchMarkets('979621'); // videogame + match-level markets + games with position\nconst range    = await MySDK.fetchMatchesRange(fromISO, toISO);\nconst booked   = await MySDK.fetchBookedMatches();           // active statuses, paginated\nconst recovered = await MySDK.recoverMarkets(sinceISO);       // match-level + game-level markets, flat\nconst byMatch  = await MySDK.recoverMatchMarkets(sinceISO);   // the same window, grouped by match\nconst settle   = await MySDK.fetchSettlements(979621);\nconst voidReasons = await MySDK.fetchVoidReasons();          // dictionary of code + label\nconst outrights = await MySDK.getSerieMarkets('10846');      // outrights on a serie (see catalog)\n\n// BetBuilder: price a combination of selections from the same match (see BetBuilder)\nconst betBuilder = await MySDK.updateBetWithSelections('979621', [\n  { market_id: '4-140633-1-bb837bc8-4c0a-bfc1-c9945e3ea545', selection_position: 0 },\n]);\n```\n\n### Publishing RTBL bets\n\nOptional - only needed if you are subscribed to the Real-Time Bet Log package.\n\n```typescript\nawait MySDK.connectToRabbitMQ();\nawait MySDK.createChannel();\n\nconst betData = {\n  event_type: 'bet_placed',\n  bet: {\n    id: 'id-of-the-bet',\n    type: 'single',\n    user_id: 'user-id',\n    cash_amount: 100,\n    currency: 'USD',\n    placed_at: new Date().toISOString(),\n    selections: [\n      { provider: 'PandaScore', provider_market_id: 'market-id', provider_selection_position: 1, decimal_odds: 1.5 },\n    ],\n  },\n};\n\nMySDK.publishBet(\n  betData,\n  (error) => console.error('Error:', error.message),\n  (data) => console.log('Success:', data),\n);\n```\n\n## Betting catalog endpoints\n\nRead-only REST access to the betting catalog: leagues, series, tournaments, videogames (esports)\nand market templates. These sit alongside `/betting/matches` on the API. The SDK resolves the catalog\nroot automatically by stripping a trailing `/matches` from `apiBaseURL`, so an existing\nconfig keeps working with no change. To point the catalog somewhere else, set the optional\n`bettingBaseURL`.\n\n```typescript\n// Leagues\nconst leagues = await MySDK.listLeagues({ filter: { videogame: 'cs-go' }, sort: 'name', page: { size: 20, number: 1 } });\nconst league  = await MySDK.getLeague('lec');            // by numeric id or slug\n\n// Series\nconst series  = await MySDK.listSeries({ filter: { videogame: 'league-of-legends' } });\nconst serie   = await MySDK.getSerie('lec-2025-summer'); // by numeric id or slug\nconst found   = await MySDK.retrieveSerie('LEC', '2025-06-01T12:00:00Z', 'league-of-legends');\nconst outrights = await MySDK.getSerieMarkets('10846');  // outright markets on the serie\n\n// Tournaments\nconst tours   = await MySDK.listTournaments({ sort: 'begin_at' });\nconst tour    = await MySDK.getTournament('lec-2025-summer-playoffs'); // by numeric id or slug\nconst foundT  = await MySDK.retrieveTournament('Playoffs', '2025-06-01T12:00:00Z', 'league-of-legends');\n\n// Videogames (esports)\nconst csgo    = await MySDK.getVideogame('cs-go');       // by numeric id or slug\n\n// Market templates\nconst templates   = await MySDK.listMarketTemplates();\nconst csTemplates = await MySDK.listMarketTemplates({ filter: { videogame_slug: 'cs-go' } });\n```\n\n### Market templates\n\n`listMarketTemplates()` returns every market template with its display name and where it is\noffered. `template` is the same value a market carries on the feed, so this is how you resolve a\ntemplate slug to something you can show, rather than hard-coding a table:\n\n```typescript\nconst nameByTemplate = new Map((await MySDK.listMarketTemplates()).map((t) => [t.template, t.name]));\n\n// later, on a market off the feed\nconst label = nameByTemplate.get(market.template) ?? market.template;\n```\n\nEach `availabilities` entry pairs an `event_type` with a `videogame_slug`, so a template offered on\nseveral videogames has one entry per videogame. The response is a flat list with no paging.\nFilters: `template`, `videogame_slug`.\n\n`event_type` is kept open, so a value added server-side arrives as a plain string rather than\nbreaking your build. Enumerate what this version knows with `MARKET_TEMPLATE_EVENT_TYPES`, and\nnarrow with `isKnownMarketTemplateEventType` when you want an exhaustive switch:\n\n```typescript\nimport { isKnownMarketTemplateEventType } from '@pandascore/odds-sdk';\n\nfor (const a of template.availabilities) {\n  if (!isKnownMarketTemplateEventType(a.event_type)) {\n    logger.info(`New event type: ${a.event_type}`);\n    continue;\n  }\n  switch (a.event_type) {\n    case 'match': offerOnMatch(template, a.videogame_slug); break;\n    case 'game':  offerOnGame(template, a.videogame_slug); break;\n    case 'serie': offerOnSerie(template, a.videogame_slug); break;\n  }\n}\n```\n\n| Method | Endpoint | Returns |\n|--------|----------|---------|\n| `listLeagues(query?)` | `GET {root}/leagues` | `League[]` |\n| `getLeague(idOrSlug)` | `GET {root}/leagues/{id_or_slug}` | `League` |\n| `listSeries(query?)` | `GET {root}/series` | `FixtureSerie[]` |\n| `getSerie(idOrSlug)` | `GET {root}/series/{id_or_slug}` | `FixtureSerie` |\n| `retrieveSerie(name, scheduledAt, videogameSlug)` | `GET {root}/series/retrieve` | `FixtureSerie` |\n| `getSerieMarkets(idOrSlug)` | `GET {root}/series/{id_or_slug}/markets` | `EnrichedMarket[]` |\n| `listMarketTemplates(query?)` | `GET {root}/market_templates` | `MarketTemplate[]` |\n| `listTournaments(query?)` | `GET {root}/tournaments` | `Tournament[]` |\n| `getTournament(idOrSlug)` | `GET {root}/tournaments/{id_or_slug}` | `Tournament` |\n| `retrieveTournament(name, scheduledAt, videogameSlug)` | `GET {root}/tournaments/retrieve` | `Tournament` |\n| `getVideogame(idOrSlug)` | `GET {root}/esports/{id_or_slug}` | `Videogame` |\n\n`{root}` is the derived `/betting` root. The list methods take an optional `CatalogQuery`\n(`filter`, `range`, `sort`, `page`) and return a single page - pass `page` to walk through\nresults. Omit the query for the first page at the server default page size.\n\n### Serie shapes\n\nA serie arrives in two shapes and the difference matters:\n\n| Shape | Where it comes from | Extra fields |\n|-------|--------------------|--------------|\n| Full | `FixtureMessage.serie` on `event_type: \"serie\"` events, and every `/betting/series` method above | `opponents`, `betting_metadata`, `videogame`, `league_name`, `league_image_url`, `widget_supported` |\n| Reduced | `FixtureMessage.match.serie`, `fetchMatch`, `fetchMatchesRange` | none of them |\n\nSo if you need a serie's participants or its trading metadata, read a serie fixture event or call\n`getSerie` - they are not on the serie nested under a match. `videogame` is not the same field as\n`videogame_title`: a serie can run on Counter-Strike (`cs-go`) while its title is Counter-Strike 2\n(`cs-2`).\n\n### Outright markets\n\n`getSerieMarkets` returns the outright markets on a serie - priced on the serie as a whole rather\nthan on a single match, which is why they are not reachable through `fetchMarkets`. A serie carries\none market with `template: \"outright-winner\"`, whose selections are one `template: \"participant\"`\nentry per competitor, lining up with the serie's own `opponents`, plus an optional `\"any-other\"`\ncatch-all. Selections are enriched with the odds formats in `oddsFormat`, same as match markets.\n\nA serie has no per-game grouping, so the result is a flat market list - there is no grouped\ncounterpart to `fetchMatchMarkets`. Pass the `event_id` of a serie fixture event straight through.\n\n`EnrichedMarket` is the market as the feed sends it with the computed odds fields added, so\n`name`, `template`, `participant_id`, `position` and the rest are fully typed and need no cast:\n\n```typescript\nMySDK.startLive({\n  onFixture: async (msg) => {\n    if (msg.event_type !== 'serie') return;\n    const outrights = await MySDK.getSerieMarkets(msg.event_id);\n    // A selection that cannot be priced - an eliminated participant, or \"any-other\" - carries\n    // null odds rather than a fabricated price.\n    for (const market of outrights) {\n      for (const sel of market.selections) {\n        if (sel.odds_decimal_with_overround !== null) render(sel.name, sel.odds_decimal_with_overround);\n      }\n    }\n  },\n});\n```\n\n## BetBuilder\n\nBetBuilder prices a combination of market selections from the same match. The API is stateless, so\nevery recalculation sends the whole current combination and returns both the combined price and the\nrecomputed probability of every other selection.\n\nBetBuilder is REST only. It needs no feed credentials, so an `apiToken` and a `company_id` are\nenough to configure the SDK for it.\n\n```typescript\nimport { PandaSDK, isValid, isCombinable, marketsByGame, matchMarkets } from '@pandascore/odds-sdk';\n\nconst MySDK = PandaSDK.initialize({ apiToken: 'YOUR_TOKEN', company_id: 123 });\n\n// 1. Bet slip opens: everything available on this match, at normal prices\nconst all = await MySDK.fetchAllMarkets('999600');\n\n// 2. User picks two selections: price the combination\nconst priced = await MySDK.updateBetWithSelections('999600', [\n  { market_id: '4-140633-1-bb837bc8-4c0a-bfc1-c9945e3ea545', selection_position: 0 },\n  { market_id: '4-140633-1-80692552-4e63-b30e-6782b13785e8', selection_position: 0 },\n]);\n\nif (isValid(priced)) {\n  const odds = priced.odds_decimal_with_overround; // the combined price\n} else {\n  // The combination was rejected. The markets are still populated and the probabilities\n  // are zeroed, so the bet slip can stay on screen.\n  for (const error of priced.market_selections_errors ?? []) {\n    error.error;              // e.g. 'market_selection_suspended'\n    error.market_id;\n    error.selection_position;\n  }\n}\n```\n\n| Method | Endpoint | Returns |\n|--------|----------|---------|\n| `fetchAllMarkets(matchId, filterMarkets?)` | `POST {root}/betbuilder/matches/{matchId}` | `BetBuilderResponse` |\n| `updateBetWithSelections(matchId, selections, filterMarkets?)` | same | `BetBuilderResponse` |\n\n`{root}` is the derived `/betting` root, the same one the catalog endpoints use.\n`fetchAllMarkets` is `updateBetWithSelections` with an empty combination. `filterMarkets` is\ndescribed under [Rendering a bet slip](#rendering-a-bet-slip) below.\n\n### Reading the response\n\n`market_selections` is the combination the server actually priced, which can differ from what you\nsent when a leg had to be dropped. Use it rather than your own local state.\n\nA selection with `probability` of 0 cannot be added to the current combination. Test it with\n`isCombinable(selection)`.\n\n`isValid(response)` tells you whether the combination was accepted. When it is false,\n`market_selections_errors` names the legs to remove. This does not reject, because the response\nstill contains everything needed to keep a bet slip rendered.\n\n### Rendering a bet slip\n\n**The one rule: never render a selection as selectable when `isCombinable` is false.** Follow it and\nthe whole class of invalid-combination errors becomes unreachable, because the user can never click\nsomething the server would reject.\n\nEach call tells you what is still available. As the user adds legs, selections that can no longer be\ncombined come back with a probability of 0, and their derived odds are `null`. Show those greyed out\nand unclickable.\n\n```typescript\nimport { isCombinable } from '@pandascore/odds-sdk';\n\nfor (const market of response.markets) {\n  for (const selection of market.selections) {\n    if (isCombinable(selection)) {\n      render(selection.name, selection.odds_decimal_with_overround);\n    } else {\n      renderDisabled(selection.name); // no price: there isn't one\n    }\n  }\n}\n```\n\nOdds are `null` exactly when a selection is not combinable, so a disabled selection never needs a\nprice. Do not substitute `0` or `1.0` for the null, or it will end up multiplied into a payout.\n\n**Do not use `filterMarkets: true` for a bet slip.** It removes uncombinable selections from the\npayload entirely, so once the user has picked a leg whole markets and games silently vanish from your\nUI and they cannot see they existed. Keep them and grey them out instead.\n\n`filterMarkets: true` is for callers that only want the combinable set and have no UI to grey out,\nsuch as a backend generating suggested combinations.\n\n### Recovering from an invalid combination\n\nIf an invalid combination does reach the API, every selection in the response comes back with a\nprobability of 0, so the market list offers no way out. Make sure users can **remove a leg from the\nbet slip itself** rather than relying on clicking markets.\n\n### Combinations across games\n\nMarkets from several games of the same match arrive in one flat list, each tagged with the event it\nbelongs to. Two helpers split it:\n\n```typescript\n// One group per game, for rendering per-game tabs\nconst byGame: Map<number, BetBuilderMarket[]> = marketsByGame(priced);\n\n// Markets attached to the match as a whole\nconst matchLevel = matchMarkets(priced);\n```\n\n### Derived odds\n\nThe API returns probabilities only. The SDK derives decimal odds as `1 / probability` and exposes\nthem on both the response and each selection. Add `'american'` or `'fractional'` to `oddsFormat` to\nget those forms too, exactly as for feed markets.\n\n### Errors\n\nA rejected request throws `BetBuilderApiError`, carrying `status`, `type`, `errorCode` and\n`rawBody`, so you can tell a match that is not booked from one whose markets have not been created.\n\n```typescript\nimport { BetBuilderApiError } from '@pandascore/odds-sdk';\n\ntry {\n  await MySDK.fetchAllMarkets(matchId);\n} catch (err) {\n  if (err instanceof BetBuilderApiError && err.errorCode === 'not_booked') {\n    // book the match first\n  }\n}\n```\n\nWhich videogames and markets are supported, how combinations across games are limited, and what\neach error code means are all covered in the BetBuilder integration documentation.\n\n### Keeping a combination up to date\n\nPrices move. When your feed listener reports a change on a market in the user's combination, call\n`updateBetWithSelections` again with the same combination to get the current price.\n\n## Settlement Feed\n\n> **Note:** This feed is not a replacement for market settlements. Use it when PandaScore\n> market settlements are not in use, or when you manage your own settlements and need the\n> raw outcome data. Coverage: CS2, Dota 2, League of Legends.\n\n### REST snapshot\n\n```typescript\nimport { SettlementMessage } from '@pandascore/odds-sdk';\n\nconst settlements: SettlementMessage = await MySDK.fetchSettlements(979621);\nconsole.log(settlements.videogame_slug); // \"cs-go\" | \"dota-2\" | \"league-of-legends\"\nconsole.log(settlements.games);          // per-game settlement data\n```\n\nThe endpoint returns `404` if the match is not in an emitting state (`not_started`,\n`postponed`, `canceled`).\n\n### Streaming\n\nSettlement updates carry `type: \"settlements\"` and arrive through the same handler as\nother feed messages (or `onSettlements` on a typed listener).\n\n```typescript\nawait MySDK.startLive((msg) => {\n  if (msg.type === 'settlements') {\n    for (const game of msg.games) {\n      console.log(`Game ${game.position}: ${game.status}`);\n    }\n  }\n});\n```\n\nTo receive only settlements, bind your queue with `v1.*.match.*.settlements.updated`\n(or `v1.cs-go.match.*.settlements.updated` for a specific videogame) and set\n`heartbeatMonitoring: false` - see the [Configuration matrix](#configuration-matrix).\n\n#### Settlement status values\n\n| Status | Meaning |\n|--------|---------|\n| `pending` | Not yet resolved. Value is `null`. Expect a future update. |\n| `settled` | Resolved. Value is populated. |\n| `voided` | Resolved as void (the underlying event did not happen). Value is `null`. |\n| `unavailable` | The game is finished and this outcome will never resolve. Settle per your void policy. |\n\n#### Per-videogame settlement types\n\n```typescript\nimport type { Cs2Settlements, Dota2Settlements, LolSettlements } from '@pandascore/odds-sdk';\n```\n\n- **CS2** (`Cs2Settlements`): `winner`, `winner_first_half`, `rounds_won`, `first_to_n_rounds`, `round_winners`, `participants_winning_n_rounds`, `player_kills`, `player_headshots`\n- **Dota 2** (`Dota2Settlements`): `winner`, `duration_seconds`, `first_kill`, `first_tower_destroyed`, `first_roshan_killed`, `team_kills`, `team_towers_destroyed`, `team_barracks_destroyed`, `team_roshans_killed`, `player_kills`, `kill_snapshots_at_ingame_timer_seconds`\n- **League of Legends** (`LolSettlements`): `winner`, `first_kill`, `first_tower_destroyed`, `first_inhibitor_destroyed`, `team_kills`, `team_towers_destroyed`, `team_inhibitors_destroyed`, `team_nashors_killed`, `team_drakes_killed`, `player_kills`, `player_assists`\n\n#### Recommended integration pattern\n\nSubscribe to the streaming feed for live updates and call `fetchSettlements` on reconnect\nto backfill missed messages. The REST snapshot always reflects the full current state, so\na missed streaming message never causes permanent inconsistency.\n\n## Void reasons\n\nWhen a market is voided, it carries a structured `void_reason` explaining why. It appears on\nthe `markets` feed messages and on the Odds API market responses (so `fetchMarkets` returns it\ntoo). Reasons are on **markets** only - the settlement feed never carries them.\n\n```typescript\nimport type { MarketsMessage } from '@pandascore/odds-sdk';\n\nconst listener = {\n  onMarkets(msg: MarketsMessage) {\n    for (const market of msg.markets) {\n      if (market.void_reason) {\n        // whole market voided - key logic on the code, not the label\n        market.void_reason.code;      // e.g. \"game-forfeited\"\n        market.void_reason.label;     // \"Game forfeited\"\n        market.void_reason.incident;  // { category, description } | null\n      }\n      // markets with independent outcomes (e.g. player-kill-to-get-n) can void a\n      // single selection while the others resolve:\n      for (const sel of market.selections) {\n        if (sel.void_reason) {\n          // this selection was voided on its own\n        }\n      }\n    }\n  },\n};\n```\n\n`void_reason` is `null` when a market/selection is not voided, was voided before this release\n(no backfill), or voided without a known reason. When only some selections are voided the\nmarket-level `void_reason` stays `null` and each voided selection carries its own.\n\n### Void reason codes\n\n`VoidReasonCode` is an open union - unknown codes added server-side still type-check, so key\nyour logic on the code and fall back to the `label` for anything unrecognised. Fetch the live\ndictionary with `MySDK.fetchVoidReasons()`.\n\n| Code | Label |\n|------|-------|\n| `game-not-played` | Game not played |\n| `game-forfeited` | Game forfeited |\n| `match-forfeited` | Match forfeited |\n| `match-canceled` | Match canceled |\n| `change-of-opponents` | Change of opponents |\n| `match-format-changed` | Match format changed |\n| `team-withdrew-from-tournament` | Team withdrew from tournament |\n| `tournament-canceled` | Tournament canceled |\n| `wrong-odds` | Wrong odds |\n| `objective-not-taken` | Objective not taken |\n| `player-did-not-play` | Player did not play |\n| `two-way-tie` | Two-way tie |\n| `early-gg` | Early GG |\n| `interval-not-started-or-finished` | Interval not started or finished |\n| `other` | Other |\n\n## Data Models\n\nAll message and entity types are exported from the package and available as TypeScript\ntypes:\n\n- **Markets**: `MarketsMessage`, `MarketsMessageMarket`, `MarketsMessageSelection`\n- **Fixtures**: `FixtureMessage`, and the nested league, tournament, videogame, game, player and team types\n- **Scoreboards**: `ScoreboardCs`, `ScoreboardDota2`, `ScoreboardLol`, `ScoreboardValorant`, `ScoreboardEsoccer`, `ScoreboardEbasketball`, `ScoreboardEhockey`, `ScoreboardEtennis`\n- **Settlements**: `SettlementMessage`, `SettlementGame`, `Cs2Settlements`, `Dota2Settlements`, `LolSettlements`, and the per-entry types\n- **Void reasons**: `VoidReason`, `VoidReasonIncident`, `VoidReasonCode`, `VoidReasonDefinition`\n- **BetBuilder**: `BetBuilderResponse`, `BetBuilderMarket`, `BetBuilderSelection`, `MarketSelection`, `MarketSelectionError`, `MarketSelectionErrorCode`, `BetBuilderEventType`, `BetBuilderMarketStatus`\n- **SDK**: `FeedStatus`, `StatusChange`, `FeedListener`, `RecoveryMode`, `RecoveryWindowExceededError`, `BetBuilderApiError`\n\n### Fixture actions\n\n`FixtureMessage.action` says what happened to the fixture:\n\n| Action | Meaning |\n|--------|---------|\n| `created` | Fixture appeared in the schedule |\n| `booked` / `unbooked` | Added to or removed from your booked offer |\n| `reviewed` | Checked by a PandaScore trader |\n| `updated` | Any other change to the fixture |\n| `rescheduled` / `postponed` | Start time moved, or pushed with no new time |\n| `started` / `finished` | Play began, or the last game ended |\n| `settled` | All markets resolved |\n| `coverage_changed` | Coverage level changed |\n| `canceled` / `deleted` | Called off, or removed from the schedule |\n| `opponents_swapped` / `opponents_updated` | Home/away flipped, or participants changed |\n\n#### `deleted` carries no match\n\n`deleted` is the one action that arrives without a body. The match has been removed from the offer,\nso there is no record left to send and `message.match` is `null` - the envelope is the whole\nmessage. Identify the match by `event_id`, which is the match id on an `event_type: \"match\"` event:\n\n```typescript\nonFixture(message) {\n  if (message.action === 'deleted' && message.event_type === 'match') {\n    removeFromOffer(message.event_id); // message.match is null here\n    return;\n  }\n  applyUpdate(message.match);\n}\n```\n\n**Reading `message.match.id` on a deleted event throws**, and the throw reaches your consumer\ncallback, so the delivery is requeued rather than processed. Guard with `message.match?.id`, or\nbranch on the action first as above. A `deleted` event is only sent before markets are created, so\nif you are already holding markets for a match this is not the message that removes it.\n\nAlso make sure your queue binding actually routes it. The routing key is\n`{version}.{videogame_slug}.{event_type}.{event_id}.{type}.{action}`, so this message ships as\n`v1.cs-go.match.1632625.fixture.deleted`. A binding that enumerates actions rather than ending in\n`.*` or `.#` will silently exclude it.\n\n`live_available` and `live_not_available` are legacy actions, still sent for backward compatibility.\nRead `betting_metadata.live_available` on the match instead.\n\nThe type stays open, so an action added server-side arrives as a plain string rather than breaking\nyour build. Enumerate the ones this version knows with `FIXTURE_ACTIONS`, and narrow with\n`isKnownFixtureAction` when you want an exhaustive switch:\n\n```typescript\nimport { isKnownFixtureAction } from '@pandascore/odds-sdk';\n\nif (!isKnownFixtureAction(msg.action)) {\n  logger.warn(`Unrecognised fixture action: ${msg.action}`); // treat as a plain update\n  return;\n}\nswitch (msg.action) {\n  case 'settled': /* ... */ break;\n  // ...\n}\n```\n\n### Market actions\n\n`MarketsMessage.action` is typed the same open way as fixture actions, with `MARKET_ACTIONS` and\n`isKnownMarketAction` alongside it:\n\n| Action | Meaning |\n|--------|---------|\n| `created` | Market appeared |\n| `odds_changed` | Prices moved |\n| `margin_changed` | Your margin was reapplied |\n| `suspended` / `deactivated` | Temporarily closed, or withdrawn |\n| `settled` / `partially_settled` | Resolved, fully or in part |\n| `rollback_settlement` | A settlement was reversed |\n| `opponent_updated` | A participant on the market changed |\n\n### Match and game status are different sets\n\n`MatchStatus` and `GameStatus` do not share values beyond `finished`, and mixing them up is easy:\n\n| | Values |\n|---|---|\n| `MatchStatus` | `not_booked`, `pending`, `pre_match`, `live`, `postponed`, `finished`, `settled`, `canceled` |\n| `GameStatus` | `not_started`, `running`, `finished`, `not_played` |\n\nA match is never `not_started` or `running`; a game is never `live` or `pending`. The two sets\noverlap only on `finished`. Both are also exported as runtime arrays (`MATCH_STATUSES`,\n`GAME_STATUSES`, `MATCH_TYPES`) for validation.\n\n> **Upgrading from 1.5.0:** the status types are kept open so a value added server-side does not\n> break your build, which also means `match.status === 'running'` still compiles - it simply never\n> matches. Search your code for `not_started` and `running` on a match and move them to `pending`,\n> `pre_match` or `live`.\n\n### Videogame-specific match fields\n\nA few fields on `FixtureMatch` only populate for one videogame:\n\n| Field | Videogame | Notes |\n|-------|-----------|-------|\n| `map_type_order` | Overwatch | Map types played, in order: `assault`, `control`, `escort`, `hybrid`. Empty array on every other videogame |\n| `games[].game_round_teams` | Counter-Strike | Round-by-round side and outcome |\n| `games[].rounds_score` | Counter-Strike | Per-team round score |\n| `games[].map`, `games[].number_of_rounds` | Team videogames | Absent on eBattles, where a game is not played on a map and has no rounds |\n\n### Reading match scores\n\n`FixtureMatch.results` is one entry per competitor. Which identifier the entry carries follows\n`opponents[i].type`:\n\n| Opponents | Identifier | Videogames |\n|-----------|------------|------------|\n| `Team` | `team_id`, no `player_id` | Counter-Strike, Dota 2, League of Legends, Valorant, Overwatch, Rocket League, ... |\n| `Player` | `player_id`, no `team_id` | eBattles: eBasketball, eSoccer, eHockey, eTennis |\n\nUse `competitorId(result)` when you only need the identifier and not which kind it is - it returns\nwhichever of the two the match carries, so the same code covers both:\n\n```typescript\nimport { competitorId } from '@pandascore/odds-sdk';\n\nconst scoreByCompetitor = new Map(match.results.map((r) => [competitorId(r), r.score]));\n\nfor (const { opponent, playing_as } of match.opponents) {\n  // On eBattles, `name` is the player and `playing_as` the team they are playing as\n  console.log(opponent.name, playing_as, scoreByCompetitor.get(opponent.id));\n}\n```\n\n## Testing\n\n```bash\nnpm test          # run the unit suite once (vitest)\nnpm run test:watch\n```\n\nThe suite covers the feed-status state machine, recovery retry and failure signaling,\nin-order buffer draining, the heartbeat state machine, and typed-listener dispatch - all\noffline, with no network or broker required.\n","readmeFilename":"README.md"}