diff --git a/package-lock.json b/package-lock.json index 4790e57..6b4bb74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1932,7 +1932,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/abi-wan-kanabi": { @@ -2094,7 +2093,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, "license": "ISC", "engines": { "node": ">= 4.0.0" @@ -2259,7 +2257,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -2330,7 +2327,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2431,7 +2427,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -2450,7 +2445,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2464,7 +2458,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2551,7 +2544,6 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, "funding": [ { "type": "github", @@ -2739,7 +2731,6 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/convert-source-map": { @@ -2795,7 +2786,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2884,7 +2874,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -2953,7 +2942,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3042,7 +3030,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3052,7 +3039,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3062,7 +3048,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3566,7 +3551,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3596,7 +3580,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", - "dev": true, "license": "Apache-2.0", "dependencies": { "micromatch": "^4.0.2" @@ -3648,7 +3631,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -3670,7 +3652,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3721,7 +3702,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3756,7 +3736,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3790,7 +3769,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -3824,7 +3802,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -3835,7 +3812,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3864,7 +3840,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3890,7 +3865,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3900,7 +3874,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -3913,7 +3886,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3926,7 +3898,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4062,7 +4033,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -4092,7 +4062,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -4154,7 +4123,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -4193,7 +4161,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0" @@ -4206,14 +4173,12 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -5212,7 +5177,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -5264,7 +5228,6 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", - "dev": true, "license": "Public Domain", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5284,7 +5247,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.1.11" @@ -5767,7 +5729,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5794,7 +5755,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5984,7 +5944,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6019,7 +5978,6 @@ "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0", @@ -6054,7 +6012,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6173,7 +6130,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", - "dev": true, "license": "MIT", "dependencies": { "@yarnpkg/lockfile": "^1.1.0", @@ -6204,7 +6160,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -6220,7 +6175,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", @@ -6237,7 +6191,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -6250,7 +6203,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6270,7 +6222,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6280,7 +6231,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6394,7 +6344,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -6992,7 +6941,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -7010,7 +6958,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -7023,7 +6970,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7349,7 +7295,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -7476,7 +7421,6 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" @@ -7496,7 +7440,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -7744,7 +7687,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -7867,7 +7809,6 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -7973,6 +7914,7 @@ "better-sqlite3": "^12.2.0", "commander": "^11.1.0", "mysql2": "^3.14.1", + "patch-package": "^8.0.0", "pg": "^8.11.0", "starknet": "9.3.0", "terminal-size": "^4.0.0" diff --git a/packages/auco-indexer/src/core/__tests__/indexer.test.ts b/packages/auco-indexer/src/core/__tests__/indexer.test.ts index f2baedb..8496e05 100644 --- a/packages/auco-indexer/src/core/__tests__/indexer.test.ts +++ b/packages/auco-indexer/src/core/__tests__/indexer.test.ts @@ -228,11 +228,6 @@ describe('StarknetIndexer', () => { afterEach(async () => { // Clear any timers that might be running - if ((mockIndexer as any).retryTimeout) { - clearTimeout((mockIndexer as any).retryTimeout); - (mockIndexer as any).retryTimeout = undefined; - } - if ((mockIndexer as any).pollTimeout) { clearTimeout((mockIndexer as any).pollTimeout); (mockIndexer as any).pollTimeout = undefined; @@ -804,7 +799,7 @@ describe('StarknetIndexer', () => { }); }); - describe('failed block retry mechanism', () => { + describe('halt and retry on block failure', () => { let mockIndexer: StarknetIndexer; let mockDbHandler: any; let mockProvider: any; @@ -868,7 +863,10 @@ describe('StarknetIndexer', () => { jest.clearAllMocks(); }); - it('should add failed blocks to retry queue', () => { + it('should halt when a block fails processing', async () => { + // Make insertBlock throw to simulate a block processing failure + mockDbHandler.insertBlock.mockRejectedValueOnce(new Error('DB write failed')); + const blockData = { block_number: 50, block_hash: '0xblock50', @@ -876,39 +874,165 @@ describe('StarknetIndexer', () => { timestamp: Date.now(), }; - (mockIndexer as any).addFailedBlock(blockData, new Error('Processing failed')); + await (mockIndexer as any).processNewHead(blockData); - const failedBlocks = (mockIndexer as any).failedBlocks; - expect(failedBlocks).toContain(50); + expect((mockIndexer as any).halted).toBe(true); + expect((mockIndexer as any).haltReason).toEqual({ + blockNumber: 50, + error: expect.any(Error), + }); }); - it('should not duplicate failed blocks', () => { + it('should not process new blocks after halt', async () => { + // Manually halt the indexer + (mockIndexer as any).halt(42, new Error('test halt')); + const blockData = { - block_number: 50, - block_hash: '0xblock50', - parent_hash: '0xblock49', + block_number: 43, + block_hash: '0xblock43', + parent_hash: '0xblock42', timestamp: Date.now(), }; - (mockIndexer as any).addFailedBlock(blockData, new Error('Processing failed')); - (mockIndexer as any).addFailedBlock(blockData, new Error('Processing failed again')); + await (mockIndexer as any).processNewHead(blockData); + + // insertBlock should never be called because the indexer is halted + expect(mockDbHandler.insertBlock).not.toHaveBeenCalled(); + }); + + it('should stop historical block processing when halted', async () => { + // Halt after the first chunk starts + mockDbHandler.checkIsBlockProcessed.mockImplementation(async (blockNumber: number) => { + if (blockNumber >= 1) { + (mockIndexer as any).halt(1, new Error('test halt')); + } + return false; + }); + + await (mockIndexer as any).processHistoricalBlocks(1, 200); + + // Should not have processed the second chunk (blocks 101-200) + // because the indexer halted during or after the first chunk + expect((mockIndexer as any).halted).toBe(true); + }); + + it('should expose halt state via getHaltState()', () => { + // Initially not halted + const initialState = mockIndexer.getHaltState(); + expect(initialState).toEqual({ halted: false, reason: null }); + + // After halting + (mockIndexer as any).halt(99, new Error('fatal error')); + + const haltedState = mockIndexer.getHaltState(); + expect(haltedState.halted).toBe(true); + expect(haltedState.reason).toEqual({ + blockNumber: 99, + error: expect.any(Error), + }); + }); + + it('should add failed block to retry queue when halting', () => { + (mockIndexer as any).halt(42, new Error('test halt')); + + expect((mockIndexer as any).failedBlocks).toContain(42); + expect((mockIndexer as any).retryTimeout).toBeDefined(); + }); + + it('should not duplicate failed blocks in retry queue', () => { + (mockIndexer as any).halt(42, new Error('test halt 1')); + (mockIndexer as any).halt(42, new Error('test halt 2')); const failedBlocks = (mockIndexer as any).failedBlocks; - expect(failedBlocks.filter((block: number) => block === 50)).toHaveLength(1); + expect(failedBlocks.filter((b: number) => b === 42)).toHaveLength(1); }); - it('should retry failed blocks successfully', async () => { - (mockIndexer as any).failedBlocks = [45, 46]; + it('should resume after successful retry', async () => { + // Halt the indexer + (mockIndexer as any).halt(50, new Error('temporary failure')); + expect((mockIndexer as any).halted).toBe(true); - // Mock processHistoricalBlocks to avoid complex dependencies - (mockIndexer as any).processHistoricalBlocks = jest.fn().mockResolvedValue(undefined); + // Mock getBlock to return a valid block for the retry + mockProvider.getBlock.mockResolvedValue({ + block_number: 50, + block_hash: '0xblock50', + parent_hash: '0xblock49', + timestamp: Date.now(), + }); + + // Mock withExponentialBackoff to just run the function + (mockIndexer as any).withExponentialBackoff = jest + .fn() + .mockImplementation(async (_op: string, fn: () => Promise) => { + return await fn(); + }); + + // Clear halt state to allow retry to run (simulating what startRetryProcess does) + (mockIndexer as any).halted = false; + (mockIndexer as any).haltReason = null; await (mockIndexer as any).retryFailedBlocks(); - expect((mockIndexer as any).processHistoricalBlocks).toHaveBeenCalledWith(45, 45); - expect((mockIndexer as any).processHistoricalBlocks).toHaveBeenCalledWith(46, 46); + expect((mockIndexer as any).halted).toBe(false); + expect((mockIndexer as any).haltReason).toBeNull(); expect((mockIndexer as any).failedBlocks).toHaveLength(0); }); + + it('should commit partial progress — blocks before failure are committed', async () => { + // Setup: blocks 1-5, block 3 fails + const blocks = [1, 2, 3, 4, 5]; + + mockDbHandler.checkIsBlockProcessed.mockResolvedValue(false); + + mockProvider.getBlock.mockImplementation(async (blockNumber: number) => ({ + block_number: blockNumber, + block_hash: `0xblock${blockNumber}`, + parent_hash: `0xblock${blockNumber - 1}`, + timestamp: Date.now(), + })); + + // Make the transaction fail for block 3 + let insertCallCount = 0; + mockDbHandler.withTransaction.mockImplementation(async (fn: () => Promise) => { + insertCallCount++; + if (insertCallCount === 3) { + // Block 3's transaction fails + throw new Error('DB write failed for block 3'); + } + return await fn(); + }); + + // Mock withExponentialBackoff to just run the function + (mockIndexer as any).withExponentialBackoff = jest + .fn() + .mockImplementation(async (_op: string, fn: () => Promise) => { + return await fn(); + }); + + await (mockIndexer as any).processHistoricalBlocks(1, 5); + + // Blocks 1 and 2 should have been inserted (their transactions succeeded) + expect(mockDbHandler.insertBlock).toHaveBeenCalledTimes(2); + expect(mockDbHandler.insertBlock).toHaveBeenCalledWith( + expect.objectContaining({ block_number: 1 }) + ); + expect(mockDbHandler.insertBlock).toHaveBeenCalledWith( + expect.objectContaining({ block_number: 2 }) + ); + + // Cursor should have been updated for blocks 1 and 2 + expect(mockDbHandler.updateCursor).toHaveBeenCalledWith(1, '0xblock1', undefined); + expect(mockDbHandler.updateCursor).toHaveBeenCalledWith(2, '0xblock2', undefined); + + // Block 3 should NOT have been inserted (its transaction failed) + expect(mockDbHandler.insertBlock).not.toHaveBeenCalledWith( + expect.objectContaining({ block_number: 3 }) + ); + + // Indexer should be halted at block 3 + expect((mockIndexer as any).halted).toBe(true); + expect((mockIndexer as any).haltReason?.blockNumber).toBe(3); + }); }); describe('exponential backoff retry', () => { diff --git a/packages/auco-indexer/src/core/indexer.ts b/packages/auco-indexer/src/core/indexer.ts index 3b9e293..db9a08a 100644 --- a/packages/auco-indexer/src/core/indexer.ts +++ b/packages/auco-indexer/src/core/indexer.ts @@ -13,7 +13,6 @@ import { import { findContractDeploymentBlock, - groupConsecutiveBlocks, parallelMap, } from '../utils/blockUtils'; import { @@ -61,9 +60,11 @@ export class StarknetIndexer { private readonly dbHandler: BaseDbHandler; private healthCheckInterval?: NodeJS.Timeout; + private halted: boolean = false; + private haltReason: { blockNumber: number; error: any } | null = null; private failedBlocks: number[] = []; private retryTimeout?: NodeJS.Timeout; - private readonly RETRY_INTERVAL = 10000; // 10 seconds between retry checks + private readonly RETRY_INTERVAL: number; private readonly HEALTH_CHECK_INTERVAL = 3000; // 1 second between health checks private readonly reconnectDelay: number = 1000; private readonly MAX_HISTORICAL_BLOCK_CONCURRENT_REQUESTS: number; @@ -101,6 +102,8 @@ export class StarknetIndexer { this.MAX_HISTORICAL_BLOCK_CONCURRENT_REQUESTS = config.maxHistoricalBlockConcurrentRequests ?? 5; + this.RETRY_INTERVAL = config.retryInterval ?? 10000; + // Warn if dev mode reset is enabled if (config.devMode?.resetOnStart) { this.logger.warn( @@ -171,6 +174,10 @@ export class StarknetIndexer { return; } + if (this.isHalted()) { + return; + } + await this.withErrorHandling( 'Processing new head', async () => { @@ -500,6 +507,10 @@ export class StarknetIndexer { // Process a new block head private async processNewHead(blockData: any): Promise { + if (this.isHalted()) { + return; + } + if (this.cursor && blockData.block_number <= this.cursor.blockNumber) { if ( blockData.block_number === this.cursor.blockNumber && @@ -510,34 +521,31 @@ export class StarknetIndexer { } } - try { - await this.withTransaction( - 'Processing block', - async () => { - await this.insertBlock(blockData); - await this.updateCursor(blockData.block_number, blockData.block_hash); - this.logger.info(`Successfully processed block #${blockData.block_number}`); - - if (this.provider) { - await this.processBlockEvents( - blockData.block_number, - blockData.block_number, - blockData.block_hash - ); - } + const result = await this.withTransaction( + 'Processing block', + async () => { + await this.insertBlock(blockData); + await this.updateCursor(blockData.block_number, blockData.block_hash); + this.logger.info(`Successfully processed block #${blockData.block_number}`); + + if (this.provider) { + await this.processBlockEvents( + blockData.block_number, + blockData.block_number, + blockData.block_hash + ); + } - if (this.failedBlocks.length > 0 && this.failedBlocks.includes(blockData.block_number)) { - this.failedBlocks = this.failedBlocks.filter( - (block) => block !== blockData.block_number - ); - } - }, - { blockNumber: blockData.block_number } + return true; // signal success + }, + { blockNumber: blockData.block_number } + ); + + if (result === undefined) { + this.halt( + blockData.block_number, + new Error(`Transaction failed for block #${blockData.block_number}`) ); - // No need to call updateEvent for blocks unless you want to track them as events - } catch (error) { - this.logger.error(`Failed to process block #${blockData.block_number}:`, error); - this.addFailedBlock(blockData, error); } } @@ -609,14 +617,15 @@ export class StarknetIndexer { clearTimeout(this.pollTimeout); } - if (this.healthCheckInterval) { - clearInterval(this.healthCheckInterval); - } - if (this.retryTimeout) { clearTimeout(this.retryTimeout); this.retryTimeout = undefined; } + + if (this.healthCheckInterval) { + clearInterval(this.healthCheckInterval); + } + this.stopProgressUiLoop(); // Unsubscribe from new heads subscription to stop event processing @@ -701,6 +710,7 @@ export class StarknetIndexer { } private async processBlockQueue(): Promise { + if (this.isHalted()) return; if (this.blockQueue.length === 0) return; const TAG = 'processBlockQueue'; const blocksToProcess = [...this.blockQueue]; @@ -823,6 +833,11 @@ export class StarknetIndexer { this.progressStats.initSyncStats(fromBlock, toBlock); for (let blockNumber = fromBlock; blockNumber <= toBlock; blockNumber += chunkSize) { + if (this.isHalted()) { + this.logger.warn(`[${TAG}] Halted — aborting historical block sync`); + return; + } + const chunkEndBlock = Math.min(blockNumber + chunkSize - 1, toBlock); const chunkLabel = `blocks_${blockNumber}_to_${chunkEndBlock}`; @@ -872,37 +887,43 @@ export class StarknetIndexer { const fetchDuration = Date.now() - time; this.logger.debug(`[${TAG}] Fetched ${blocks.length} blocks in ${fetchDuration}ms`); - // Process all blocks and their events in a single transaction - await this.withTransaction( - `Processing blocks ${blockNumber} to ${chunkEndBlock} and their events`, - async () => { - const insertStart = Date.now(); + if (blocks.length === 0) { + this.logger.info( + `[${TAG}] Skipping blocks ${blockNumber} to ${chunkEndBlock} - already processed` + ); + continue; + } - // Batch insert all blocks - if (blocks.length > 0) { - await this.dbHandler.batchInsertBlocks(blocks); + // Sort blocks by number to ensure correct ordering for partial progress + blocks.sort((a, b) => a.block_number - b.block_number); - this.logger.debug( - `[${TAG}] Inserted ${blocks.length} blocks in ${Date.now() - insertStart}ms` - ); - const eventsStart = Date.now(); + // Process each block individually in its own transaction for partial progress + for (const block of blocks) { + if (this.isHalted()) { + return; + } - if (blocks.length < chunkSize) { - const blockRanges = groupConsecutiveBlocks(blocks.map((block) => block.block_number)); - for (const range of blockRanges) { - await this.processBlockEvents(range.from, range.to); - } - } else { - await this.processBlockEvents(blockNumber, chunkEndBlock); - } - this.logger.debug(`[${TAG}] Events processed in ${Date.now() - eventsStart}ms`); - } else { - this.logger.info( - `[${TAG}] Skipping blocks ${blockNumber} to ${chunkEndBlock} - already processed` - ); - } + const result = await this.withTransaction( + `Processing block #${block.block_number}`, + async () => { + await this.insertBlock(block); + await this.processBlockEvents(block.block_number, block.block_number); + await this.updateCursor(block.block_number, block.block_hash); + return true; + }, + { blockNumber: block.block_number } + ); + + if (result === undefined) { + this.halt( + block.block_number, + new Error(`Transaction failed for block #${block.block_number}`) + ); + return; } - ); + + this.logger.debug(`[${TAG}] Committed block #${block.block_number}`); + } } } @@ -1082,62 +1103,92 @@ export class StarknetIndexer { return await this.dbHandler.checkIsBlockProcessed(blockNumber); } - private addFailedBlock(blockData: QueuedBlock, _error: any): void { - if (!this.failedBlocks.includes(blockData.block_number)) { - this.failedBlocks.push(blockData.block_number); - this.logger.warn(`Added block #${blockData.block_number} to failed blocks queue`); + private halt(blockNumber: number, error: any): void { + this.halted = true; + this.haltReason = { blockNumber, error }; + + if (!this.failedBlocks.includes(blockNumber)) { + this.failedBlocks.push(blockNumber); } - // Start retry process if it's not already running + this.logger.error( + `[HALTED] Indexer halted due to failure at block #${blockNumber}. ` + + `Error: ${error instanceof Error ? error.message : String(error)}. ` + + `Will retry automatically every ${this.RETRY_INTERVAL / 1000}s.` + ); + if (!this.retryTimeout) { this.startRetryProcess(); } } - private async retryFailedBlocks(): Promise { - if (this.failedBlocks.length === 0) return; - - const latestBlock = await this.provider!.getBlock('latest'); - const blocksToRetry = this.failedBlocks.filter( - (blockNumber) => blockNumber < latestBlock.block_number - ); - - if (blocksToRetry.length === 0) return; + private resume(): void { + this.halted = false; + this.haltReason = null; + this.logger.info('[RESUMED] Indexer resumed after successful retry of all failed blocks.'); + } - this.logger.info(`Attempting to retry ${blocksToRetry.length} failed blocks`); + private async retryFailedBlocks(): Promise { + const blocksToRetry = [...this.failedBlocks]; + this.logger.info(`[RETRY] Attempting to retry ${blocksToRetry.length} failed block(s): ${blocksToRetry.join(', ')}`); for (const blockNumber of blocksToRetry) { try { await this.processHistoricalBlocks(blockNumber, blockNumber); - // Remove from failed blocks if successful - this.failedBlocks = this.failedBlocks.filter((b) => b !== blockNumber); + + if (!this.halted) { + // Block succeeded — remove from failed list + this.failedBlocks = this.failedBlocks.filter((b) => b !== blockNumber); + this.logger.info(`[RETRY] Block #${blockNumber} succeeded on retry.`); + } else { + // processHistoricalBlocks re-halted on this block, stop retrying further + this.logger.warn(`[RETRY] Block #${blockNumber} still failing.`); + return; + } } catch (error) { - this.logger.error(`Retry failed for block #${blockNumber}:`, error); + this.logger.error(`[RETRY] Unexpected error retrying block #${blockNumber}:`, error); + return; } } + + if (this.failedBlocks.length === 0) { + this.resume(); + } } private startRetryProcess(): void { - const retryProcess = async () => { - if (!this.started) return; + this.retryTimeout = setTimeout(async () => { + this.retryTimeout = undefined; - try { - await this.retryFailedBlocks(); - } catch (error) { - this.logger.error('Error in retry process:', error); + if (this.failedBlocks.length === 0) { + return; } - // Only schedule next retry if there are failed blocks + // Temporarily clear halt so processHistoricalBlocks can run + this.halted = false; + this.haltReason = null; + + await this.retryFailedBlocks(); + + // If still have failed blocks, schedule next retry if (this.failedBlocks.length > 0) { - this.retryTimeout = setTimeout(retryProcess, this.RETRY_INTERVAL); - } else { - this.logger.debug('No failed blocks to retry, stopping retry process'); - clearTimeout(this.retryTimeout); - this.retryTimeout = undefined; + this.startRetryProcess(); } - }; + }, this.RETRY_INTERVAL); + } + + private isHalted(): boolean { + if (this.halted) { + this.logger.warn( + `[HALTED] Indexer is halted (block #${this.haltReason?.blockNumber}). Ignoring request to process.` + ); + return true; + } + return false; + } - retryProcess(); + public getHaltState(): { halted: boolean; reason: { blockNumber: number; error: any } | null } { + return { halted: this.halted, reason: this.haltReason }; } private startProgressUiLoop() { diff --git a/packages/auco-indexer/src/types/indexer.ts b/packages/auco-indexer/src/types/indexer.ts index 2a44f99..9911ad5 100644 --- a/packages/auco-indexer/src/types/indexer.ts +++ b/packages/auco-indexer/src/types/indexer.ts @@ -108,6 +108,13 @@ export interface IndexerConfig { */ maxHistoricalBlockConcurrentRequests?: number; + /** + * Interval in milliseconds between retry attempts for failed blocks (default: 10000) + * When a block fails processing, the indexer halts and retries the failed block(s) + * on this interval until they succeed. + */ + retryInterval?: number; + /** Enable UI progress bar for tracking indexer progress */ enableUiProgress?: boolean;